Skip to content

Commit d6f8163

Browse files
committed
added variables
1 parent 06670d2 commit d6f8163

7 files changed

+557
-494
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ Numbers implement arbitrary precision fixed-point decimal arithmetic with [shops
1919
* Functions:
2020
* Pre-defined: pi, cos, floor, sin, sqrt, trunc, and more (see `function.go`: `Eval()`)
2121
* User-defined: TODO
22+
* Variables, defined as `:variable_name:`
23+
24+
## Functions
25+
26+
Function names are case-insensitive.
27+
A function can optionally accept one or more arguments but it must return a single Value.
28+
29+
## Variables
30+
31+
Variable names are case-sensitive.
32+
33+
Values are passed as a `map[string]Value` using `WithVariables` when calling `Eval`.
2234

2335
## High level design
2436

gal.go

+4-284
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package gal
22

3-
import (
4-
"fmt"
5-
6-
"github.com/pkg/errors"
7-
)
8-
93
type exprType int
104

115
const (
@@ -29,287 +23,13 @@ type Value interface {
2923
entry
3024
}
3125

32-
func Eval(expr string) Value {
33-
tree, err := buildExprTree(expr)
26+
func Eval(expr string, opts ...option) Value {
27+
treeBuilder := NewTreeBuilder(opts...)
28+
29+
tree, err := treeBuilder.FromExpr(expr)
3430
if err != nil {
3531
return NewUndefinedWithReasonf(err.Error())
3632
}
3733

3834
return tree.Eval()
3935
}
40-
41-
func buildExprTree(expr string) (Tree, error) {
42-
exprTree := Tree{}
43-
44-
for idx := 0; idx < len(expr); {
45-
part, ptype, length, err := extractPart(expr[idx:])
46-
if err != nil {
47-
return nil, err
48-
}
49-
50-
if ptype == blankType {
51-
break
52-
}
53-
54-
switch ptype {
55-
case numericalType:
56-
v, err := NewNumberFromString(part)
57-
if err != nil {
58-
return nil, err
59-
}
60-
exprTree = append(exprTree, v)
61-
62-
case stringType:
63-
v := NewString(part)
64-
exprTree = append(exprTree, v)
65-
66-
case operatorType:
67-
switch part {
68-
case plus.String():
69-
exprTree = append(exprTree, plus)
70-
case minus.String():
71-
exprTree = append(exprTree, minus)
72-
case multiply.String():
73-
exprTree = append(exprTree, multiply)
74-
case divide.String():
75-
exprTree = append(exprTree, divide)
76-
case modulus.String():
77-
exprTree = append(exprTree, modulus)
78-
default:
79-
return nil, errors.WithStack(newErrUnknownOperator(part))
80-
}
81-
82-
case functionType:
83-
// TODO: squash the leading and trailing '()'
84-
fname, l, _ := readFunctionName(part)
85-
v, err := buildExprTree(part[l+1 : len(part)-1]) // exclude leading '(' and trailing ')'
86-
if err != nil {
87-
return nil, err
88-
}
89-
if fname == "" {
90-
exprTree = append(exprTree, v)
91-
} else {
92-
exprTree = append(exprTree, NewFunction(fname, v.Split()...))
93-
}
94-
95-
case variableType:
96-
v := NewVariable(part)
97-
exprTree = append(exprTree, v)
98-
99-
default:
100-
return nil, newErrSyntaxError(fmt.Sprintf("internal error: unknown expression part type '%v'", ptype))
101-
}
102-
103-
idx += length
104-
}
105-
106-
return exprTree, nil
107-
}
108-
109-
// returns the part extracted as string, the type extracted, the cursor position
110-
// after extraction or an error.
111-
func extractPart(expr string) (string, exprType, int, error) {
112-
// left trim blanks
113-
pos := 0
114-
for _, r := range expr {
115-
if !isBlankSpace(r) {
116-
break
117-
}
118-
pos++
119-
}
120-
121-
// blank: no part
122-
if pos == len(expr) {
123-
return "", blankType, pos, nil
124-
}
125-
126-
// read part - "string"
127-
if expr[pos] == '"' {
128-
s, l, err := readString(expr[pos:])
129-
if err != nil {
130-
return "", unknownType, 0, err
131-
}
132-
return s, stringType, pos + l, nil
133-
}
134-
135-
// read part - :variable:
136-
if expr[pos] == ':' {
137-
s, l, err := readVariable(expr[pos:])
138-
if err != nil {
139-
return "", unknownType, 0, err
140-
}
141-
return s, variableType, pos + l, nil
142-
}
143-
144-
// read part - function(...)
145-
// conceptually, parenthesis grouping is a special case of anonymous identity function
146-
if expr[pos] == '(' || (expr[pos] >= 'a' && expr[pos] <= 'z') || (expr[pos] >= 'A' && expr[pos] <= 'Z') {
147-
fname, lf, err := readFunctionName(expr[pos:])
148-
if err != nil {
149-
return "", unknownType, 0, err
150-
}
151-
fargs, la, err := readFunctionArguments(expr[pos+lf:])
152-
if err != nil {
153-
return "", unknownType, 0, err
154-
}
155-
return fname + fargs, functionType, pos + lf + la, nil
156-
}
157-
158-
// read part - operator
159-
// TODO: only single character operators are supported
160-
if isOperator(string(expr[pos])) {
161-
if expr[pos] == '+' || expr[pos] == '-' {
162-
s, l := squashPlusMinusChain(expr[pos:])
163-
return s, operatorType, pos + l, nil
164-
}
165-
return string(expr[pos]), operatorType, pos + 1, nil
166-
}
167-
168-
// read part - number
169-
// TODO: complex numbers are not supported
170-
s, l, err := readNumber(expr[pos:])
171-
if err != nil {
172-
return "", unknownType, 0, err
173-
}
174-
return s, numericalType, pos + l, nil
175-
}
176-
177-
func readString(expr string) (string, int, error) {
178-
to := 1 // keep leading double-quotes
179-
escapes := 0
180-
181-
for i, r := range expr[1:] {
182-
to += 1
183-
if expr[i] == '\\' {
184-
escapes += 1
185-
continue
186-
}
187-
if r == '"' && (escapes == 0 || escapes&1 == 0) {
188-
break
189-
}
190-
191-
escapes = 0
192-
}
193-
194-
if expr[to-1] != '"' {
195-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("non-terminated string '%s'", expr[:to])))
196-
}
197-
198-
return expr[:to], to, nil
199-
}
200-
201-
func readVariable(expr string) (string, int, error) {
202-
to := 1 // keep leading ':'
203-
204-
for _, r := range expr[1:] {
205-
to += 1
206-
if r == ':' {
207-
break
208-
}
209-
if isBlankSpace(r) {
210-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("invalid character '%c' for variable name '%s'", r, expr[:to])))
211-
}
212-
}
213-
214-
if expr[to-1] != ':' {
215-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("missing ':' to end variable '%s'", expr[:to])))
216-
}
217-
218-
return expr[:to], to, nil
219-
}
220-
221-
func readFunctionName(expr string) (string, int, error) {
222-
to := 0 // this could be an anonymous identity function (i.e. simple case of parenthesis grouping)
223-
224-
for _, r := range expr {
225-
if r == '(' {
226-
return expr[:to], to, nil
227-
}
228-
if isBlankSpace(r) {
229-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("invalid character '%c' for function name '%s'", r, expr[:to])))
230-
}
231-
to += 1
232-
}
233-
234-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("missing '(' for function name '%s'", expr[:to])))
235-
}
236-
237-
func readFunctionArguments(expr string) (string, int, error) {
238-
to := 1
239-
bktCount := 1 // the currently opened bracket
240-
241-
for _, r := range expr[1:] {
242-
to += 1
243-
if r == '(' {
244-
bktCount++
245-
continue
246-
}
247-
if r == ')' {
248-
bktCount--
249-
if bktCount == 0 {
250-
return expr[:to], to, nil
251-
}
252-
}
253-
// TODO: handle stringType
254-
}
255-
256-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("missing ')' for function arguments '%s'", expr[:to])))
257-
}
258-
259-
func readNumber(expr string) (string, int, error) {
260-
to := 0
261-
isFloat := false
262-
263-
for _, r := range expr {
264-
if isBlankSpace(r) || isOperator(string(r)) {
265-
break
266-
}
267-
268-
to++
269-
270-
if r == '.' && !isFloat {
271-
isFloat = true
272-
continue
273-
}
274-
if r >= '0' && r <= '9' {
275-
continue
276-
}
277-
278-
return "", 0, errors.WithStack(newErrSyntaxError(fmt.Sprintf("invalid character '%c' for number '%s'", r, expr[:to])))
279-
}
280-
281-
return expr[:to], to, nil
282-
}
283-
284-
func squashPlusMinusChain(expr string) (string, int) {
285-
to := 0
286-
outcomeSign := 1
287-
288-
for _, r := range expr {
289-
// if isBlankSpace(r) {
290-
// break
291-
// }
292-
if r != '+' && r != '-' && !isBlankSpace(r) {
293-
break
294-
}
295-
if r == '-' {
296-
outcomeSign = -outcomeSign
297-
}
298-
to += 1
299-
}
300-
301-
sign := "-"
302-
if outcomeSign == 1 {
303-
sign = "+"
304-
}
305-
306-
return sign, to
307-
}
308-
309-
func isBlankSpace(r rune) bool {
310-
return r == ' ' || r == '\t' || r == '\n'
311-
}
312-
313-
func isOperator(s string) bool {
314-
return s == "+" || s == "-" || s == "/" || s == "*" || s == "^" || s == "%"
315-
}

0 commit comments

Comments
 (0)