Skip to content

Commit 2569144

Browse files
authored
feat: Add True / False Bools + fix bugs, TODOs, new Stringer (#11)
Add True / False Bools + fix bugs, TODOs, new Stringer
1 parent 3a5552c commit 2569144

13 files changed

+1048
-46
lines changed

.golangci.yml

+823
Large diffs are not rendered by default.

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,20 @@ Both examples will happily accept a `Value` of type `String` or `Number` and pro
115115

116116
Numbers implement arbitrary precision fixed-point decimal arithmetic with [shopspring/decimal](https://github.com/shopspring/decimal).
117117

118+
## Strings
119+
120+
Strings must be enclosed in double-quotes (`"`) e.g. valid: `"this is a string"`, invalid: `this is a syntax error` (missing double-quotes).
121+
122+
Escapes are supported:
123+
- `"this is \"also\" a valid string"`
124+
- `"this is fine too\\"` (escapes cancel each other out)
125+
126+
## Bools
127+
128+
In additional to boolean expressions, sepcial contants `True` and `False` may be used.
129+
130+
Do not double-quote them, or they will become plain strings!
131+
118132
## Supported operations
119133

120134
* Operators: `+` `-` `*` `/` `%` `**` `<<` `>>` `<` `<=` `==` `!=` `>` `>=` `And` `&&` `Or` `||`

function.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func PiLong(args ...Value) Value {
103103
return NewUndefinedWithReasonf("pi() requires no argument, got %d", len(args))
104104
}
105105

106-
pi, _ := NewNumberFromString(Pi51199)
106+
pi, _ := NewNumberFromString(Pi51199) //nolint: errcheck
107107

108108
return pi
109109
}

function_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/seborama/gal/v8"
8+
89
"github.com/stretchr/testify/assert"
910
)
1011

gal.go

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

3+
import "fmt"
4+
35
type exprType int
46

57
const (
@@ -10,6 +12,7 @@ const (
1012
stringType
1113
variableType
1214
functionType
15+
boolType
1316
)
1417

1518
type Value interface {
@@ -33,6 +36,7 @@ type Value interface {
3336
Or(Value) Bool
3437
// Helpers
3538
Stringer
39+
fmt.Stringer
3640
entry
3741
}
3842

gal_test.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package gal_test
22

33
import (
4-
"fmt"
54
"testing"
65

76
"github.com/google/go-cmp/cmp"
8-
"github.com/seborama/gal/v8"
97
"github.com/stretchr/testify/assert"
8+
9+
"github.com/seborama/gal/v8"
1010
)
1111

1212
func TestEval(t *testing.T) {
@@ -130,6 +130,10 @@ func TestEval_Boolean(t *testing.T) {
130130
expr = `( 123 == 123 And 12 > 45 ) Or ( "b" != "b" )`
131131
val = gal.Parse(expr).Eval()
132132
assert.Equal(t, gal.False.String(), val.String())
133+
134+
expr = `True Or False`
135+
val = gal.Parse(expr).Eval()
136+
assert.Equal(t, gal.True.String(), val.String())
133137
}
134138

135139
func TestWithVariablesAndFunctions(t *testing.T) {
@@ -298,11 +302,9 @@ func TestMultiValueFunctions(t *testing.T) {
298302
// That way, it can receiv either two Numberer's or one single MultiValue that holds 2 Numberer's.
299303
var margs gal.MultiValue
300304
if len(args) == 1 {
301-
fmt.Println("DEBUG - a single MultiValue")
302305
margs = args[0].(gal.MultiValue) // not checking type satisfaction for simplicity
303306
}
304307
if len(args) == 2 {
305-
fmt.Println("DEBUG - two Value's")
306308
margs = gal.NewMultiValue(args...)
307309
}
308310
if margs.Size() != 2 {
@@ -328,7 +330,7 @@ func TestStringsWithSpaces(t *testing.T) {
328330
parsedExpr := gal.Parse(expr)
329331

330332
got := parsedExpr.Eval()
331-
assert.Equal(t, "ab cdef gh", got.String())
333+
assert.Equal(t, `"ab cdef gh"`, got.String())
332334
}
333335

334336
func TestFunctionsAndStringsWithSpaces(t *testing.T) {
@@ -345,5 +347,5 @@ func TestFunctionsAndStringsWithSpaces(t *testing.T) {
345347
},
346348
}),
347349
)
348-
assert.Equal(t, "ab cdef gh", got.String())
350+
assert.Equal(t, `"ab cdef gh"`, got.String())
349351
}

tree.go

+70-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
package gal
22

3+
import (
4+
"fmt"
5+
"log/slog"
6+
"strings"
7+
)
8+
39
type entryKind int
410

11+
func (ek entryKind) String() string {
12+
switch ek {
13+
case unknownEntryKind:
14+
return "unknownEntryKind"
15+
case valueEntryKind:
16+
return "valueEntryKind"
17+
case operatorEntryKind:
18+
return "operatorEntryKind"
19+
case treeEntryKind:
20+
return "treeEntryKind"
21+
case functionEntryKind:
22+
return "functionEntryKind"
23+
case variableEntryKind:
24+
return "variableEntryKind"
25+
default:
26+
return fmt.Sprintf("unknown:%d", ek)
27+
}
28+
}
29+
530
const (
631
unknownEntryKind entryKind = iota
732
valueEntryKind
@@ -90,7 +115,7 @@ func WithFunctions(funcs Functions) treeOption {
90115
// It accepts optional functional parameters to supply user-defined
91116
// entities such as functions and variables.
92117
func (tree Tree) Eval(opts ...treeOption) Value {
93-
//config
118+
// config
94119
cfg := &treeConfig{}
95120

96121
for _, o := range opts {
@@ -141,22 +166,27 @@ func (tree Tree) Split() []Tree {
141166
// For instance, a tree representing the expression '2 + 5 * 4 / 2' with an operator precedence
142167
// of 'multiplicativeOperators' would read the Tree left to right and return a new Tree that
143168
// represents: '2 + 10' where 10 was calculated (and reduced) from 5 * 4 = 20 / 2 = 10.
169+
//
170+
// nolint: gocognit,gocyclo,cyclop
144171
func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *treeConfig) Tree {
145172
var outTree Tree
146173

147174
var val entry
148-
var op Operator = invalidOperator
175+
var op Operator = invalidOperator //nolint: stylecheck
149176

150177
for i := 0; i < tree.TrunkLen(); i++ {
151178
e := tree[i]
179+
slog.Debug("Tree.Calc: entry in Tree", "i", i, "kind", e.kind().String())
152180
if e == nil {
181+
slog.Debug("Tree.Calc: nil entry in Tree")
153182
return Tree{
154183
NewUndefinedWithReasonf("syntax error: nil value at tree entry #%d - tree: %+v", i, tree),
155184
}
156185
}
157186

158187
switch e.kind() {
159188
case valueEntryKind:
189+
slog.Debug("Tree.Calc: valueEntryKind", "i", i, "Value", e.(Value).String())
160190
if val == nil && op == invalidOperator {
161191
val = e
162192
continue
@@ -168,9 +198,12 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree
168198
}
169199
}
170200

201+
slog.Debug("Tree.Calc: valueEntryKind - calculate", "i", i, "val", val.(Value).String(), "op", op.String(), "e", e.(Value).String())
171202
val = calculate(val.(Value), op, e.(Value))
203+
slog.Debug("Tree.Calc: valueEntryKind - calculate", "i", i, "result", val.(Value).String())
172204

173205
case treeEntryKind:
206+
slog.Debug("Tree.Calc: treeEntryKind", "i", i)
174207
if val == nil && op != invalidOperator {
175208
return Tree{
176209
NewUndefinedWithReasonf("syntax error: missing left hand side value for operator '%s'", op.String()),
@@ -184,9 +217,11 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree
184217
}
185218

186219
val = calculate(val.(Value), op, rhsVal)
220+
slog.Debug("Tree.Calc: treeEntryKind - calculate", "i", i, "val", val.(Value).String(), "op", op.String(), "rhsVal", rhsVal.String(), "result", val.(Value).String())
187221

188222
case operatorEntryKind:
189-
op = e.(Operator)
223+
slog.Debug("Tree.Calc: operatorEntryKind", "i", i, "Value", e.(Operator).String())
224+
op = e.(Operator) //nolint: errcheck
190225
if isOperatorInPrecedenceGroup(op) {
191226
// same operator precedence: keep operating linearly, do not build a tree
192227
continue
@@ -199,7 +234,8 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree
199234
op = invalidOperator
200235

201236
case functionEntryKind:
202-
f := e.(Function)
237+
slog.Debug("Tree.Calc: functionEntryKind", "i", i, "name", e.(Function).Name)
238+
f := e.(Function) //nolint: errcheck
203239
if f.BodyFn == nil {
204240
f.BodyFn = cfg.functions.Function(f.Name)
205241
}
@@ -210,22 +246,26 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree
210246
}
211247

212248
val = calculate(val.(Value), op, rhsVal)
249+
slog.Debug("Tree.Calc: functionEntryKind - calculate", "i", i, "val", val.(Value).String(), "op", op.String(), "rhsVal", rhsVal.String(), "result", val.(Value).String())
213250

214251
case variableEntryKind:
252+
slog.Debug("Tree.Calc: variableEntryKind", "i", i, "name", e.(Variable).Name)
215253
varName := e.(Variable).Name
216254
rhsVal, ok := cfg.Variable(varName)
217255
if !ok {
218256
return Tree{
219257
NewUndefinedWithReasonf("syntax error: unknown variable name: '%s'", varName),
220258
}
221259
}
260+
slog.Debug("Tree.Calc: variableEntryKind", "i", i, "value", rhsVal.String())
222261

223262
if val == nil {
224263
val = rhsVal
225264
continue
226265
}
227266

228267
val = calculate(val.(Value), op, rhsVal)
268+
slog.Debug("Tree.Calc: variableEntryKind - calculate", "i", i, "val", val.(Value).String(), "op", op.String(), "rhsVal", rhsVal.String(), "result", val.(Value).String())
229269

230270
case unknownEntryKind:
231271
return Tree{e}
@@ -274,6 +314,32 @@ func (Tree) kind() entryKind {
274314
return treeEntryKind
275315
}
276316

317+
func (tree Tree) String(indents ...string) string {
318+
indent := strings.Join(indents, "")
319+
320+
res := ""
321+
for _, e := range tree {
322+
switch e.kind() {
323+
case unknownEntryKind:
324+
res += fmt.Sprintf(indent+"unknownEntryKind %T\n", e)
325+
case valueEntryKind:
326+
res += fmt.Sprintf(indent+"Value %T %s\n", e, e.(Value).String())
327+
case operatorEntryKind:
328+
res += fmt.Sprintf(indent+"Operator %s\n", e.(Operator).String())
329+
case treeEntryKind:
330+
res += fmt.Sprintf(indent+"Tree {\n%s}\n", e.(Tree).String(" "))
331+
case functionEntryKind:
332+
res += fmt.Sprintf(indent+"Function %s\n", e.(Function).Name)
333+
case variableEntryKind:
334+
res += fmt.Sprintf(indent+"Variable %s\n", e.(Variable).Name)
335+
default:
336+
res += fmt.Sprintf(indent+"undefined %T %s\n", e, e.kind().String())
337+
}
338+
}
339+
return res
340+
}
341+
342+
// nolint: gocognit,gocyclo,cyclop
277343
func calculate(lhs Value, op Operator, rhs Value) Value {
278344
var outVal Value
279345

0 commit comments

Comments
 (0)