Compare commits

...

50 Commits

Author SHA1 Message Date
camoroso c39970fa7e new operator 'in' added. It check if an item is member of a list, or if a key is contained in a dictionary 2024-05-18 07:47:41 +02:00
camoroso 14bb9e942b operator-fraction.go: added link to a tutorial about fractions 2024-05-18 07:07:17 +02:00
camoroso 9451958218 utils_test.go: new test file 2024-05-18 07:06:06 +02:00
camoroso 91fdc1926e Doc: updated last change date and time 2024-05-17 15:48:17 +02:00
camoroso 9a3abdf1b6 Doc: more details on the syntax of the selector and variable default operators 2024-05-17 15:46:56 +02:00
camoroso ac3e690f87 Doc: more details on the syntax of the dictionaries, variables and multi-expressions 2024-05-17 07:31:13 +02:00
camoroso f0a152a17a Doc: more details on the syntax of the numbers, strings and boolean 2024-05-16 07:11:20 +02:00
camoroso ca89931ca9 Doc: more details on the syntax of the numbers 2024-05-15 22:06:26 +02:00
camoroso d2e8aed4f7 parser_test.go: test on divisopn by-zero 2024-05-15 22:05:49 +02:00
camoroso 8138cd2a80 operand-func.go: commented the errors moved to common-errors..go 2024-05-15 22:04:38 +02:00
camoroso 6786666cf4 funcs_test.go: added some tests 2024-05-15 22:03:41 +02:00
camoroso 35e794701a func-string.go: commented out the param count check 2024-05-15 22:03:03 +02:00
camoroso 52ef134be6 func-base.go: commented out the param count check 2024-05-15 22:02:07 +02:00
camoroso 624318d84e common-errors.go: moved here some errors 2024-05-15 22:01:13 +02:00
camoroso aa1338cd51 Fixed special convertion case from decimal 'x.y()' to fraction 2024-05-15 06:45:40 +02:00
camoroso c9db4b84e3 funcs_test.go: added some tests 2024-05-14 05:41:53 +02:00
camoroso e7e9330b71 scanner: Fixed decimal number parser; it didn't save parenthesis around period part 2024-05-14 05:41:10 +02:00
camoroso 8eb25bbc86 operand-const.go -> operand-literal.go 2024-05-14 04:59:55 +02:00
camoroso efc92d434b the sum operation now supports dicts too 2024-05-14 04:56:24 +02:00
camoroso 4151f3f5e2 literals of rational number, e.g. 1.2(3), are now supported and are evaluated as fractions 2024-05-14 04:55:16 +02:00
camoroso f028485caa added functions to get the fenerating fraction of a decimal number 2024-05-13 14:24:37 +02:00
camoroso ab07405cda common-errors.go: added errDivisionByZero() 2024-05-13 14:23:21 +02:00
camoroso 47be0c66cf Doc: some typo and better examples 2024-05-11 20:14:54 +02:00
camoroso 50e7168214 Since now builtin functions are registared in a new global context. This reduces the effort to copy the whole set of builtin functions in the context of a function call; only the called function will be copied, if it is global. 2024-05-11 10:45:38 +02:00
camoroso 0a9543543d Remove the unused 'parent' param from the function newTerm(). 2024-05-11 06:41:06 +02:00
camoroso 775751c67b Doc: string examples 2024-05-11 06:35:32 +02:00
camoroso 4aaffd6c44 dict: implemented length of dict 2024-05-10 09:25:18 +02:00
camoroso 924051fbcd extended funcs and list tests 2024-05-10 09:18:32 +02:00
camoroso 5a9b6525a2 new function isRational() that return true is the passed value is integere or fraction 2024-05-10 09:17:51 +02:00
camoroso 8c66d90532 operator-length.go: fixed length of list 2024-05-10 09:16:31 +02:00
camoroso 4aa0113c6a new string test file 2024-05-10 07:33:59 +02:00
camoroso d035fa0d5e operator-dot.go: fixed string index 2024-05-10 07:32:45 +02:00
camoroso cf73b5c98d Doc: add link to dev-expr download page 2024-05-10 06:39:48 +02:00
camoroso 8346e28340 Doc: fixed some typo and added some list details 2024-05-10 04:49:03 +02:00
camoroso 9c2eca40d7 utils.go: added IsBool() and IsFract() 2024-05-10 04:48:13 +02:00
camoroso c3198e4c79 parser_test.go: did not show the correct section name 2024-05-10 04:47:34 +02:00
camoroso 99e0190b9c operator-dot.go: fixed the index range checking 2024-05-10 04:45:51 +02:00
camoroso d4f63a3837 funcs_test.go: fixed and extended 2024-05-10 04:44:08 +02:00
camoroso 389d48b646 func-base.go: added functions to check data-type of a value 2024-05-10 04:43:14 +02:00
camoroso 1f7b9131fc The 'last' variable now also receives the value of the final expression 2024-05-10 04:41:26 +02:00
camoroso dce49fd2b7 Doc: fractions and dictionaries 2024-05-09 07:20:22 +02:00
camoroso 8ee0bb5701 Some data-type check functions (e.g. IsInteger()) exported 2024-05-08 07:53:01 +02:00
camoroso b2b0bb04c5 formatter.go: number base options added 2024-05-08 07:51:38 +02:00
camoroso f3abf5e77c Doc: dev-expr description updated 2024-05-08 07:51:01 +02:00
camoroso 34b7799177 parser_test.go: test added (mixed number types) 2024-05-07 07:23:38 +02:00
camoroso 8c3c54913a Doc: dev-expr introduction 2024-05-07 07:22:32 +02:00
camoroso 5910345c08 operator-{sum,prod}.go: Fixed sum and prod operation with a fraction and a float 2024-05-06 17:32:39 +02:00
camoroso 8b4dad1381 utils.go: new function isNumOrFract(x); it returns true if x is a float, an integer, ora a fraction 2024-05-06 17:31:12 +02:00
camoroso 6ef468408c operator-sum.go: Fixed sum of fraction and float 2024-05-06 17:16:30 +02:00
camoroso 3a30d890c6 operator-assign.go: improvements that allow to define function aliases (TODO: maybe *funcDefFunctor else-if section can be removed 2024-05-06 16:01:50 +02:00
45 changed files with 3062 additions and 348 deletions
+2 -1
View File
@@ -60,7 +60,7 @@ func (self *ast) addToken(tk *Token) (err error) {
} }
func (self *ast) addToken2(tk *Token) (t *term, err error) { func (self *ast) addToken2(tk *Token) (t *term, err error) {
if t = newTerm(tk, nil); t != nil { if t = newTerm(tk); t != nil {
err = self.addTerm(t) err = self.addTerm(t)
} else { } else {
err = tk.Errorf("unexpected token %q", tk.String()) err = tk.Errorf("unexpected token %q", tk.String())
@@ -128,6 +128,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
} }
if err == nil { if err == nil {
result, err = self.root.compute(ctx) result, err = self.root.compute(ctx)
ctx.setVar(ControlLastResult, result)
} }
// } else { // } else {
// err = errors.New("empty expression") // err = errors.New("empty expression")
+21 -3
View File
@@ -8,6 +8,20 @@ import (
"fmt" "fmt"
) )
func errTooFewParams(minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 {
err = fmt.Errorf("too few params -- expected %d or more, got %d", minArgs, argCount)
} else {
err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount)
}
return
}
func errTooMuchParams(maxArgs, argCount int) (err error) {
err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount)
return
}
// --- General errors // --- General errors
func errCantConvert(funcName string, value any, kind string) error { func errCantConvert(funcName string, value any, kind string) error {
@@ -18,11 +32,15 @@ func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value) return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
} }
func errDivisionByZero(funcName string) error {
return fmt.Errorf("%s() division by zero", funcName)
}
// --- Parameter errors // --- Parameter errors
func errOneParam(funcName string) error { // func errOneParam(funcName string) error {
return fmt.Errorf("%s() requires exactly one param", funcName) // return fmt.Errorf("%s() requires exactly one param", funcName)
} // }
func errMissingRequiredParameter(funcName, paramName string) error { func errMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName) return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
+3 -14
View File
@@ -25,20 +25,9 @@ func TestDictParser(t *testing.T) {
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)}, /* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil}, /* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil}, /* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
// /* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil}, /* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
// /* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil}, /* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
// /* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
// /* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// /* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
// /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
// /* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
// /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
// /* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
// /* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 14 */ {`[1,2,3].1`, int64(2), nil},
// /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
// /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
} }
succeeded := 0 succeeded := 0
+409 -102
View File
@@ -22,49 +22,242 @@ Expressions calculator
toc::[] toc::[]
#TODO: Work in progress# #TODO: Work in progress (last update on 2024/05/17, 15:47 p.m.)#
== Expr == Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions. _Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology === Concepts and terminology
#TODO# #TODO#
image::expression-diagram.png[]
=== `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
Here are some examples of execution.
.Run `dev-expr` in REPL mode and ask for help
[source,shell]
----
# Type 'exit' or Ctrl+D to quit the program.
[user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help
--- REPL commands:
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
base -- Set the integer output base: 2, 8, 10, or 16
exit -- Exit the program
help -- Show command list
ml -- Enable/Disable multi-line output
mods -- List builtin modules
--- Command line options:
-b <builtin> Import builtin modules.
<builtin> can be a list of module names or a glob-pattern.
Use the special value 'all' or the pattern '*' to import all modules.
-e <expression> Evaluate <expression> instead of standard-input
-i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu
-m, --modules List all builtin modules
-p Print prefix form
-t Print tree form <2>
>>>
----
<1> Only available for single fraction values
<2> Work in progress
.REPL examples
[source,shell]
----
[user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> 2+3
5
>>> 2+3*(4-1.5)
9.5
>>> 0xFD + 0b1 + 0o1 <1>
255
>>> 1|2 + 2|3 <2>
7|6
>>> ml <3>
>>> 1|2 + 2|3
7
-
6
>>> 1+2 but 5|2+0.5 <4>
3
>>> 1+2; 5|2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
<2> Fractions: numerator | denominator.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
== Data types == Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists. _Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
=== Numbers === Numbers
Numbers can be integers (GO int64) or float (GO float64). In mixed operations involving integers and floats, integers are automatically promoted to floats. _Expr_ supports three type of numbers:
. [blue]#Integers#
. [blue]#Floats#
. [blue]#Factions#
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
==== Integers
__Expr__'s integers are a subset of the integer set. Internally they are stored as Golang _int64_ values.
.Integer literal syntax
====
*_integer_* = [_sign_] _digit-seq_ +
_sign_ = "**+**" | "**-**" +
_digit-seq_ = _dec-seq_ | _bin-seq_ | _oct-seq_ | _hex-seq_ +
_dec-seq_ = {__dec-digit__} +
_dec-digit_ = "**0**"|"**1**"|...|"**9**" +
_bin-seq_ = "**0b**"{__bin-digit__} +
_bin-digit_ = "**0**"|"**1**" +
_oct-seq_ = "**0o**"{__oct-digit__} +
_oct-digit_ = "**0**"|"**1**"|...|"**7**" +
_hex-seq_ = "**0x**"{__hex-digit__} +
_hex-digit_ = "**0**"|"**1**"|...|"**9**"|"**a**"|...|"**z**"|"**A**"|...|"**Z**"
====
Value range: *-9223372036854775808* to *9223372036854775807*
.Arithmetic operators .Arithmetic operators
[cols="^1,^2,6,4"] [cols="^1,^2,6,4"]
|=== |===
| Symbol | Operation | Description | Examples | Symbol | Operation | Description | Examples
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` -> 1
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ + | [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> 2
[blue]`-(+2)` _[-2]_ | [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` _[1]_ + | [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
[blue]`4 + 0.5` _[4.5]_
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` _[2]_ +
[blue]`4 - 0.5` _[3.5]_
| [blue]`*` | _product_ | Multiply two values | `-1 * 2` _[-2]_ +
[blue]`4 * 0.5` _[2.0]_
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`-1 / 2` _[0]_ +
[blue]`1.0 / 2` _[0.5]_
| [blue]`./` | _Float division_ | Force float division | [blue]`-1 ./ 2` _[-0.5]_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` _[1]_
|=== |===
=== String ^(*)^ See also the _float division_ [blue]`./` below.
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
==== Floats
__Expr__'s floats are a subset of the rational number set. Note that they can't hold the exact value of an unlimited number; floats can only approximate them. Internally floats are stored as Golang's _float64_ values.
.Float literal syntax
====
*_float_* = [_sign_] _dec-seq_ "**.**" [_dec-seq_] [("**e**"|"**E**") [_sign_] _dec-seq_] +
_sign_ = "**+**" | "**-**" +
_dec-seq_ = _see-integer-literal-syntax_
====
.Examples
`>>>` [blue]`1.0` +
[green]`1` +
`>>>` [blue]`0.123` +
[green]`0.123` +
`>>>` [blue]`4.5e+3` +
[green]`4500` +
`>>>` [blue]`4.5E-33` +
[green]`4.5e-33` +
`>>>` [blue]`4.5E-3` +
[green]`0.0045` +
`>>>` [blue]`4.5E10` +
[green]`4.5e+10`
.Arithmetic operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _sum_ | Add two values | [blue]`4 + 0.5` -> 4.5
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`4 - 0.5` -> 3.5
| [blue]`*` | _product_ | Multiply two values | [blue]`4 * 0.5` -> 2.0
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
| [blue]`./`| _Float division_ | Force float division | [blue]`-1 ./ 2` -> -0.5
|===
==== Fractions
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
.Fraction literal syntax
====
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
_sign_ = "**+**" | "**-**" +
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
_dec-seq_ = _see-integer-literal-syntax_ +
_digit-seq_ = _see-integer-literal-syntax_
====
.Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` +
[green]`1|2` +
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
[green]`2|3` +
`>>>` [blue]`1|2 + 2|3` +
[green]`7|6` +
`>>>` [blue]`1|2 * 2|3` +
[green]`1|3` +
`>>>` [blue]`1|2 / 1|3` +
[green]`3|2` +
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
[green]`1.5` +
`>>>` [blue]`-1|2` +
[green]`-1|2` +
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
`>>>` [blue]`1|(-2)` +
[green]`-1|2`
// ----
Fractions can be used together with integers and floats in expressions.
.Examples
`>>>` [blue]`1|2 + 5` +
[green]`11|2` +
`>>>` [blue]`4 - 1|2` +
[green]`7|2` +
`>>>` [blue]`1.0 + 1|2` +
[green]`1.5`
=== Strings
Strings are character sequences enclosed between two double quote [blue]`"`.
.Examples
`>>>` [blue]`"I'm a string"` +
[green]`I'm a string` +
`>>>` [blue]`"123abc?!"` +
[green]`123abc?!` +
`>>>` [blue]`"123\nabc"` +
[green]`123` +
[green]`abc` +
`>>>` [blue]`"123\tabc"` +
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
Some arithmetic operators can also be used with strings. Some arithmetic operators can also be used with strings.
@@ -79,28 +272,46 @@ Some arithmetic operators can also be used with strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_ | [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|=== |===
The items of strings can be accessed using the dot `.` operator.
=== Boolean .Item access syntax
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values. ====
_item_ = _string-expr_ "**.**" _integer-expr_
====
.String examples
`>>>` [blue]`s="abc"` [gray]_assign the string to variable s_ +
[green]`abc` +
`>>>` [blue]`s.1` [gray]_char at position 1 (starting from 0)_ +
[green]`b` +
`>>>` [blue]`s.(-1)` [gray]_char at position -1, the rightmost one_ +
[green]`c` +
`>>>` [blue]`\#s` [gray]_number of chars_ +
[gren]`3` +
`>>>` [blue]`#"abc"` [gray]_number of chars_ +
[green]`3`
=== Booleans
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
.Relational operators .Relational operators
[cols="^1,^2,6,4"] [cols="^1,^2,6,4"]
|=== |===
| Symbol | Operation | Description | Examples | Symbol | Operation | Description | Examples
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ + | [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` -> _false_ +
[blue]`"a" == "a"` _[true]_ [blue]`"a" == "a"` -> _true_
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` _[true]_ + | [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` -> _true_ +
[blue]`"a" != "a"` _[false]_ [blue]`"a" != "a"` -> _false_
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ + | [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` -> _false_ +
[blue]`"a" < "b"` _[true]_ [blue]`"a" < "b"` -> _true_
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` _[false]_ + | [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` -> _false_ +
[blue]`"b" \<= "b"` _[true]_ [blue]`"b" \<= "b"` -> _true_
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ + | [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` -> _true_ +
[blue]`"a" < "b"` _[false]_ [blue]`"a" < "b"` -> _false_
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` _[true]_ + | [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` -> _true_ +
[blue]`"b" \<= "b"` _[true]_ [blue]`"b" \<= "b"` -> _true_
|=== |===
@@ -109,19 +320,19 @@ Boolean data type has two values only: _true_ and _false_. Relational and Boolea
|=== |===
| Symbol | Operation | Description | Examples | Symbol | Operation | Description | Examples
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ + | [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` -> _false_ +
[blue]`NOT (2 < 1)` _[true]_ [blue]`NOT (2 < 1)` -> _true_
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` _[false]_ + | [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` -> _false_ +
[blue]`"a" < "b" AND NOT (2 < 1)` _[true]_ [blue]`"a" < "b" AND NOT (2 < 1)` -> _true_
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` _[true]_ + | [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` -> _true_ +
[blue]`"a" == "b" OR (2 == 1)` _[false]_ [blue]`"a" == "b" OR (2 == 1)` -> _false_
|=== |===
[CAUTION] [CAUTION]
==== ====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of operators [blue]`and` and [blue]`or` is sufficient to establish the result of the whole operation, the right expression would not evaluated at all. Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
.Example .Example
[source,go] [source,go]
@@ -131,58 +342,132 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_. <1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
==== ====
=== List === Lists
_Expr_ supports list of mixed-type values, also specified by normal expressions. _Expr_ supports list of mixed-type values, also specified by normal expressions. Internally, _Expr_'s lists are Go arrays.
.List examples .List literal syntax
[source,go] ====
---- *_list_* = _empty-list_ | _non-empty-list_ +
[1, 2, 3] // List of integers _empty-list_ = "**[]**" +
["one", "two", "three"] // List of strings _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
["one", 2, false, 4.1] // List of mixed-types ====
["one"+1, 2.0*(9-2)] // List of expressions
[ [1,"one"], [2,"two"]] // List of lists
----
.Examples
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
[green]`[1, 2, 3]` +
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
[green]`["one", "two", "three"]` +
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
[green]`["one", 2, false, 4.1]` +
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
[green]`["one1", 14]` +
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
[green]`[[1, "one"], [2, "two"]]`
.List operators .List operators
[cols="^2,^2,5,4"] [cols="^2,^2,5,4"]
|=== |===
| Symbol | Operation | Description | Examples | Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` _[ [1,2,3] ]_ | [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|=== |===
== Variables The items of array can be accessed using the dot `.` operator.
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
.Item access syntax
====
_item_ = _list-expr_ "**.**" _list-expr_
====
.Items of list
`>>>` [blue]`[1,2,3].1` +
[green]`2` +
`>>>` [blue]`list=[1,2,3]; list.1` +
[green]`2` +
`>>>` [blue]`["one","two","three"].1` +
[green]`two` +
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
[green]`two` +
`>>>` [blue]`list.(-1)` +
[green]`three` +
`>>>` [blue]`list.(10)` +
[red]`Eval Error: [1:9] index 10 out of bounds` +
`>>>` [blue]`#list` +
[green]`3`
=== Dictionaries
WARNING: Support for dictionaries is still ongoing.
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. Dictionary literals are sequences of pairs separated by comma `,`; sequences are enclosed between brace brackets.
.Dict literal syntax
====
*_dict_* = _empty-dict_ | _non-empty-dict_ +
_empty-dict_ = "**{}**" +
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
====
.Examples .Examples
[source,go] `>>>` [blue]`{1:"one", 2:"two"}` +
---- `>>>` [blue]`{"one":1, "two": 2}` +
a=1 `>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}`
x = 5.2 * (9-3)
x = 1; y = 2*x == Variables
---- _Expr_ supports variables like most programming languages. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
.Variable literal syntax
====
*_variable_* = _identifier_ "*=*" _any-value_ +
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
====
NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
.Examples
`>>>` [blue]`a=1` +
[green]`1` +
`>>>` [blue]`a_b=1+2` +
[green]`1+2` +
`>>>` [blue]`a_b` +
[green]`3` +
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
[green]`31.200000000000003` +
`>>>` [blue]`x = 1; y = 2*x` +
[green]`2` +
`>>>` [blue]`_a=2` +
[red]`Parse Error: [1:2] unexpected token "_"` +
`>>>` [blue]`1=2` +
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
== Other operations == Other operations
=== [blue]`;` operator === [blue]`;` operator
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result. The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions. IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
TIP: [blue]`;` can be used to set some variables before the final calculation. TIP: [blue]`;` can be used to set some variables before the final calculation.
.Example .Example
[source,go] `>>>` [blue]`a=1; b=2; c=3; a+b+c` +
---- [green]`6`
a=1; b=2; c=3; a+b+c // returns 6
---- The value of each sub-expression is stored in the automatica variable _last_.
.Example
`>>>` [blue]`2+3; b=last+10; last` +
[green]`15`
=== [blue]`but` operator === [blue]`but` operator
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5. [blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`. [blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
@@ -191,43 +476,65 @@ The assignment operator [blue]`=` is used to define variables in the evaluation
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation. The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
.Example .Example
[source,go] `>>>` [blue]`a=15+1`
---- [green]`16`
a=15+1 // returns 16
----
=== Selector operator [blue]`? : ::` === Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages. The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Syntax .Selector literal Syntax
[source,bnf] _selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
---- _selector-case_ = [_match-list_] _case-value_ +
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>] _match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
<selector-case> ::= [<match-list>] <case-value> _item_ = _expression_ +
<match-list> ::= "["<item>{","<items>}"]" _case-multi-expression_ = "*{*" _multi-expression_ "*}*" +
<item> ::= <expression _multi-expression_ = _expression_ { "*;*" _expression_ } +
<case-multi-expression> ::= "{" <multi-expression> "}" _default-multi-expression_ = _multi-expression_
<multi-expression> ::= <expression> {";" <expression>}
----
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision find a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation. In other words, the selector operator evaluates the _select-expression_ on the left-hand side of the [blue]`?` symbol; it then compares the result obtained with the values listed in the __match-list__'s, from left to right. If the comparision finds a match with a value in a _match-list_, the associated _case-multi-expression_ is evaluted, and its result will be the final result of the selection operation.
The match lists are optional. In that case, the position, from left to right, of the `<selector-case>` is used as match-list. Of course, that only works if the select-expression results in an integer. The match lists are optional. In that case, the position, from left to right, of the _selector-case_ is used as _match-list_. Of course, that only works if the _select-expression_ results in an integer.
The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.
The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that if the value of the _select-expression_ does not match any _match-list_, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the [blue]`::` symbol (double-colon). Also note that the default expression has no _match-list_.
.Examples .Examples
[source,go] `>>>` [blue]`1 ? {"a"} : {"b"}` +
---- [green]`b` +
1 ? {"a"} : {"b"} // returns "b" `>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
10 ? {"a"} : {"b"} :: {"c"} // returns "c" [green]`c' +
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b" [green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause" [green]`b` +
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b" `>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b" [red]`Parse Error: [1:34] case list in default clause` +
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression [green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
---- [green]`b` +
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} : {"b"}` +
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
=== Variable default value [blue]`??` and [blue]`?=`
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evuated at all.
The [blue]`??` do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
.Examples
`>>>` [blue]`var ?? (1+2)`'
[green]`3` +
`>>>` [blue]`var` +
[red]`Eval Error: undefined variable or function "var"` +
`>>>` [blue]`var ?= (1+2)` +
[green]`3` +
`>>>` [blue]`var` +
[green]`3`
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
== Priorities of operators == Priorities of operators
The table below shows all supported operators by decreasing priorities. The table below shows all supported operators by decreasing priorities.
+1774
View File
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+4
View File
@@ -9,6 +9,10 @@ type FmtOpt uint16
const ( const (
TTY FmtOpt = 1 << iota TTY FmtOpt = 1 << iota
MultiLine MultiLine
Base2
Base8
Base10
Base16
) )
type Formatter interface { type Formatter interface {
+123 -7
View File
@@ -10,16 +10,51 @@ import (
) )
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
result = args[0] == nil result = args[0] == nil
} else { return
err = errOneParam(name)
} }
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsInteger(args[0])
return
}
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsFloat(args[0])
return
}
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsBool(args[0])
return
}
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsString(args[0])
return
}
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsFract(args[0])
return
}
func isRationalFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsRational(args[0])
return
}
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsList(args[0])
return
}
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsDict(args[0])
return return
} }
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) { func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
switch v := args[0].(type) { switch v := args[0].(type) {
case int64: case int64:
result = v result = v
@@ -39,8 +74,77 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
default: default:
err = errCantConvert(name, v, "int") err = errCantConvert(name, v, "int")
} }
return
}
func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
result = float64(v)
case float64:
result = v
case bool:
if v {
result = float64(1)
} else { } else {
err = errOneParam(name) result = float64(0)
}
case string:
var f float64
if f, err = strconv.ParseFloat(v, 64); err == nil {
result = f
}
case *fraction:
result = v.toFloat()
default:
err = errCantConvert(name, v, "float")
}
return
}
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
var den int64 = 1
if len(args) > 1 {
var ok bool
if den, ok = args[1].(int64); !ok {
err = errExpectedGot(name, "integer", args[1])
} else if den == 0 {
err = errDivisionByZero(name)
}
}
if err == nil {
result = newFraction(v, den)
}
case float64:
result, err = float64ToFraction(v)
// var n, d int64
// if n, d, err = float64ToFraction(v); err == nil {
// result = newFraction(n, d)
// }
case bool:
if v {
result = newFraction(1, 1)
} else {
result = newFraction(0, 1)
}
case string:
result, err = makeGeneratingFraction(v)
// var f float64
// // TODO temporary implementation
// if f, err = strconv.ParseFloat(v, 64); err == nil {
// var n, d int64
// if n, d, err = float64ToFraction(f); err == nil {
// result = newFraction(n, d)
// }
// } else {
// errors.New("convertion from string to float is ongoing")
// }
case *fraction:
result = v
default:
err = errCantConvert(name, v, "float")
} }
return return
} }
@@ -50,8 +154,20 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
} }
func ImportBuiltinsFuncs(ctx ExprContext) { func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1) ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1) ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
} }
func init() { func init() {
+1 -1
View File
@@ -35,7 +35,7 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
} }
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) { func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isString(paramValue) /*|| isList(paramValue)*/) { if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue) err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
} }
return return
+3 -3
View File
@@ -10,7 +10,7 @@ import (
) )
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) { func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
if !(isNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ { if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected", err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
funcName, paramPos+1, subPos+1, level, paramValue) funcName, paramPos+1, subPos+1, level, paramValue)
} }
@@ -45,7 +45,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
count++ count++
if !sumAsFloat { if !sumAsFloat {
if isFloat(v) { if IsFloat(v) {
sumAsFloat = true sumAsFloat = true
if sumAsFract { if sumAsFract {
floatSum = fractSum.toFloat() floatSum = fractSum.toFloat()
@@ -120,7 +120,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
count++ count++
if !mulAsFloat { if !mulAsFloat {
if isFloat(v) { if IsFloat(v) {
mulAsFloat = true mulAsFloat = true
if mulAsFract { if mulAsFract {
floatProd = fractProd.toFloat() floatProd = fractProd.toFloat()
+18 -18
View File
@@ -33,9 +33,9 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
} }
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) < 1 { // if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSeparator) // return nil, errMissingRequiredParameter(name, paramSeparator)
} // }
if sep, ok := args[0].(string); ok { if sep, ok := args[0].(string); ok {
if len(args) == 1 { if len(args) == 1 {
result = "" result = ""
@@ -62,9 +62,9 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var source string var source string
var ok bool var ok bool
if len(args) < 1 { // if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource) // return nil, errMissingRequiredParameter(name, paramSource)
} // }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) return nil, errWrongParamType(name, paramSource, typeString, args[0])
} }
@@ -93,9 +93,9 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
var source string var source string
var ok bool var ok bool
if len(args) < 1 { // if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource) // return nil, errMissingRequiredParameter(name, paramSource)
} // }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) return nil, errWrongParamType(name, paramSource, typeString, args[0])
} }
@@ -108,9 +108,9 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
var ok bool var ok bool
result = false result = false
if len(args) < 1 { // if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource) // return result, errMissingRequiredParameter(name, paramSource)
} // }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, errWrongParamType(name, paramSource, typeString, args[0])
} }
@@ -133,9 +133,9 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
var ok bool var ok bool
result = false result = false
if len(args) < 1 { // if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource) // return result, errMissingRequiredParameter(name, paramSource)
} // }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, errWrongParamType(name, paramSource, typeString, args[0])
} }
@@ -159,9 +159,9 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
var parts []string var parts []string
var ok bool var ok bool
if len(args) < 1 { // if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource) // return result, errMissingRequiredParameter(name, paramSource)
} // }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, errWrongParamType(name, paramSource, typeString, args[0])
} }
+27 -4
View File
@@ -20,7 +20,7 @@ func TestFuncs(t *testing.T) {
/* 7 */ {`int(3.9)`, int64(3), nil}, /* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil}, /* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)}, /* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)}, /* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)}, /* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
/* 12 */ {`two=func(){2}; two()`, int64(2), nil}, /* 12 */ {`two=func(){2}; two()`, int64(2), nil},
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil}, /* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
@@ -55,12 +55,35 @@ func TestFuncs(t *testing.T) {
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil}, /* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)}, /* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil}, /* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
/* 45 */ {`["a", "b", "c"]`, newListA("a","b","c"), nil}, /* 45 */ {`isInt(2+1)`, true, nil},
/* 46 */ {`["a", "b", "c"]`, newList([]any{"a","b","c"}), nil}, /* 46 */ {`isInt(3.1)`, false, nil},
/* 47 */ {`isFloat(3.1)`, true, nil},
/* 48 */ {`isString("3.1")`, true, nil},
/* 49 */ {`isString("3" + 1)`, true, nil},
/* 50 */ {`isList(["3", 1])`, true, nil},
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 52 */ {`isFract(1|3)`, true, nil},
/* 53 */ {`isFract(3|1)`, false, nil},
/* 54 */ {`isRational(3|1)`, true, nil},
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
/* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
/* 59 */ {`fract(1.21)`, newFraction(121, 100), nil},
/* 60 */ {`dec(2)`, float64(2), nil},
/* 61 */ {`dec(2.0)`, float64(2), nil},
/* 62 */ {`dec("2.0")`, float64(2), nil},
/* 63 */ {`dec(true)`, float64(1), nil},
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
// /* 64 */ {`string(true)`, "true", nil},
} }
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
// parserTest(t, "Func", inputs[25:26]) //parserTest(t, "Func", inputs[54:55])
parserTest(t, "Func", inputs) parserTest(t, "Func", inputs)
} }
+54
View File
@@ -0,0 +1,54 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// global-context.go
package expr
import "path/filepath"
var globalCtx *SimpleFuncStore
func ImportInContext(name string) (exists bool) {
var mod *module
if mod, exists = moduleRegister[name]; exists {
mod.importFunc(globalCtx)
mod.imported = true
}
return
}
func ImportInContextByGlobPattern(pattern string) (count int, err error) {
var matched bool
for name, mod := range moduleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
mod.importFunc(globalCtx)
mod.imported = true
}
} else {
break
}
}
return
}
func GetVar(ctx ExprContext, name string) (value any, exists bool) {
if value, exists = ctx.GetVar(name); !exists {
value, exists = globalCtx.GetVar(name)
}
return
}
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
if item, exists = ctx.GetFuncInfo(name); exists {
ownerCtx = ctx
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
ownerCtx = globalCtx
}
return
}
func init() {
globalCtx = NewSimpleFuncStore()
}
+7 -2
View File
@@ -36,6 +36,11 @@ func TestListParser(t *testing.T) {
/* 14 */ {`[1,2,3].1`, int64(2), nil}, /* 14 */ {`[1,2,3].1`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil}, /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil}, /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil}, // /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil}, // /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
@@ -54,8 +59,8 @@ func TestListParser(t *testing.T) {
var gotErr error var gotErr error
ctx := NewSimpleFuncStore() ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123)) // ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc") // ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx) ImportMathFuncs(ctx)
parser := NewParser(ctx) parser := NewParser(ctx)
+23 -24
View File
@@ -6,7 +6,6 @@ package expr
import ( import (
"fmt" "fmt"
"path/filepath"
) )
type module struct { type module struct {
@@ -31,30 +30,30 @@ func registerImport(name string, importFunc func(ExprContext), description strin
moduleRegister[name] = newModule(importFunc, description) moduleRegister[name] = newModule(importFunc, description)
} }
func ImportInContext(ctx ExprContext, name string) (exists bool) { // func ImportInContext(ctx ExprContext, name string) (exists bool) {
var mod *module // var mod *module
if mod, exists = moduleRegister[name]; exists { // if mod, exists = moduleRegister[name]; exists {
mod.importFunc(ctx) // mod.importFunc(ctx)
mod.imported = true // mod.imported = true
} // }
return // return
} // }
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) { // func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool // var matched bool
for name, mod := range moduleRegister { // for name, mod := range moduleRegister {
if matched, err = filepath.Match(pattern, name); err == nil { // if matched, err = filepath.Match(pattern, name); err == nil {
if matched { // if matched {
count++ // count++
mod.importFunc(ctx) // mod.importFunc(ctx)
mod.imported = true // mod.imported = true
} // }
} else { // } else {
break // break
} // }
} // }
return // return
} // }
func IterateModules(op func(name, description string, imported bool) bool) { func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil { if op != nil {
-32
View File
@@ -1,32 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-const.go
package expr
// -------- const term
func newConstTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- eval func
func evalConst(ctx ExprContext, self *term) (v any, err error) {
v = self.tk.Value
return
}
// init
func init() {
registerTermConstructor(SymString, newConstTerm)
registerTermConstructor(SymInteger, newConstTerm)
registerTermConstructor(SymFloat, newConstTerm)
registerTermConstructor(SymBool, newConstTerm)
registerTermConstructor(SymKwNil, newConstTerm)
}
+6 -7
View File
@@ -23,16 +23,15 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) { func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
if info, exists := ctx.GetFuncInfo(name); exists { if info, exists, owner := GetFuncInfo(ctx, name); exists {
if info.MinArgs() > len(params) { if info.MinArgs() > len(params) {
if info.MaxArgs() < 0 { err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params))
err = fmt.Errorf("too few params -- expected %d or more, got %d", info.MinArgs(), len(params))
} else {
err = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
} }
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
err = errTooMuchParams(info.MaxArgs(), len(params))
} }
if info.MaxArgs() >= 0 && info.MaxArgs() < len(params) { if err == nil && owner != ctx {
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params)) ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
} }
} else { } else {
err = fmt.Errorf("unknown function %s()", name) err = fmt.Errorf("unknown function %s()", name)
+11
View File
@@ -6,6 +6,7 @@ package expr
import ( import (
"fmt" "fmt"
"reflect"
"strings" "strings"
) )
@@ -61,6 +62,16 @@ func newList(listAny []any) (list *ListType) {
return return
} }
func (list *ListType) indexDeepCmp(target any) (index int) {
index = -1
for i, item := range *list {
if reflect.DeepEqual(item, target) {
index = i
break
}
}
return
}
// -------- list term // -------- list term
func newListTermA(args ...*term) *term { func newListTermA(args ...*term) *term {
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-literal.go
package expr
// -------- literal term
func newLiteralTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalLiteral,
}
}
// -------- eval func
func evalLiteral(ctx ExprContext, self *term) (v any, err error) {
v = self.tk.Value
return
}
// init
func init() {
registerTermConstructor(SymString, newLiteralTerm)
registerTermConstructor(SymInteger, newLiteralTerm)
registerTermConstructor(SymFloat, newLiteralTerm)
registerTermConstructor(SymFraction, newLiteralTerm)
registerTermConstructor(SymBool, newLiteralTerm)
registerTermConstructor(SymKwNil, newLiteralTerm)
}
+2 -2
View File
@@ -24,8 +24,8 @@ func newVarTerm(tk *Token) *term {
func evalVar(ctx ExprContext, self *term) (v any, err error) { func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool var exists bool
name := self.source() name := self.source()
if v, exists = ctx.GetVar(name); !exists { if v, exists = GetVar(ctx, name); !exists {
if info, exists := ctx.GetFuncInfo(name); exists { if info, exists, _ := GetFuncInfo(ctx, name); exists {
v = info.Functor() v = info.Functor()
} else { } else {
err = fmt.Errorf("undefined variable or function %q", name) err = fmt.Errorf("undefined variable or function %q", name)
+11 -5
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserightChilded.
// operator-assign.go // operator-assign.go
package expr package expr
@@ -27,11 +27,17 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if v, err = self.children[1].compute(ctx); err == nil { rightChild := self.children[1]
if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, 0
if funcDef, ok := functor.(*funcDefFunctor); ok { if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, -1
funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists {
minArgs = info.MinArgs()
maxArgs = info.MaxArgs()
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
l := len(funcDef.params) l := len(funcDef.params)
minArgs = l minArgs = l
maxArgs = l maxArgs = l
+3 -3
View File
@@ -26,15 +26,15 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
} }
count := 0 count := 0
if isString(childValue) { if IsString(childValue) {
module, _ := childValue.(string) module, _ := childValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module) count, err = ImportInContextByGlobPattern(module)
} else { } else {
var moduleSpec any var moduleSpec any
it := NewAnyIterator(childValue) it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() { for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok { if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) { if ImportInContext(module) {
count++ count++
} else { } else {
err = self.Errorf("unknown module %q", module) err = self.Errorf("unknown module %q", module)
+4 -3
View File
@@ -22,10 +22,11 @@ func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err
var indexValue any var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil { if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil { if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue { if v >= 0 && v < maxValue {
index = v index = v
} else if index >= -maxValue {
index = maxValue + v
} else { } else {
err = indexTerm.Errorf("index %d out of bounds", v) err = indexTerm.Errorf("index %d out of bounds", v)
} }
@@ -56,7 +57,7 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
case string: case string:
var index int var index int
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil { if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
v = unboxedValue[index] v = string(unboxedValue[index])
} }
case map[any]any: case map[any]any:
var ok bool var ok bool
+1 -1
View File
@@ -25,7 +25,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isInteger(leftValue) { if IsInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 { if i, _ := leftValue.(int64); i >= 0 {
f := int64(1) f := int64(1)
for k := int64(1); k <= i; k++ { for k := int64(1); k <= i; k++ {
+134
View File
@@ -4,9 +4,12 @@
// operand-fraction.go // operand-fraction.go
package expr package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"strconv" "strconv"
"strings" "strings"
) )
@@ -24,6 +27,137 @@ func newFraction(num, den int64) *fraction {
return &fraction{num, den} return &fraction{num, den}
} }
func float64ToFraction(f float64) (fract *fraction, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
// fmt.Printf("S: '%s'\n",s)
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
/*
func _float64ToFraction(f float64) (num, den int64, err error) {
const expMask = 1<<11 - 1
bits := math.Float64bits(f)
mantissa := bits & (1<<52 - 1)
exp := int((bits >> 52) & expMask)
switch exp {
case expMask: // non-finite
err = errors.New("infite")
return
case 0: // denormal
exp -= 1022
default: // normal
mantissa |= 1 << 52
exp -= 1023
}
shift := 52 - exp
// Optimization (?): partially pre-normalise.
for mantissa&1 == 0 && shift > 0 {
mantissa >>= 1
shift--
}
if f < 0 {
num = -int64(mantissa)
} else {
num = int64(mantissa)
}
den = int64(1)
if shift > 0 {
den = den << shift
} else {
num = num << (-shift)
}
return
}
*/
func makeGeneratingFraction(s string) (f *fraction, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2]
}
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *fraction) toFloat() float64 { func (f *fraction) toFloat() float64 {
return float64(f.num) / float64(f.den) return float64(f.num) / float64(f.den)
} }
+46
View File
@@ -0,0 +1,46 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-in.go
package expr
//-------- in term
func newInTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalIn,
}
}
func hasKey(d map[any]any, target any) (ok bool) {
_, ok = d[target]
return
}
func evalIn(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if IsList(rightValue) {
list, _ := rightValue.(*ListType)
v = list.indexDeepCmp(leftValue) >= 0
} else if IsDict(rightValue) {
d, _ := rightValue.(map[any]any)
v = hasKey(d, leftValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymKwIn, newInTerm)
}
+2 -2
View File
@@ -24,7 +24,7 @@ func evalInclude(ctx ExprContext, self *term) (v any, err error) {
} }
count := 0 count := 0
if isList(childValue) { if IsList(childValue) {
list, _ := childValue.([]any) list, _ := childValue.([]any)
for i, filePathSpec := range list { for i, filePathSpec := range list {
if filePath, ok := filePathSpec.(string); ok { if filePath, ok := filePathSpec.(string); ok {
@@ -39,7 +39,7 @@ func evalInclude(ctx ExprContext, self *term) (v any, err error) {
break break
} }
} }
} else if isString(childValue) { } else if IsString(childValue) {
filePath, _ := childValue.(string) filePath, _ := childValue.(string)
v, err = EvalFile(ctx, filePath) v, err = EvalFile(ctx, filePath)
} else { } else {
+2 -2
View File
@@ -33,7 +33,7 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isList(rightValue) { if IsList(rightValue) {
list, _ := rightValue.(*ListType) list, _ := rightValue.(*ListType)
newList := append(ListType{leftValue}, *list...) newList := append(ListType{leftValue}, *list...)
v = &newList v = &newList
@@ -50,7 +50,7 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isList(leftValue) { if IsList(leftValue) {
list, _ := leftValue.(*ListType) list, _ := leftValue.(*ListType)
newList := append(*list, rightValue) newList := append(*list, rightValue)
v = &newList v = &newList
+7 -4
View File
@@ -23,12 +23,15 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isList(childValue) { if IsList(childValue) {
list, _ := childValue.([]any) ls, _ := childValue.(*ListType)
v = int64(len(list)) v = int64(len(*ls))
} else if isString(childValue) { } else if IsString(childValue) {
s, _ := childValue.(string) s, _ := childValue.(string)
v = int64(len(s)) v = int64(len(s))
} else if IsDict(childValue) {
m, _ := childValue.(map[any]any)
v = int64(len(m))
} else if it, ok := childValue.(Iterator); ok { } else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) { if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil) count, _ := extIt.CallOperation(countName, nil)
+1 -1
View File
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
if it, ok := childValue.(Iterator); ok { if it, ok := childValue.(Iterator); ok {
v, err = it.Next() v, err = it.Next()
} else if isInteger(childValue) && self.children[0].symbol() == SymIdentifier { } else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
v = childValue v = childValue
i, _ := childValue.(int64) i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1) ctx.SetVar(self.children[0].source(), i+1)
+11 -11
View File
@@ -30,20 +30,20 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isString(leftValue) && isInteger(rightValue) { if IsString(leftValue) && IsInteger(rightValue) {
s, _ := leftValue.(string) s, _ := leftValue.(string)
n, _ := rightValue.(int64) n, _ := rightValue.(int64)
v = strings.Repeat(s, int(n)) v = strings.Repeat(s, int(n))
} else if isNumber(leftValue) && isNumber(rightValue) { } else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) { if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) * numAsFloat(rightValue) v = numAsFloat(leftValue) * numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
v = leftInt * rightInt v = leftInt * rightInt
} }
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -71,14 +71,16 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumber(leftValue) && isNumber(rightValue) { if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) { if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue) d := numAsFloat(rightValue)
if d == 0.0 { if d == 0.0 {
err = errors.New("division by zero") err = errors.New("division by zero")
} else { } else {
v = numAsFloat(leftValue) / d v = numAsFloat(leftValue) / d
} }
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 { if rightInt, _ := rightValue.(int64); rightInt == 0 {
@@ -87,8 +89,6 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
v = leftInt / rightInt v = leftInt / rightInt
} }
} }
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -114,7 +114,7 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumber(leftValue) && isNumber(rightValue) { if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
d := numAsFloat(rightValue) d := numAsFloat(rightValue)
if d == 0.0 { if d == 0.0 {
err = errors.New("division by zero") err = errors.New("division by zero")
@@ -148,7 +148,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isInteger(leftValue) && isInteger(rightValue) { if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
if rightInt == 0 { if rightInt == 0 {
err = errors.New("division by zero") err = errors.New("division by zero")
+9 -9
View File
@@ -25,15 +25,15 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumber(leftValue) && isNumber(rightValue) { if IsNumber(leftValue) && IsNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) { if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64) li, _ := leftValue.(int64)
ri, _ := rightValue.(int64) ri, _ := rightValue.(int64)
v = li == ri v = li == ri
} else { } else {
v = numAsFloat(leftValue) == numAsFloat(rightValue) v = numAsFloat(leftValue) == numAsFloat(rightValue)
} }
} else if isString(leftValue) && isString(rightValue) { } else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string) ls, _ := leftValue.(string)
rs, _ := rightValue.(string) rs, _ := rightValue.(string)
v = ls == rs v = ls == rs
@@ -111,15 +111,15 @@ func evalLess(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumber(leftValue) && isNumber(rightValue) { if IsNumber(leftValue) && IsNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) { if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64) li, _ := leftValue.(int64)
ri, _ := rightValue.(int64) ri, _ := rightValue.(int64)
v = li < ri v = li < ri
} else { } else {
v = numAsFloat(leftValue) < numAsFloat(rightValue) v = numAsFloat(leftValue) < numAsFloat(rightValue)
} }
} else if isString(leftValue) && isString(rightValue) { } else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string) ls, _ := leftValue.(string)
rs, _ := rightValue.(string) rs, _ := rightValue.(string)
v = ls < rs v = ls < rs
@@ -148,15 +148,15 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumber(leftValue) && isNumber(rightValue) { if IsNumber(leftValue) && IsNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) { if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64) li, _ := leftValue.(int64)
ri, _ := rightValue.(int64) ri, _ := rightValue.(int64)
v = li <= ri v = li <= ri
} else { } else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue) v = numAsFloat(leftValue) <= numAsFloat(rightValue)
} }
} else if isString(leftValue) && isString(rightValue) { } else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string) ls, _ := leftValue.(string)
rs, _ := rightValue.(string) rs, _ := rightValue.(string)
v = ls <= rs v = ls <= rs
+2 -2
View File
@@ -35,14 +35,14 @@ func evalSign(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isFloat(rightValue) { if IsFloat(rightValue) {
if self.tk.Sym == SymChangeSign { if self.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64) f, _ := rightValue.(float64)
v = -f v = -f
} else { } else {
v = rightValue v = rightValue
} }
} else if isInteger(rightValue) { } else if IsInteger(rightValue) {
if self.tk.Sym == SymChangeSign { if self.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64) i, _ := rightValue.(int64)
v = -i v = -i
+21 -9
View File
@@ -28,17 +28,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) { if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue) v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if isNumber(leftValue) && isNumber(rightValue) { } else if IsNumber(leftValue) && IsNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) { if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue) v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
v = leftInt + rightInt v = leftInt + rightInt
} }
} else if isList(leftValue) || isList(rightValue) { } else if IsList(leftValue) || IsList(rightValue) {
var leftList, rightList *ListType var leftList, rightList *ListType
var ok bool var ok bool
if leftList, ok = leftValue.(*ListType); !ok { if leftList, ok = leftValue.(*ListType); !ok {
@@ -56,7 +56,19 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
} }
v = &sumList v = &sumList
} else if isFraction(leftValue) || isFraction(rightValue) { } else if isFraction(leftValue) || isFraction(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
v, err = sumAnyFract(leftValue, rightValue) v, err = sumAnyFract(leftValue, rightValue)
}
} else if IsDict(leftValue) && IsDict(rightValue) {
leftDict, _ := leftValue.(map[any]any)
rightDict, _ := rightValue.(map[any]any)
c := CloneMap(leftDict)
for key, value := range rightDict {
c[key] = value
}
v = c
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -82,15 +94,17 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumber(leftValue) && isNumber(rightValue) { if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) { if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue) v = numAsFloat(leftValue) - numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
v = leftInt - rightInt v = leftInt - rightInt
} }
} else if isList(leftValue) && isList(rightValue) { } else if IsList(leftValue) && IsList(rightValue) {
leftList, _ := leftValue.(*ListType) leftList, _ := leftValue.(*ListType)
rightList, _ := rightValue.(*ListType) rightList, _ := rightValue.(*ListType)
diffList := make(ListType, 0, len(*leftList)-len(*rightList)) diffList := make(ListType, 0, len(*leftList)-len(*rightList))
@@ -100,8 +114,6 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
} }
} }
v = &diffList v = &diffList
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
+1 -1
View File
@@ -119,7 +119,7 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next() tk = scanner.Next()
if tk.IsSymbol(SymIdentifier) { if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk, nil) param := newTerm(tk)
args = append(args, param) args = append(args, param)
tk = scanner.Next() tk = scanner.Next()
} else if itemExpected { } else if itemExpected {
+8 -5
View File
@@ -35,10 +35,10 @@ func TestGeneralParser(t *testing.T) {
/* 14 */ {`not true`, false, nil}, /* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil}, /* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil}, /* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil}, /* *17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil}, /* *18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil}, /* *19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil}, /* *20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil}, /* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil}, /* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil}, /* 23 */ {"1.5*2", float64(3.0), nil},
@@ -157,6 +157,9 @@ func TestGeneralParser(t *testing.T) {
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil}, /* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 137 */ {`builtin "os.file"`, int64(1), nil}, /* 137 */ {`builtin "os.file"`, int64(1), nil},
/* 138 */ {`v=10; v++; v`, int64(11), nil}, /* 138 */ {`v=10; v++; v`, int64(11), nil},
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
/* 140 */ {`1.2()`, newFraction(6, 5), nil},
/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
@@ -176,7 +179,7 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
ctx := NewSimpleFuncStore() ctx := NewSimpleFuncStore()
parser := NewParser(ctx) parser := NewParser(ctx)
logTest(t, i+1, "Iterator", input.source, input.wantResult, input.wantErr) logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source) r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
+21 -2
View File
@@ -385,7 +385,8 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
} }
} }
} }
if err == nil && (ch == 'e' || ch == 'E') { if err == nil {
if ch == 'e' || ch == 'E' {
sym = SymFloat sym = SymFloat
sb.WriteByte(ch) sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil { if ch, err = self.readChar(); err == nil {
@@ -398,7 +399,23 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
} else { } else {
err = errors.New("expected integer exponent") err = fmt.Errorf("[%d:%d] expected integer exponent, got %c", self.row, self.column, ch)
}
}
} else if ch == '(' {
sym = SymFraction
sb.WriteByte(ch)
ch, err = self.readChar()
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
if err == nil {
if ch != ')' {
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", self.row, self.column, ch)
} else {
sb.WriteByte(ch)
_, err = self.readChar()
}
} }
} }
} }
@@ -412,6 +429,8 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
txt := sb.String() txt := sb.String()
if sym == SymFloat { if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64) value, err = strconv.ParseFloat(txt, 64)
} else if sym == SymFraction {
value, err = makeGeneratingFraction(txt)
} else { } else {
value, err = strconv.ParseInt(txt, numBase, 64) value, err = strconv.ParseInt(txt, numBase, 64)
} }
+10 -5
View File
@@ -7,6 +7,7 @@ package expr
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"strings" "strings"
"testing" "testing"
) )
@@ -55,15 +56,18 @@ func TestScanner(t *testing.T) {
/* 33 */ {`(`, SymOpenRound, nil, nil}, /* 33 */ {`(`, SymOpenRound, nil, nil},
/* 34 */ {`)`, SymClosedRound, nil, nil}, /* 34 */ {`)`, SymClosedRound, nil, nil},
/* 35 */ {`1E+2`, SymFloat, float64(100), nil}, /* 35 */ {`1E+2`, SymFloat, float64(100), nil},
/* 36 */ {`1E+x`, SymError, errors.New("expected integer exponent"), nil}, /* 36 */ {`1E+x`, SymError, errors.New("[1:5] expected integer exponent, got x"), nil},
/* 37 */ {`$`, SymDollar, nil, nil}, /* 37 */ {`$`, SymDollar, nil, nil},
/* 38 */ {`\`, SymError, errors.New("incomplete escape sequence"), nil}, /* 38 */ {`\`, SymError, errors.New("incomplete escape sequence"), nil},
/* 39 */ {`"string"`, SymString, "string", nil}, /* 39 */ {`"string"`, SymString, "string", nil},
/* 39 */ {`identifier`, SymIdentifier, "identifier", nil}, /* 40 */ {`identifier`, SymIdentifier, "identifier", nil},
/* 41 */ {`1.2(3)`, SymFraction, newFraction(37, 30), nil},
} }
for i, input := range inputs { for i, input := range inputs {
// if i != 40 {
// continue
// }
if input.wantErr == nil { if input.wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source)) t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
} else { } else {
@@ -75,7 +79,8 @@ func TestScanner(t *testing.T) {
if tk := scanner.Next(); tk == nil { if tk := scanner.Next(); tk == nil {
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue) t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue { // } else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
} else if tk.Sym != input.wantSym || !reflect.DeepEqual(tk.Value, input.wantValue) {
if tk.Sym == SymError && input.wantSym == tk.Sym { if tk.Sym == SymError && input.wantSym == tk.Sym {
if tkErr, tkOk := tk.Value.(error); tkOk { if tkErr, tkOk := tk.Value.(error); tkOk {
if inputErr, inputOk := input.wantValue.(error); inputOk { if inputErr, inputOk := input.wantValue.(error); inputOk {
@@ -86,7 +91,7 @@ func TestScanner(t *testing.T) {
} }
} }
} else { } else {
t.Errorf("%d: %q -> got = %v (value=%v [%T), want %v (value=%v [%T)", i+1, t.Errorf("%d: %q -> got = %v (value=%v [%T]), want %v (value=%v [%T])", i+1,
input.source, tk.Sym, tk.Value, tk.Value, input.wantSym, input.wantValue, input.wantValue) input.source, tk.Sym, tk.Value, tk.Value, input.wantSym, input.wantValue, input.wantValue)
} }
} }
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// strings_test.go
package expr
import (
"testing"
)
func TestStringsParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil},
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc".1`, `b`, nil},
/* 5 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
}
+3
View File
@@ -73,6 +73,7 @@ const (
SymBool SymBool
SymInteger SymInteger
SymFloat SymFloat
SymFraction
SymString SymString
SymIterator SymIterator
SymOr SymOr
@@ -98,6 +99,7 @@ const (
SymKwBut SymKwBut
SymKwFunc SymKwFunc
SymKwBuiltin SymKwBuiltin
SymKwIn
SymKwInclude SymKwInclude
SymKwNil SymKwNil
) )
@@ -111,6 +113,7 @@ func init() {
"BUILTIN": SymKwBuiltin, "BUILTIN": SymKwBuiltin,
"BUT": SymKwBut, "BUT": SymKwBut,
"FUNC": SymKwFunc, "FUNC": SymKwFunc,
"IN": SymKwIn,
"INCLUDE": SymKwInclude, "INCLUDE": SymKwInclude,
"NOT": SymKwNot, "NOT": SymKwNot,
"OR": SymKwOr, "OR": SymKwOr,
+1 -2
View File
@@ -17,11 +17,10 @@ func registerTermConstructor(sym Symbol, constructor termContructor) {
constructorRegistry[sym] = constructor constructorRegistry[sym] = constructor
} }
func newTerm(tk *Token, parent *term) (inst *term) { func newTerm(tk *Token) (inst *term) {
if constructorRegistry != nil { if constructorRegistry != nil {
if construct, exists := constructorRegistry[tk.Sym]; exists { if construct, exists := constructorRegistry[tk.Sym]; exists {
inst = construct(tk) inst = construct(tk)
inst.setParent(parent)
} }
} }
return return
+1
View File
@@ -31,6 +31,7 @@ func TestGetRoom(t *testing.T) {
t.Errorf("err: got <%v>, want <nil>", gotErr) t.Errorf("err: got <%v>, want <nil>", gotErr)
} }
} }
func TestGetChildrenCount(t *testing.T) { func TestGetChildrenCount(t *testing.T) {
tk1 := NewValueToken(0, 0, SymInteger, "100", 100) tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
+29 -8
View File
@@ -9,37 +9,58 @@ import (
"reflect" "reflect"
) )
func isString(v any) (ok bool) { func IsString(v any) (ok bool) {
_, ok = v.(string) _, ok = v.(string)
return ok return ok
} }
func isInteger(v any) (ok bool) { func IsInteger(v any) (ok bool) {
_, ok = v.(int64) _, ok = v.(int64)
return ok return ok
} }
func isFloat(v any) (ok bool) { func IsFloat(v any) (ok bool) {
_, ok = v.(float64) _, ok = v.(float64)
return ok return ok
} }
func isList(v any) (ok bool) { func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func IsList(v any) (ok bool) {
_, ok = v.(*ListType) _, ok = v.(*ListType)
return ok return ok
} }
func isDict(v any) (ok bool) { func IsDict(v any) (ok bool) {
_, ok = v.(map[any]any) _, ok = v.(map[any]any)
return ok return ok
} }
func isNumber(v any) (ok bool) { func IsFract(v any) (ok bool) {
return isFloat(v) || isInteger(v) _, ok = v.(*fraction)
return ok
}
func IsRational(v any) (ok bool) {
if _, ok = v.(*fraction); !ok {
_, ok = v.(int64)
}
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}
func isNumOrFract(v any) (ok bool) {
return IsFloat(v) || IsInteger(v) || isFraction(v)
} }
func isNumberString(v any) (ok bool) { func isNumberString(v any) (ok bool) {
return isString(v) || isNumber(v) return IsString(v) || IsNumber(v)
} }
func isFunctor(v any) (ok bool) { func isFunctor(v any) (ok bool) {
+139
View File
@@ -0,0 +1,139 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils_test.go
package expr
import (
"errors"
"testing"
)
func TestIsString(t *testing.T) {
count := 0
succeeded := 0
failed := 0
count++
if !IsBool(true) {
t.Errorf("%d: IsBool(true) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsString("abc") {
t.Errorf("%d: IsString(\"abc\") -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsInteger(int64(123)) {
t.Errorf("%d: IsInteger(123) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsFloat(1.23) {
t.Errorf("%d: IsFloat(1.23) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsFloat(numAsFloat(123)) {
t.Errorf("%d: IsFloat(numAsFloat(123)) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
b, ok := toBool(true)
if !(ok && b) {
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++
} else {
succeeded++
}
count++
b, ok = toBool("abc")
if !(ok && b) {
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++
} else {
succeeded++
}
t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
}
func TestToIntOk(t *testing.T) {
source := int64(64)
wantValue := int(64)
wantErr := error(nil)
gotValue, gotErr := toInt(source, "test")
if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestToIntErr(t *testing.T) {
source := uint64(64)
wantValue := 0
wantErr := errors.New(`test expected integer, got uint64 (64)`)
gotValue, gotErr := toInt(source, "test")
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestAnyInteger(t *testing.T) {
type inputType struct {
source any
wantValue int64
wantOk bool
}
section := "utils.anyInteger"
inputs := []inputType{
/* 1 */ {int8(-8), int64(-8), true},
/* 2 */ {int16(-16), int64(-16), true},
/* 3 */ {int32(-32), int64(-32), true},
/* 4 */ {int64(-64), int64(-64), true},
/* 5 */ {uint8(8), int64(8), true},
/* 6 */ {uint16(16), int64(16), true},
/* 7 */ {uint32(32), int64(32), true},
/* 8 */ {uint64(64), int64(64), true},
/* 9 */ {int(-1), int64(-1), true},
/* 10 */ {true, 0, false},
}
succeeded := 0
failed := 0
for i, input := range inputs {
gotValue, gotOk := anyInteger(input.source)
if gotOk != input.wantOk || gotValue != input.wantValue {
t.Errorf("%d: anyInteger(%v) -> gotOk = %t, wantOk = %t; gotValue = %v, wantValue = %v",
i+1, input.source, gotOk, input.wantOk, gotValue, input.wantValue)
failed++
} else {
succeeded++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}