Skip to content

Commit

Permalink
302 calculate (AplaProject#123)
Browse files Browse the repository at this point in the history
* Added calculate

* Added calcExp function

* Added dividing by zero error

* using IsDigit
  • Loading branch information
gentee committed Jan 22, 2018
1 parent 6c56038 commit 914d838
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 0 deletions.
281 changes: 281 additions & 0 deletions packages/template/calculate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
// Copyright 2016 The go-daylight Authors
// This file is part of the go-daylight library.
//
// The go-daylight library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-daylight library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-daylight library. If not, see <http://www.gnu.org/licenses/>.

package template

import (
"errors"
"fmt"
"strconv"
"strings"
"unicode"

"github.com/shopspring/decimal"
)

const (
tkNumber = iota
tkAdd
tkSub
tkMul
tkDiv
tkLPar
tkRPar

expInt = 0
expFloat = 1
expMoney = 2
)

type token struct {
Type int
Value interface{}
}

type opFunc func()

var (
errExp = errors.New(`wrong expression`)
errDiv = errors.New(`dividing by zero`)
)

func parsing(input string, itype int) (*[]token, error) {
var err error

tokens := make([]token, 0)
newToken := func(itype int, value interface{}) {
tokens = append(tokens, token{itype, value})
}
prevNumber := func() bool {
return len(tokens) > 0 && tokens[len(tokens)-1].Type == tkNumber
}
prevOper := func() bool {
return len(tokens) > 0 && (tokens[len(tokens)-1].Type >= tkAdd &&
tokens[len(tokens)-1].Type <= tkDiv)
}
var (
numlen int
)
ops := map[rune]struct {
id int
pr int
}{
'+': {tkAdd, 1},
'-': {tkSub, 1},
'*': {tkMul, 2},
'/': {tkDiv, 2},
}
for off, ch := range input {
if unicode.IsDigit(ch) || ch == '.' {
numlen++
continue
}
if numlen > 0 {
var val interface{}

switch itype {
case expInt:
val, err = strconv.ParseInt(input[off-numlen:off], 10, 64)
case expFloat:
val, err = strconv.ParseFloat(input[off-numlen:off], 64)
case expMoney:
val, err = decimal.NewFromString(input[off-numlen : off])
}
if err != nil {
return nil, err
}
if prevNumber() {
return nil, errExp
}
newToken(tkNumber, val)
numlen = 0
}
if item, ok := ops[ch]; ok {
if prevOper() {
return nil, errExp
}
newToken(item.id, item.pr)
continue
}
switch ch {
case '(':
if prevNumber() {
return nil, errExp
}
newToken(tkLPar, 3)
case ')':
if prevOper() {
return nil, errExp
}
newToken(tkRPar, 3)
case ' ', '\t', '\n', '\r':
default:
return nil, errExp
}
}
return &tokens, nil
}

func calcExp(tokens []token, resType, prec int) string {
var top int

stack := make([]interface{}, 0, 16)

addInt := func() {
stack[top-1] = stack[top-1].(int64) + stack[top].(int64)
}
addFloat := func() {
stack[top-1] = stack[top-1].(float64) + stack[top].(float64)
}
addMoney := func() {
stack[top-1] = stack[top-1].(decimal.Decimal).Add(stack[top].(decimal.Decimal))
}
subInt := func() {
stack[top-1] = stack[top-1].(int64) - stack[top].(int64)
}
subFloat := func() {
stack[top-1] = stack[top-1].(float64) - stack[top].(float64)
}
subMoney := func() {
stack[top-1] = stack[top-1].(decimal.Decimal).Sub(stack[top].(decimal.Decimal))
}
mulInt := func() {
stack[top-1] = stack[top-1].(int64) * stack[top].(int64)
}
mulFloat := func() {
stack[top-1] = stack[top-1].(float64) * stack[top].(float64)
}
mulMoney := func() {
stack[top-1] = stack[top-1].(decimal.Decimal).Mul(stack[top].(decimal.Decimal))
}
divInt := func() {
stack[top-1] = stack[top-1].(int64) / stack[top].(int64)
}
divFloat := func() {
stack[top-1] = stack[top-1].(float64) / stack[top].(float64)
}
divMoney := func() {
stack[top-1] = stack[top-1].(decimal.Decimal).Div(stack[top].(decimal.Decimal))
}

funcs := map[int][]opFunc{
tkAdd: {addInt, addFloat, addMoney},
tkSub: {subInt, subFloat, subMoney},
tkMul: {mulInt, mulFloat, mulMoney},
tkDiv: {divInt, divFloat, divMoney},
}
for _, item := range tokens {
if item.Type == tkNumber {
stack = append(stack, item.Value)
} else {
if len(stack) < 2 {
return errExp.Error()
}
top = len(stack) - 1
if item.Type == tkDiv {
switch resType {
case expInt:
if stack[top].(int64) == 0 {
return errDiv.Error()
}
case expFloat:
if stack[top].(float64) == 0 {
return errDiv.Error()
}
case expMoney:
if stack[top].(decimal.Decimal).Cmp(decimal.New(0, 0)) == 0 {
return errDiv.Error()
}
}
}
funcs[item.Type][resType]()
stack = stack[:top]
}
}
if len(stack) != 1 {
return errExp.Error()
}
if prec > 0 {
if resType == expFloat {
return strconv.FormatFloat(stack[0].(float64), 'f', prec, 64)
}
if resType == expMoney {
money := fmt.Sprint(stack[0])
if len(money) < prec+1 {
money = strings.Repeat(`0`, prec+1-len(money)) + money
}
money = money[:len(money)-prec] + `.` + money[len(money)-prec:]
return strings.TrimRight(strings.TrimRight(money, `0`), `.`)
}
}
return fmt.Sprint(stack[0])
}

func calculate(exp, etype string, prec int) string {
var resType int
if len(etype) == 0 && strings.Contains(exp, `.`) {
etype = `float`
}
switch etype {
case `float`:
resType = expFloat
case `money`:
resType = expMoney
}
tk, err := parsing(exp+` `, resType)
if err != nil {
return err.Error()
}
stack := make([]token, 0, len(*tk))
buf := make([]token, 0, 10)
for _, item := range *tk {
switch item.Type {
case tkNumber:
stack = append(stack, item)
case tkLPar:
buf = append(buf, item)
case tkRPar:
i := len(buf) - 1
for i >= 0 && buf[i].Type != tkLPar {
stack = append(stack, buf[i])
i--
}
if i < 0 {
return errExp.Error()
}
buf = buf[:i]
default:
if len(buf) > 0 {
last := buf[len(buf)-1]
if last.Type != tkLPar && last.Value.(int) >= item.Value.(int) {
stack = append(stack, last)
buf[len(buf)-1] = item
continue
}
}
buf = append(buf, item)
}
}
for i := len(buf) - 1; i >= 0; i-- {
last := buf[i]
if last.Type >= tkAdd && last.Type <= tkDiv {
stack = append(stack, last)
} else {
return errExp.Error()
}
}
return calcExp(stack, resType, prec)
}
6 changes: 6 additions & 0 deletions packages/template/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func init() {
funcs[`Lower`] = tplFunc{lowerTag, defaultTag, `lower`, `Text`}
funcs[`AddToolButton`] = tplFunc{defaultTag, defaultTag, `addtoolbutton`, `Title,Icon,Page,PageParams`}
funcs[`Address`] = tplFunc{addressTag, defaultTag, `address`, `Wallet`}
funcs[`Calculate`] = tplFunc{calculateTag, defaultTag, `calculate`, `Exp,Type,Prec`}
funcs[`CmpTime`] = tplFunc{cmpTimeTag, defaultTag, `cmptime`, `Time1,Time2`}
funcs[`Code`] = tplFunc{defaultTag, defaultTag, `code`, `Text`}
funcs[`DateTime`] = tplFunc{dateTimeTag, defaultTag, `datetime`, `DateTime,Format`}
Expand Down Expand Up @@ -204,6 +205,11 @@ func addressTag(par parFunc) string {
return converter.AddressToString(id)
}

func calculateTag(par parFunc) string {
return calculate((*par.Pars)[`Exp`], (*par.Pars)[`Type`],
converter.StrToInt((*par.Pars)[`Prec`]))
}

func ecosysparTag(par parFunc) string {
if len((*par.Pars)[`Name`]) == 0 {
return ``
Expand Down
7 changes: 7 additions & 0 deletions packages/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ func TestJSON(t *testing.T) {
}

var forTest = tplList{
{`Calculate( Exp: 342278783438/0, Type: money )Calculate( Exp: 5.2/0, Type: float )
Calculate( Exp: 7/0)`,
`[{"tag":"text","text":"dividing by zerodividing by zerodividing by zero"}]`},
{`SetVar(val, 2200000034343443343430000)SetVar(zero, 0)Calculate( Exp: (342278783438+5000)*(#val#-932780000), Type: money, Prec:18 )Calculate( Exp: (2+50)*(#zero#-9), Type: money )`,
`[{"tag":"text","text":"753013346318631859.1075080680647-468"}]`},
{`SetVar(val, 100)Calculate(10000-(34+5)*#val#)=Calculate("((10+#val#-45)*3.0-10)/4.5 + #val#", Prec: 4)`,
`[{"tag":"text","text":"6100"},{"tag":"text","text":"=141.1111"}]`},
{`Span((span text), ok )Span(((span text), ok) )Div(){{My body}}`,
`[{"tag":"span","attr":{"class":"ok"},"children":[{"tag":"text","text":"(span text)"}]},{"tag":"span","children":[{"tag":"text","text":"((span text), ok)"}]},{"tag":"div","children":[{"tag":"text","text":"{My body}"}]}]`},
{`Code(P(Some text)
Expand Down

0 comments on commit 914d838

Please sign in to comment.