|
1 | 1 | package gal_test
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "fmt" |
4 | 5 | "testing"
|
5 | 6 |
|
6 | 7 | "github.com/google/go-cmp/cmp"
|
7 |
| - "github.com/seborama/gal/v7" |
| 8 | + "github.com/seborama/gal/v8" |
8 | 9 | "github.com/stretchr/testify/assert"
|
9 | 10 | )
|
10 | 11 |
|
@@ -65,6 +66,56 @@ func TestTreeBuilder_FromExpr_UnknownVariable(t *testing.T) {
|
65 | 66 | }
|
66 | 67 | }
|
67 | 68 |
|
| 69 | +func TestEval_Boolean(t *testing.T) { |
| 70 | + expr := `2 > 1` |
| 71 | + val := gal.Parse(expr).Eval() |
| 72 | + assert.Equal(t, gal.True.String(), val.String()) |
| 73 | + |
| 74 | + expr = `2 > 2` |
| 75 | + val = gal.Parse(expr).Eval() |
| 76 | + assert.Equal(t, gal.False.String(), val.String()) |
| 77 | + |
| 78 | + expr = `2 >= 2` |
| 79 | + val = gal.Parse(expr).Eval() |
| 80 | + assert.Equal(t, gal.True.String(), val.String()) |
| 81 | + |
| 82 | + expr = `2 < 1` |
| 83 | + val = gal.Parse(expr).Eval() |
| 84 | + assert.Equal(t, gal.False.String(), val.String()) |
| 85 | + |
| 86 | + expr = `2 < 2` |
| 87 | + val = gal.Parse(expr).Eval() |
| 88 | + assert.Equal(t, gal.False.String(), val.String()) |
| 89 | + |
| 90 | + expr = `2 <= 2` |
| 91 | + val = gal.Parse(expr).Eval() |
| 92 | + assert.Equal(t, gal.True.String(), val.String()) |
| 93 | + |
| 94 | + expr = `2 != 2` |
| 95 | + val = gal.Parse(expr).Eval() |
| 96 | + assert.Equal(t, gal.False.String(), val.String()) |
| 97 | + |
| 98 | + expr = `1 != 2` |
| 99 | + val = gal.Parse(expr).Eval() |
| 100 | + assert.Equal(t, gal.True.String(), val.String()) |
| 101 | + |
| 102 | + expr = `3 != 2` |
| 103 | + val = gal.Parse(expr).Eval() |
| 104 | + assert.Equal(t, gal.True.String(), val.String()) |
| 105 | + |
| 106 | + expr = `2 == 2` |
| 107 | + val = gal.Parse(expr).Eval() |
| 108 | + assert.Equal(t, gal.True.String(), val.String()) |
| 109 | + |
| 110 | + expr = `1 == 2` |
| 111 | + val = gal.Parse(expr).Eval() |
| 112 | + assert.Equal(t, gal.False.String(), val.String()) |
| 113 | + |
| 114 | + expr = `3 == 2` |
| 115 | + val = gal.Parse(expr).Eval() |
| 116 | + assert.Equal(t, gal.False.String(), val.String()) |
| 117 | +} |
| 118 | + |
68 | 119 | func TestWithVariablesAndFunctions(t *testing.T) {
|
69 | 120 | expr := `double(:val1:) + triple(:val2:)`
|
70 | 121 | parsedExpr := gal.Parse(expr)
|
@@ -143,52 +194,140 @@ func TestWithVariablesAndFunctions(t *testing.T) {
|
143 | 194 | }
|
144 | 195 | }
|
145 | 196 |
|
146 |
| -func TestEval_Boolean(t *testing.T) { |
147 |
| - expr := `2 > 1` |
148 |
| - val := gal.Parse(expr).Eval() |
149 |
| - assert.Equal(t, gal.True.String(), val.String()) |
| 197 | +func TestNestedFunctions(t *testing.T) { |
| 198 | + expr := `double(triple(7))` |
| 199 | + parsedExpr := gal.Parse(expr) |
150 | 200 |
|
151 |
| - expr = `2 > 2` |
152 |
| - val = gal.Parse(expr).Eval() |
153 |
| - assert.Equal(t, gal.False.String(), val.String()) |
| 201 | + // step 1: define funcs and vars and Eval the expression |
| 202 | + funcs := gal.Functions{ |
| 203 | + "double": func(args ...gal.Value) gal.Value { |
| 204 | + if len(args) != 1 { |
| 205 | + return gal.NewUndefinedWithReasonf("double() requires a single argument, got %d", len(args)) |
| 206 | + } |
154 | 207 |
|
155 |
| - expr = `2 >= 2` |
156 |
| - val = gal.Parse(expr).Eval() |
157 |
| - assert.Equal(t, gal.True.String(), val.String()) |
| 208 | + value, ok := args[0].(gal.Numberer) |
| 209 | + if !ok { |
| 210 | + return gal.NewUndefinedWithReasonf("double(): syntax error - argument must be a number-like value, got '%v'", args[0]) |
| 211 | + } |
158 | 212 |
|
159 |
| - expr = `2 < 1` |
160 |
| - val = gal.Parse(expr).Eval() |
161 |
| - assert.Equal(t, gal.False.String(), val.String()) |
| 213 | + return value.Number().Multiply(gal.NewNumber(2)) |
| 214 | + }, |
| 215 | + "triple": func(args ...gal.Value) gal.Value { |
| 216 | + if len(args) != 1 { |
| 217 | + return gal.NewUndefinedWithReasonf("triple() requires a single argument, got %d", len(args)) |
| 218 | + } |
162 | 219 |
|
163 |
| - expr = `2 < 2` |
164 |
| - val = gal.Parse(expr).Eval() |
165 |
| - assert.Equal(t, gal.False.String(), val.String()) |
| 220 | + value, ok := args[0].(gal.Numberer) |
| 221 | + if !ok { |
| 222 | + return gal.NewUndefinedWithReasonf("triple(): syntax error - argument must be a number-like value, got '%v'", args[0]) |
| 223 | + } |
166 | 224 |
|
167 |
| - expr = `2 <= 2` |
168 |
| - val = gal.Parse(expr).Eval() |
169 |
| - assert.Equal(t, gal.True.String(), val.String()) |
| 225 | + return value.Number().Multiply(gal.NewNumber(3)) |
| 226 | + }, |
| 227 | + } |
170 | 228 |
|
171 |
| - expr = `2 != 2` |
172 |
| - val = gal.Parse(expr).Eval() |
173 |
| - assert.Equal(t, gal.False.String(), val.String()) |
| 229 | + got := parsedExpr.Eval( |
| 230 | + gal.WithFunctions(funcs), |
| 231 | + ) |
| 232 | + expected := gal.NewNumber(42) |
| 233 | + assert.Equal(t, expected.String(), got.String()) |
| 234 | +} |
174 | 235 |
|
175 |
| - expr = `1 != 2` |
176 |
| - val = gal.Parse(expr).Eval() |
177 |
| - assert.Equal(t, gal.True.String(), val.String()) |
| 236 | +// If renaming this test, also update the README.md file, where it is mentioned. |
| 237 | +func TestMultiValueFunctions(t *testing.T) { |
| 238 | + expr := `sum(div(triple(7) double(4)))` |
| 239 | + parsedExpr := gal.Parse(expr) |
178 | 240 |
|
179 |
| - expr = `3 != 2` |
180 |
| - val = gal.Parse(expr).Eval() |
181 |
| - assert.Equal(t, gal.True.String(), val.String()) |
| 241 | + // step 1: define funcs and vars and Eval the expression |
| 242 | + funcs := gal.Functions{ |
| 243 | + "double": func(args ...gal.Value) gal.Value { |
| 244 | + if len(args) != 1 { |
| 245 | + return gal.NewUndefinedWithReasonf("double() requires a single argument, got %d", len(args)) |
| 246 | + } |
182 | 247 |
|
183 |
| - expr = `2 == 2` |
184 |
| - val = gal.Parse(expr).Eval() |
185 |
| - assert.Equal(t, gal.True.String(), val.String()) |
| 248 | + value, ok := args[0].(gal.Numberer) |
| 249 | + if !ok { |
| 250 | + return gal.NewUndefinedWithReasonf("double(): syntax error - argument must be a number-like value, got '%v'", args[0]) |
| 251 | + } |
186 | 252 |
|
187 |
| - expr = `1 == 2` |
188 |
| - val = gal.Parse(expr).Eval() |
189 |
| - assert.Equal(t, gal.False.String(), val.String()) |
| 253 | + return value.Number().Multiply(gal.NewNumber(2)) |
| 254 | + }, |
| 255 | + "triple": func(args ...gal.Value) gal.Value { |
| 256 | + if len(args) != 1 { |
| 257 | + return gal.NewUndefinedWithReasonf("triple() requires a single argument, got %d", len(args)) |
| 258 | + } |
190 | 259 |
|
191 |
| - expr = `3 == 2` |
192 |
| - val = gal.Parse(expr).Eval() |
193 |
| - assert.Equal(t, gal.False.String(), val.String()) |
| 260 | + value, ok := args[0].(gal.Numberer) |
| 261 | + if !ok { |
| 262 | + return gal.NewUndefinedWithReasonf("triple(): syntax error - argument must be a number-like value, got '%v'", args[0]) |
| 263 | + } |
| 264 | + |
| 265 | + return value.Number().Multiply(gal.NewNumber(3)) |
| 266 | + }, |
| 267 | + "div": func(args ...gal.Value) gal.Value { |
| 268 | + // returns the division of value1 by value2 as the interger portion and the remainder |
| 269 | + if len(args) != 2 { |
| 270 | + return gal.NewUndefinedWithReasonf("mult() requires two arguments, got %d", len(args)) |
| 271 | + } |
| 272 | + |
| 273 | + dividend := args[0].(gal.Numberer).Number() |
| 274 | + divisor := args[1].(gal.Numberer).Number() |
| 275 | + |
| 276 | + quotient := dividend.Divide(divisor).(gal.Numberer).Number().IntPart() |
| 277 | + remainder := dividend.Number().Sub(quotient.(gal.Number).Multiply(divisor.Number())) |
| 278 | + return gal.NewMultiValue(quotient, remainder) |
| 279 | + }, |
| 280 | + "sum": func(args ...gal.Value) gal.Value { |
| 281 | + // NOTE: we convert the args to a MultiValue to make this function "bilingual". |
| 282 | + // That way, it can receiv either two Numberer's or one single MultiValue that holds 2 Numberer's. |
| 283 | + var margs gal.MultiValue |
| 284 | + if len(args) == 1 { |
| 285 | + fmt.Println("DEBUG - a single MultiValue") |
| 286 | + margs = args[0].(gal.MultiValue) // not checking type satisfaction for simplicity |
| 287 | + } |
| 288 | + if len(args) == 2 { |
| 289 | + fmt.Println("DEBUG - two Value's") |
| 290 | + margs = gal.NewMultiValue(args...) |
| 291 | + } |
| 292 | + if margs.Size() != 2 { |
| 293 | + return gal.NewUndefinedWithReasonf("sum() requires either two Numberer-type Value's or one MultiValue holdings 2 Numberer's, as arguments, but got %d arguments", margs.Size()) |
| 294 | + } |
| 295 | + |
| 296 | + value1 := args[0].(gal.MultiValue).Get(0).(gal.Numberer) |
| 297 | + value2 := args[0].(gal.MultiValue).Get(1).(gal.Numberer) |
| 298 | + |
| 299 | + return value1.Number().Add(value2.Number()) |
| 300 | + }, |
| 301 | + } |
| 302 | + |
| 303 | + got := parsedExpr.Eval( |
| 304 | + gal.WithFunctions(funcs), |
| 305 | + ) |
| 306 | + expected := gal.NewNumber(7) |
| 307 | + assert.Equal(t, expected.String(), got.String()) |
| 308 | +} |
| 309 | + |
| 310 | +func TestStringsWithSpaces(t *testing.T) { |
| 311 | + expr := `"ab cd" + "ef gh"` |
| 312 | + parsedExpr := gal.Parse(expr) |
| 313 | + |
| 314 | + got := parsedExpr.Eval() |
| 315 | + assert.Equal(t, "ab cdef gh", got.String()) |
| 316 | +} |
| 317 | + |
| 318 | +func TestFunctionsAndStringsWithSpaces(t *testing.T) { |
| 319 | + expr := `f("ab cd") + f("ef gh")` |
| 320 | + parsedExpr := gal.Parse(expr) |
| 321 | + |
| 322 | + got := parsedExpr.Eval( |
| 323 | + gal.WithFunctions(gal.Functions{ |
| 324 | + "f": func(args ...gal.Value) gal.Value { |
| 325 | + if len(args) != 1 { |
| 326 | + return gal.NewUndefinedWithReasonf("f() requires a single argument, got %d", len(args)) |
| 327 | + } |
| 328 | + return args[0] |
| 329 | + }, |
| 330 | + }), |
| 331 | + ) |
| 332 | + assert.Equal(t, "ab cdef gh", got.String()) |
194 | 333 | }
|
0 commit comments