Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f22b5a6f0b | |||
| 7c8dbb0ac7 | |||
| e5c5920db0 | |||
| 61efdb4eef | |||
| 82ec78719d | |||
| 554ff1a9dd | |||
| 6bb891e09d | |||
| 1c4ffd7d64 | |||
| b92b19e1dd | |||
| 9967918418 | |||
| 6c14c07d66 | |||
| 9ea170e53b | |||
| a543360151 | |||
| 24a25bbf94 | |||
| d6a1607041 | |||
| 4d43ab2c2f | |||
| 9bd4a0ba23 | |||
| 2b184cf3f2 | |||
| 263e419d9a | |||
| c39970fa7e | |||
| 14bb9e942b | |||
| 9451958218 | |||
| 91fdc1926e | |||
| 9a3abdf1b6 | |||
| ac3e690f87 | |||
| f0a152a17a | |||
| ca89931ca9 | |||
| d2e8aed4f7 | |||
| 8138cd2a80 | |||
| 6786666cf4 | |||
| 35e794701a | |||
| 52ef134be6 | |||
| 624318d84e | |||
| aa1338cd51 |
@@ -119,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
||||
if self.forest != nil {
|
||||
for _, root := range self.forest {
|
||||
if result, err = root.compute(ctx); err == nil {
|
||||
ctx.setVar(ControlLastResult, result)
|
||||
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||
} else {
|
||||
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
||||
break
|
||||
@@ -128,7 +128,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
||||
}
|
||||
if err == nil {
|
||||
result, err = self.root.compute(ctx)
|
||||
ctx.setVar(ControlLastResult, result)
|
||||
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||
}
|
||||
// } else {
|
||||
// err = errors.New("empty expression")
|
||||
|
||||
+17
-3
@@ -8,6 +8,20 @@ import (
|
||||
"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
|
||||
|
||||
func errCantConvert(funcName string, value any, kind string) error {
|
||||
@@ -24,9 +38,9 @@ func errDivisionByZero(funcName string) error {
|
||||
|
||||
// --- Parameter errors
|
||||
|
||||
func errOneParam(funcName string) error {
|
||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
}
|
||||
// func errOneParam(funcName string) error {
|
||||
// return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
// }
|
||||
|
||||
func errMissingRequiredParameter(funcName, paramName string) error {
|
||||
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ func exportVar(ctx ExprContext, name string, value any) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.setVar(name, value)
|
||||
ctx.UnsafeSetVar(name, value)
|
||||
}
|
||||
|
||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ type ExprContext interface {
|
||||
Clone() ExprContext
|
||||
GetVar(varName string) (value any, exists bool)
|
||||
SetVar(varName string, value any)
|
||||
setVar(varName string, value any)
|
||||
UnsafeSetVar(varName string, value any)
|
||||
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||
|
||||
@@ -27,6 +27,7 @@ func TestDictParser(t *testing.T) {
|
||||
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
|
||||
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
|
||||
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
|
||||
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
@@ -101,3 +102,49 @@ func TestDictParser(t *testing.T) {
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
}
|
||||
|
||||
func TestDictToStringMultiLine(t *testing.T) {
|
||||
var good bool
|
||||
section := "dict-ToString-ML"
|
||||
want := `{
|
||||
"first": 1
|
||||
}`
|
||||
args := map[any]*term{
|
||||
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||
}
|
||||
dict := newDict(args)
|
||||
got := dict.ToString(MultiLine)
|
||||
// fmt.Printf("got=%q\n", got)
|
||||
|
||||
if good = got == want; !good {
|
||||
t.Errorf("ToString(MultiLine): got = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if good {
|
||||
t.Logf("%s -- succeeded", section)
|
||||
} else {
|
||||
t.Logf("%s -- failed", section)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDictToString(t *testing.T) {
|
||||
var good bool
|
||||
section := "dict-ToString-SL"
|
||||
want := `{"first": 1}`
|
||||
args := map[any]*term{
|
||||
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||
}
|
||||
dict := newDict(args)
|
||||
got := dict.ToString(0)
|
||||
// fmt.Printf("got=%q\n", got)
|
||||
|
||||
if good = got == want; !good {
|
||||
t.Errorf("ToString(0): got = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if good {
|
||||
t.Logf("%s -- succeeded", section)
|
||||
} else {
|
||||
t.Logf("%s -- failed", section)
|
||||
}
|
||||
}
|
||||
|
||||
+321
-157
@@ -22,7 +22,7 @@ Expressions calculator
|
||||
|
||||
toc::[]
|
||||
|
||||
#TODO: Work in progress (last update on 2024/05/10, 06:52 a.m.)#
|
||||
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)#
|
||||
|
||||
== Expr
|
||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||
@@ -33,9 +33,9 @@ _Expr_ is a GO package capable of analysing, interpreting and calculating expres
|
||||
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, beyond in additon to the automatic 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` 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.
|
||||
|
||||
It cat work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
|
||||
`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].
|
||||
|
||||
@@ -44,21 +44,23 @@ Here are some examples of execution.
|
||||
.Run `dev-expr` in REPL mode and ask for help
|
||||
[source,shell]
|
||||
----
|
||||
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
|
||||
# Type 'exit' or Ctrl+D to quit the program.
|
||||
|
||||
[user]$ tools/expr -- Expressions calculator v1.7.0,2024/05/08 (celestino.amoroso@portale-stac.it)
|
||||
Type help to get the list of command.
|
||||
[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
|
||||
source -- Load a file as input
|
||||
tty -- Enable/Disable ansi output <1>
|
||||
|
||||
--- Command line options:
|
||||
-b <builtin> Import builtin modules.
|
||||
@@ -80,9 +82,12 @@ Here are some examples of execution.
|
||||
.REPL examples
|
||||
[source,shell]
|
||||
----
|
||||
[user]$ tools/expr -- Expressions calculator v1.6.1,2024/05/06 (celestino.amoroso@portale-stac.it)
|
||||
Type help to get the list of command.
|
||||
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||
[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)
|
||||
@@ -113,55 +118,116 @@ Here are some examples of execution.
|
||||
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
|
||||
|
||||
=== 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
|
||||
[cols="^1,^2,6,4"]
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ +
|
||||
[blue]`-(+2)` _[-2]_
|
||||
|
||||
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 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]_
|
||||
|
||||
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` -> 1
|
||||
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> 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]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
|
||||
|===
|
||||
|
||||
=== Fractions
|
||||
^(*)^ See also the _float division_ [blue]`./` below.
|
||||
|
||||
|
||||
==== 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` +
|
||||
[green]`2|3` [gray]_Fractions are always reduced to their lowest terms_ +
|
||||
`>>>` [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_ +
|
||||
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
|
||||
[green]`1.5` +
|
||||
`>>>` [blue]`-1|2` +
|
||||
[green]`-1|2` +
|
||||
`>>>` [blue]`1|-2` [gray]_Wrong sign specification_ +
|
||||
`>>>` [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`
|
||||
@@ -169,17 +235,29 @@ _Expr_ also supports fractions. Fraction literals are made with two integers sep
|
||||
|
||||
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` +
|
||||
[green]`1.5`
|
||||
|
||||
|
||||
|
||||
=== Strings
|
||||
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
|
||||
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.
|
||||
|
||||
@@ -188,53 +266,52 @@ Some arithmetic operators can also be used with strings.
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
|
||||
[blue]`"one" + 2` _["one2"]_
|
||||
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` -> _"onetwo"_ +
|
||||
[blue]`"one" + 2` -> _"one2"_
|
||||
|
||||
| [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.
|
||||
|
||||
.Item access syntax
|
||||
[source,bnf]
|
||||
----
|
||||
<item> ::= <string-expr>"."<index-expr>
|
||||
----
|
||||
====
|
||||
_item_ = _string-expr_ "**.**" _integer-expr_
|
||||
====
|
||||
|
||||
.String examples
|
||||
`>>>` [blue]`s="abc"` [gray]_assign the string to variable s_ +
|
||||
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ +
|
||||
[green]`abc` +
|
||||
`>>>` [blue]`s.1` [gray]_char at position 1 (starting from 0)_ +
|
||||
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
|
||||
[green]`b` +
|
||||
`>>>` [blue]`s.(-1)` [gray]_char at position -1, the rightmost one_ +
|
||||
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
|
||||
[green]`c` +
|
||||
`>>>` [blue]`\#s` [gray]_number of chars_ +
|
||||
`>>>` [blue]`\#s` [gray]_// number of chars_ +
|
||||
[gren]`3` +
|
||||
`>>>` [blue]`#"abc"` [gray]_number of chars_ +
|
||||
[green]`3` +
|
||||
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
|
||||
[green]`3`
|
||||
|
||||
=== Boolean
|
||||
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
|
||||
|
||||
=== Booleans
|
||||
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||
|
||||
.Relational operators
|
||||
[cols="^1,^2,6,4"]
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ +
|
||||
[blue]`"a" == "a"` _[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]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ +
|
||||
[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]`"b" \<= "b"` _[true]_
|
||||
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ +
|
||||
[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]`"b" \<= "b"` _[true]_
|
||||
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` -> _false_ +
|
||||
[blue]`"a" == "a"` -> _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]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` -> _false_ +
|
||||
[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]`"b" \<= "b"` -> _true_
|
||||
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` -> _true_ +
|
||||
[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]`"b" \<= "b"` -> _true_
|
||||
|===
|
||||
|
||||
|
||||
@@ -243,19 +320,19 @@ Boolean data type has two values only: _true_ and _false_. Relational and Boolea
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ +
|
||||
[blue]`NOT (2 < 1)` _[true]_
|
||||
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` -> _false_ +
|
||||
[blue]`NOT (2 < 1)` -> _true_
|
||||
|
||||
| [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]`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]`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]`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_
|
||||
|===
|
||||
|
||||
[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
|
||||
[source,go]
|
||||
@@ -266,35 +343,47 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
|
||||
====
|
||||
|
||||
=== 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
|
||||
[source,go]
|
||||
----
|
||||
[1, 2, 3] // List of integers
|
||||
["one", "two", "three"] // List of strings
|
||||
["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
|
||||
----
|
||||
.List literal syntax
|
||||
====
|
||||
*_list_* = _empty-list_ | _non-empty-list_ +
|
||||
_empty-list_ = "**[]**" +
|
||||
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
||||
====
|
||||
|
||||
.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
|
||||
[cols="^2,^2,5,4"]
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [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]`+` | _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]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
|
||||
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
|
||||
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
|
||||
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
|
||||
[blue]`6 in [1,2,3]` -> _false_
|
||||
|===
|
||||
|
||||
The items of array can be accessed using the dot `.` operator.
|
||||
|
||||
.Item access syntax
|
||||
[source,bnf]
|
||||
----
|
||||
<item> ::= <list-expr>"."<index-expr>
|
||||
----
|
||||
====
|
||||
_item_ = _list-expr_ "**.**" _integer-expr_
|
||||
====
|
||||
|
||||
.Items of list
|
||||
`>>>` [blue]`[1,2,3].1` +
|
||||
@@ -310,113 +399,183 @@ The items of array can be accessed using the dot `.` operator.
|
||||
`>>>` [blue]`list.(10)` +
|
||||
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
||||
`>>>` [blue]`#list` +
|
||||
[green]`3`
|
||||
[green]`3` +
|
||||
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
|
||||
[green]`c`
|
||||
|
||||
|
||||
|
||||
== Dictionaries
|
||||
The _dictionary_ 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.
|
||||
=== Dictionaries
|
||||
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
|
||||
|
||||
.Dictionary examples
|
||||
[source,go]
|
||||
----
|
||||
{1:"one", 2:"two"}
|
||||
{"one":1, "two": 2}
|
||||
{"sum":1+2+3, "prod":1*2*3}
|
||||
----
|
||||
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
|
||||
|
||||
WARNING: Support for dictionaries is still ongoing.
|
||||
|
||||
== Variables
|
||||
A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go _ExprContext_ interface, e.g. _SimpleVarStore_ or _SimpleFuncStore_.
|
||||
.Dict literal syntax
|
||||
====
|
||||
*_dict_* = _empty-dict_ | _non-empty-dict_ +
|
||||
_empty-dict_ = "**{}**" +
|
||||
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
|
||||
====
|
||||
|
||||
.Examples
|
||||
[source,go]
|
||||
----
|
||||
a=1
|
||||
x = 5.2 * (9-3)
|
||||
x = 1; y = 2*x
|
||||
----
|
||||
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||
[green]`{1: "one", 2: "two"}` +
|
||||
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||
[green]`{"one": 1, "two": 2}` +
|
||||
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||
[green]`{"sum": 6, "prod": 6}`
|
||||
|
||||
|
||||
.Dict operators
|
||||
[cols="^2,^2,4,5"]
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
|
||||
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
|
||||
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
|
||||
[blue]`"six" in {"one":1, "two":2}` -> _false_
|
||||
|===
|
||||
|
||||
|
||||
== Variables
|
||||
_Expr_, like most programming languages, supports variables. 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
|
||||
|
||||
=== [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.
|
||||
|
||||
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
||||
|
||||
.Example
|
||||
[source,go]
|
||||
----
|
||||
a=1; b=2; c=3; a+b+c // returns 6
|
||||
----
|
||||
`>>>` [blue]`a=1; b=2; c=3; a+b+c` +
|
||||
[green]`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` 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 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.
|
||||
|
||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||
.Examples
|
||||
[blue]`5 but 2` +
|
||||
[green]`2` +
|
||||
[blue]`x=2*3 but x-1` +
|
||||
[green]`5`.
|
||||
|
||||
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||
|
||||
=== Assignment operator [blue]`=`
|
||||
The assignment operator [blue]`=` is used to define variables in the evaluation context or to change their value (see _ExprContext_).
|
||||
The assignment operator [blue]`=` is used to define variables or to change their value in the evaluation context (see _ExprContext_).
|
||||
|
||||
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
|
||||
[source,go]
|
||||
----
|
||||
a=15+1 // returns 16
|
||||
----
|
||||
`>>>` [blue]`a=15+1`
|
||||
[green]`16`
|
||||
|
||||
|
||||
=== Selector operator [blue]`? : ::`
|
||||
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
||||
|
||||
.Syntax
|
||||
[source,bnf]
|
||||
----
|
||||
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>]
|
||||
<selector-case> ::= [<match-list>] <case-value>
|
||||
<match-list> ::= "["<item>{","<items>}"]"
|
||||
<item> ::= <expression
|
||||
<case-multi-expression> ::= "{" <multi-expression> "}"
|
||||
<multi-expression> ::= <expression> {";" <expression>}
|
||||
----
|
||||
.Selector literal Syntax
|
||||
_selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
|
||||
_selector-case_ = [_match-list_] _case-value_ +
|
||||
_match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
|
||||
_item_ = _expression_ +
|
||||
_case-multi-expression_ = "*{*" _multi-expression_ "*}*" +
|
||||
_multi-expression_ = _expression_ { "*;*" _expression_ } +
|
||||
_default-multi-expression_ = _multi-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 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.
|
||||
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
|
||||
[source,go]
|
||||
----
|
||||
`>>>` [blue]`1 ? {"a"} : {"b"}`
|
||||
[green]`b`
|
||||
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}`
|
||||
[green]`c'
|
||||
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`
|
||||
[green]`b`
|
||||
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`
|
||||
[red]`Parse Error: [1:34] case list in default clause`
|
||||
[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"}`
|
||||
`>>>` [blue]`1 ? {"a"} : {"b"}` +
|
||||
[green]`b` +
|
||||
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
|
||||
[green]`c' +
|
||||
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
|
||||
[green]`b` +
|
||||
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
|
||||
[red]`Parse Error: [1:34] case list in default clause` +
|
||||
[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
|
||||
The table below shows all supported operators by decreasing priorities.
|
||||
|
||||
.Operators priorities
|
||||
[cols="^2,^2,^2,^5,^5"]
|
||||
[cols="^2,^2,^2,^5,^6"]
|
||||
|===
|
||||
| Priority | Operators | Position | Operation | Operands and results
|
||||
|
||||
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
|
||||
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
|
||||
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `""` _any_ -> _any_
|
||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||
@@ -428,24 +587,29 @@ The table below shows all supported operators by decreasing priorities.
|
||||
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
||||
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
|
||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
||||
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
|
||||
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_
|
||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
|
||||
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
|
||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||
|===
|
||||
|
||||
== Functions
|
||||
|
||||
+575
-223
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -22,7 +22,7 @@ func TestExpr(t *testing.T) {
|
||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
||||
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
||||
/* 10 */ {`
|
||||
/* 6 */ {`
|
||||
ds={
|
||||
"init":func(end){@end=end; @current=0 but true},
|
||||
"current":func(){current},
|
||||
|
||||
@@ -4,17 +4,62 @@
|
||||
// formatter.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
|
||||
type FmtOpt uint16
|
||||
|
||||
const (
|
||||
TTY FmtOpt = 1 << iota
|
||||
MultiLine
|
||||
Truncate
|
||||
Base2
|
||||
Base8
|
||||
Base10
|
||||
Base16
|
||||
)
|
||||
|
||||
const (
|
||||
TruncateEllipsis = "(...)"
|
||||
MinTruncateSize = 10
|
||||
TruncateSize = MinTruncateSize + 15
|
||||
)
|
||||
|
||||
func TruncateString(s string) (trunc string) {
|
||||
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
|
||||
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
|
||||
return
|
||||
}
|
||||
|
||||
type Formatter interface {
|
||||
ToString(options FmtOpt) string
|
||||
}
|
||||
|
||||
func getFormatted(v any, opt FmtOpt) (text string) {
|
||||
if v == nil {
|
||||
text = "(nil)"
|
||||
} else if s, ok := v.(string); ok {
|
||||
text = s
|
||||
} else if formatter, ok := v.(Formatter); ok {
|
||||
text = formatter.ToString(opt)
|
||||
} else {
|
||||
text = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Typer interface {
|
||||
TypeName() string
|
||||
}
|
||||
|
||||
func getTypeName(v any) (name string) {
|
||||
if typer, ok := v.(Typer); ok {
|
||||
name = typer.TypeName()
|
||||
} else if IsInteger(v) {
|
||||
name = "integer"
|
||||
} else if IsFloat(v) {
|
||||
name = "float"
|
||||
} else {
|
||||
name = fmt.Sprintf("%T", v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+1
-5
@@ -10,11 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) == 1 {
|
||||
result = args[0] == nil
|
||||
} else {
|
||||
err = errOneParam(name)
|
||||
}
|
||||
result = args[0] == nil
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+18
-18
@@ -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) {
|
||||
if len(args) < 1 {
|
||||
return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
}
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
// }
|
||||
if sep, ok := args[0].(string); ok {
|
||||
if len(args) == 1 {
|
||||
result = ""
|
||||
@@ -62,9 +62,9 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
|
||||
var source string
|
||||
var ok bool
|
||||
|
||||
if len(args) < 1 {
|
||||
return nil, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
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 ok bool
|
||||
|
||||
if len(args) < 1 {
|
||||
return nil, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
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
|
||||
|
||||
result = false
|
||||
if len(args) < 1 {
|
||||
return result, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
// if len(args) < 1 {
|
||||
// return result, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
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
|
||||
|
||||
result = false
|
||||
if len(args) < 1 {
|
||||
return result, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
// if len(args) < 1 {
|
||||
// return result, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
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 ok bool
|
||||
|
||||
if len(args) < 1 {
|
||||
return result, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
// if len(args) < 1 {
|
||||
// return result, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ func TestFuncs(t *testing.T) {
|
||||
/* 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},
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -40,6 +40,9 @@ func TestListParser(t *testing.T) {
|
||||
/* 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},
|
||||
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil},
|
||||
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, 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},
|
||||
@@ -58,8 +61,8 @@ func TestListParser(t *testing.T) {
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx.SetVar("var1", int64(123))
|
||||
ctx.SetVar("var2", "abc")
|
||||
// ctx.SetVar("var1", int64(123))
|
||||
// ctx.SetVar("var2", "abc")
|
||||
ImportMathFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
|
||||
+121
-2
@@ -4,6 +4,125 @@
|
||||
// operand-dict.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DictType map[any]any
|
||||
|
||||
func newDict(dictAny map[any]*term) (dict *DictType) {
|
||||
var d DictType
|
||||
if dictAny != nil {
|
||||
d = make(DictType, len(dictAny))
|
||||
for i, item := range dictAny {
|
||||
d[i] = item
|
||||
}
|
||||
} else {
|
||||
d = make(DictType)
|
||||
}
|
||||
dict = &d
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("{\n")
|
||||
|
||||
first := true
|
||||
for name, value := range *dict {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
if key, ok := name.(string); ok {
|
||||
sb.WriteString(string('"') + key + string('"'))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", name))
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
if f, ok := value.(Formatter); ok {
|
||||
sb.WriteString(f.ToString(MultiLine))
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("\n}")
|
||||
}
|
||||
|
||||
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
if opt&MultiLine != 0 {
|
||||
dict.toMultiLine(&sb, 0)
|
||||
} else {
|
||||
sb.WriteByte('{')
|
||||
first := true
|
||||
for key, value := range *dict {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if s, ok := key.(string); ok {
|
||||
sb.WriteString(string('"') + s + string('"'))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", key))
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(opt))
|
||||
} else if t, ok := value.(*term); ok {
|
||||
sb.WriteString(t.String())
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%#v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (dict *DictType) String() string {
|
||||
return dict.ToString(0)
|
||||
}
|
||||
|
||||
func (dict *DictType) TypeName() string {
|
||||
return "dict"
|
||||
}
|
||||
|
||||
func (dict *DictType) hasKey(target any) (ok bool) {
|
||||
for key := range *dict {
|
||||
if ok = reflect.DeepEqual(key, target); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) clone() (c *DictType) {
|
||||
c = newDict(nil)
|
||||
for k, v := range *dict {
|
||||
(*c)[k] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) merge(second *DictType) {
|
||||
if second != nil {
|
||||
for k, v := range *second {
|
||||
(*dict)[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------- dict term
|
||||
func newDictTerm(args map[any]*term) *term {
|
||||
return &term{
|
||||
@@ -19,7 +138,7 @@ func newDictTerm(args map[any]*term) *term {
|
||||
// -------- dict func
|
||||
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
||||
dict, _ := self.value().(map[any]*term)
|
||||
items := make(map[any]any, len(dict))
|
||||
items := make(DictType, len(dict))
|
||||
for key, tree := range dict {
|
||||
var param any
|
||||
if param, err = tree.compute(ctx); err != nil {
|
||||
@@ -28,7 +147,7 @@ func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
||||
items[key] = param
|
||||
}
|
||||
if err == nil {
|
||||
v = items
|
||||
v = &items
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+4
-3
@@ -7,7 +7,8 @@ package expr
|
||||
import "fmt"
|
||||
|
||||
// -------- expr term
|
||||
func newExprTerm(tk *Token) *term {
|
||||
func newExprTerm(root *term) *term {
|
||||
tk := NewValueToken(root.tk.row, root.tk.col, SymExpression, root.source(), root)
|
||||
return &term{
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
@@ -20,8 +21,8 @@ func newExprTerm(tk *Token) *term {
|
||||
|
||||
// -------- eval expr
|
||||
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
||||
if expr, ok := self.value().(Expr); ok {
|
||||
v, err = expr.eval(ctx, false)
|
||||
if expr, ok := self.value().(*term); ok {
|
||||
v, err = expr.compute(ctx)
|
||||
} else {
|
||||
err = fmt.Errorf("expression expected, got %T", self.value())
|
||||
}
|
||||
|
||||
+4
-8
@@ -25,14 +25,10 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
||||
if info, exists, owner := GetFuncInfo(ctx, name); exists {
|
||||
if info.MinArgs() > len(params) {
|
||||
if info.MaxArgs() < 0 {
|
||||
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))
|
||||
}
|
||||
err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params))
|
||||
}
|
||||
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
|
||||
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
|
||||
err = errTooMuchParams(info.MaxArgs(), len(params))
|
||||
}
|
||||
if err == nil && owner != ctx {
|
||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||
@@ -91,10 +87,10 @@ func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any)
|
||||
if functor, ok := arg.(Functor); ok {
|
||||
ctx.RegisterFunc(p, functor, 0, -1)
|
||||
} else {
|
||||
ctx.setVar(p, arg)
|
||||
ctx.UnsafeSetVar(p, arg)
|
||||
}
|
||||
} else {
|
||||
ctx.setVar(p, nil)
|
||||
ctx.UnsafeSetVar(p, nil)
|
||||
}
|
||||
}
|
||||
result, err = functor.expr.eval(ctx, false)
|
||||
|
||||
+3
-2
@@ -66,12 +66,13 @@ func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
|
||||
}
|
||||
|
||||
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
||||
if dictAny, ok := firstChildValue.(map[any]any); ok {
|
||||
// if dictAny, ok := firstChildValue.(map[any]any); ok {
|
||||
if dictAny, ok := firstChildValue.(*DictType); ok {
|
||||
requiredFields := []string{currentName, nextName}
|
||||
fieldsMask := 0b11
|
||||
foundFields := 0
|
||||
ds = make(map[string]Functor)
|
||||
for keyAny, item := range dictAny {
|
||||
for keyAny, item := range *dictAny {
|
||||
if key, ok := keyAny.(string); ok {
|
||||
if functor, ok := item.(*funcDefFunctor); ok {
|
||||
ds[key] = functor
|
||||
|
||||
+30
-11
@@ -6,12 +6,28 @@ package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ListType []any
|
||||
|
||||
func (ls *ListType) ToString(opt FmtOpt) string {
|
||||
func newListA(listAny ...any) (list *ListType) {
|
||||
return newList(listAny)
|
||||
}
|
||||
|
||||
func newList(listAny []any) (list *ListType) {
|
||||
if listAny != nil {
|
||||
ls := make(ListType, len(listAny))
|
||||
for i, item := range listAny {
|
||||
ls[i] = item
|
||||
}
|
||||
list = &ls
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte('[')
|
||||
if len(*ls) > 0 {
|
||||
@@ -39,29 +55,32 @@ func (ls *ListType) ToString(opt FmtOpt) string {
|
||||
}
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
return sb.String()
|
||||
s = sb.String()
|
||||
if opt&Truncate != 0 && len(s) > TruncateSize {
|
||||
s = TruncateString(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *ListType) String() string {
|
||||
return ls.ToString(0)
|
||||
}
|
||||
|
||||
func newListA(listAny ...any) (list *ListType) {
|
||||
return newList(listAny)
|
||||
func (ls *ListType) TypeName() string {
|
||||
return "list"
|
||||
}
|
||||
|
||||
func newList(listAny []any) (list *ListType) {
|
||||
if listAny != nil {
|
||||
ls := make(ListType, len(listAny))
|
||||
for i, item := range listAny {
|
||||
ls[i] = item
|
||||
func (list *ListType) indexDeepCmp(target any) (index int) {
|
||||
index = -1
|
||||
for i, item := range *list {
|
||||
if reflect.DeepEqual(item, target) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
list = &ls
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// -------- list term
|
||||
func newListTermA(args ...*term) *term {
|
||||
return newListTerm(args)
|
||||
|
||||
+3
-1
@@ -8,7 +8,7 @@ import "fmt"
|
||||
|
||||
// -------- variable term
|
||||
func newVarTerm(tk *Token) *term {
|
||||
return &term{
|
||||
t := &term{
|
||||
tk: *tk,
|
||||
// class: classVar,
|
||||
// kind: kindUnknown,
|
||||
@@ -18,6 +18,8 @@ func newVarTerm(tk *Token) *term {
|
||||
priority: priValue,
|
||||
evalFunc: evalVar,
|
||||
}
|
||||
t.tk.Sym = SymVariable
|
||||
return t
|
||||
}
|
||||
|
||||
// -------- eval func
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
leftTerm := self.children[0]
|
||||
if leftTerm.tk.Sym != SymIdentifier {
|
||||
if leftTerm.tk.Sym != SymVariable {
|
||||
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||
return
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
||||
} else {
|
||||
ctx.setVar(leftTerm.source(), v)
|
||||
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
+4
-15
@@ -8,9 +8,7 @@ package expr
|
||||
|
||||
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindUnknown,
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priCoalesce,
|
||||
@@ -26,7 +24,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
leftTerm := self.children[0]
|
||||
if leftTerm.tk.Sym != SymIdentifier {
|
||||
if leftTerm.tk.Sym != SymVariable {
|
||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||
return
|
||||
}
|
||||
@@ -34,11 +32,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||
v = leftValue
|
||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||
// if _, ok := rightValue.(Functor); ok {
|
||||
// err = errCoalesceNoFunc(self.children[1])
|
||||
// } else {
|
||||
v = rightValue
|
||||
// }
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -63,7 +57,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
leftTerm := self.children[0]
|
||||
if leftTerm.tk.Sym != SymIdentifier {
|
||||
if leftTerm.tk.Sym != SymVariable {
|
||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||
return
|
||||
}
|
||||
@@ -75,17 +69,12 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||
} else {
|
||||
v = rightValue
|
||||
ctx.setVar(leftTerm.source(), rightValue)
|
||||
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// utils
|
||||
// func errCoalesceNoFunc(t *term) error {
|
||||
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
||||
// }
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
||||
|
||||
+3
-15
@@ -59,28 +59,16 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||
v = string(unboxedValue[index])
|
||||
}
|
||||
case map[any]any:
|
||||
case *DictType:
|
||||
var ok bool
|
||||
var indexValue any
|
||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||
if v, ok = unboxedValue[indexValue]; !ok {
|
||||
if v, ok = (*unboxedValue)[indexValue]; !ok {
|
||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||
}
|
||||
}
|
||||
// case *dataCursor:
|
||||
// if indexTerm.symbol() == SymIdentifier {
|
||||
// opName := indexTerm.source()
|
||||
// if opName == resetName {
|
||||
// _, err = unboxedValue.Reset()
|
||||
// } else if opName == cleanName {
|
||||
// _, err = unboxedValue.Clean()
|
||||
// } else {
|
||||
// err = indexTerm.Errorf("iterators do not support command %q", opName)
|
||||
// }
|
||||
// v = err == nil
|
||||
// }
|
||||
case ExtIterator:
|
||||
if indexTerm.symbol() == SymIdentifier {
|
||||
if indexTerm.symbol() == SymVariable {
|
||||
opName := indexTerm.source()
|
||||
if unboxedValue.HasOperation(opName) {
|
||||
v, err = unboxedValue.CallOperation(opName, []any{})
|
||||
|
||||
+12
-3
@@ -4,6 +4,8 @@
|
||||
// operand-fraction.go
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -29,7 +31,7 @@ func float64ToFraction(f float64) (fract *fraction, err error) {
|
||||
var sign string
|
||||
intPart, decPart := math.Modf(f)
|
||||
if decPart < 0.0 {
|
||||
sign="-"
|
||||
sign = "-"
|
||||
intPart = -intPart
|
||||
decPart = -decPart
|
||||
}
|
||||
@@ -89,11 +91,14 @@ func makeGeneratingFraction(s string) (f *fraction, err error) {
|
||||
goto exit
|
||||
}
|
||||
if s[0] == '-' {
|
||||
sign=int64(-1)
|
||||
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
|
||||
@@ -106,7 +111,7 @@ func makeGeneratingFraction(s string) (f *fraction, err error) {
|
||||
den = 1
|
||||
dec := parts[1]
|
||||
lsd := len(dec)
|
||||
for i:=lsd-1; i>= 0 && dec[i]=='0'; i-- {
|
||||
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
|
||||
lsd--
|
||||
}
|
||||
for _, c := range dec[0:lsd] {
|
||||
@@ -199,6 +204,10 @@ func (f *fraction) ToString(opt FmtOpt) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (f *fraction) TypeName() string {
|
||||
return "fraction"
|
||||
}
|
||||
|
||||
// -------- fraction term
|
||||
func newFractionTerm(tk *Token) *term {
|
||||
return &term{
|
||||
|
||||
@@ -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) {
|
||||
dict, _ := rightValue.(*DictType)
|
||||
v = dict.hasKey(leftValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymKwIn, newInTerm)
|
||||
}
|
||||
@@ -37,6 +37,9 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
||||
list, _ := rightValue.(*ListType)
|
||||
newList := append(ListType{leftValue}, *list...)
|
||||
v = &newList
|
||||
if self.children[1].symbol() == SymVariable {
|
||||
ctx.UnsafeSetVar(self.children[1].source(), v)
|
||||
}
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -54,12 +57,33 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||
list, _ := leftValue.(*ListType)
|
||||
newList := append(*list, rightValue)
|
||||
v = &newList
|
||||
if self.children[0].symbol() == SymVariable {
|
||||
ctx.UnsafeSetVar(self.children[0].source(), v)
|
||||
}
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||
// var leftValue, rightValue any
|
||||
|
||||
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if IsList(leftValue) {
|
||||
// list, _ := leftValue.(*ListType)
|
||||
// newList := append(*list, rightValue)
|
||||
// v = &newList
|
||||
// if
|
||||
// } else {
|
||||
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymInsert, newInsertTerm)
|
||||
|
||||
+3
-2
@@ -30,8 +30,9 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||
s, _ := childValue.(string)
|
||||
v = int64(len(s))
|
||||
} else if IsDict(childValue) {
|
||||
m, _ := childValue.(map[any]any)
|
||||
v = int64(len(m))
|
||||
// m, _ := childValue.(map[any]any)
|
||||
m, _ := childValue.(*DictType)
|
||||
v = int64(len(*m))
|
||||
} else if it, ok := childValue.(Iterator); ok {
|
||||
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
||||
count, _ := extIt.CallOperation(countName, nil)
|
||||
|
||||
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
if it, ok := childValue.(Iterator); ok {
|
||||
v, err = it.Next()
|
||||
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
||||
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable {
|
||||
v = childValue
|
||||
i, _ := childValue.(int64)
|
||||
ctx.SetVar(self.children[0].source(), i+1)
|
||||
|
||||
+26
-15
@@ -38,15 +38,28 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
rightInt, _ := rightValue.(int64)
|
||||
v = leftInt + rightInt
|
||||
}
|
||||
} else if IsList(leftValue) || IsList(rightValue) {
|
||||
// } else if IsList(leftValue) || IsList(rightValue) {
|
||||
// var leftList, rightList *ListType
|
||||
// var ok bool
|
||||
// if leftList, ok = leftValue.(*ListType); !ok {
|
||||
// leftList = &ListType{leftValue}
|
||||
// }
|
||||
// if rightList, ok = rightValue.(*ListType); !ok {
|
||||
// rightList = &ListType{rightValue}
|
||||
// }
|
||||
// sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||
// for _, item := range *leftList {
|
||||
// sumList = append(sumList, item)
|
||||
// }
|
||||
// for _, item := range *rightList {
|
||||
// sumList = append(sumList, item)
|
||||
// }
|
||||
// v = &sumList
|
||||
} else if IsList(leftValue) && IsList(rightValue) {
|
||||
var leftList, rightList *ListType
|
||||
var ok bool
|
||||
if leftList, ok = leftValue.(*ListType); !ok {
|
||||
leftList = &ListType{leftValue}
|
||||
}
|
||||
if rightList, ok = rightValue.(*ListType); !ok {
|
||||
rightList = &ListType{rightValue}
|
||||
}
|
||||
leftList, _ = leftValue.(*ListType)
|
||||
rightList, _ = rightValue.(*ListType)
|
||||
|
||||
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||
for _, item := range *leftList {
|
||||
sumList = append(sumList, item)
|
||||
@@ -55,19 +68,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
sumList = append(sumList, item)
|
||||
}
|
||||
v = &sumList
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
|
||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
leftDict, _ := leftValue.(*DictType)
|
||||
rightDict, _ := rightValue.(*DictType)
|
||||
c := leftDict.clone()
|
||||
c.merge(rightDict)
|
||||
v = c
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
|
||||
@@ -277,6 +277,7 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
||||
err = scanner.Previous().ErrorExpectedGot("}")
|
||||
} else {
|
||||
subtree = newDictTerm(args)
|
||||
// subtree = newMapTerm(args)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -389,7 +390,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
var subTree *ast
|
||||
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
||||
subTree.root.priority = priValue
|
||||
err = tree.addTerm(subTree.root)
|
||||
err = tree.addTerm(newExprTerm(subTree.root))
|
||||
currentTerm = subTree.root
|
||||
}
|
||||
case SymFuncCall:
|
||||
|
||||
+4
-2
@@ -98,7 +98,7 @@ func TestGeneralParser(t *testing.T) {
|
||||
/* 77 */ {`5 % 2`, int64(1), nil},
|
||||
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
||||
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
||||
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
||||
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
|
||||
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
||||
@@ -137,7 +137,7 @@ func TestGeneralParser(t *testing.T) {
|
||||
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||
/* 119 */ {`{}`, map[any]any{}, nil},
|
||||
/* 119 */ {`{}`, &DictType{}, nil},
|
||||
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
||||
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||
@@ -158,6 +158,8 @@ func TestGeneralParser(t *testing.T) {
|
||||
/* 137 */ {`builtin "os.file"`, int64(1), 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", ".")
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
||||
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||
ctx.varStore[varName] = value
|
||||
}
|
||||
@@ -98,8 +98,8 @@ func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString(f.ToString(0))
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
} else if _, ok = value.(map[any]any); ok {
|
||||
sb.WriteString("dict{}")
|
||||
// } else if _, ok = value.(map[any]any); ok {
|
||||
// sb.WriteString("dict{}")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ const (
|
||||
SymIdentifier
|
||||
SymBool
|
||||
SymInteger
|
||||
SymVariable
|
||||
SymFloat
|
||||
SymFraction
|
||||
SymString
|
||||
@@ -99,6 +100,7 @@ const (
|
||||
SymKwBut
|
||||
SymKwFunc
|
||||
SymKwBuiltin
|
||||
SymKwIn
|
||||
SymKwInclude
|
||||
SymKwNil
|
||||
)
|
||||
@@ -112,6 +114,7 @@ func init() {
|
||||
"BUILTIN": SymKwBuiltin,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"IN": SymKwIn,
|
||||
"INCLUDE": SymKwInclude,
|
||||
"NOT": SymKwNot,
|
||||
"OR": SymKwOr,
|
||||
|
||||
@@ -161,10 +161,14 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
|
||||
}
|
||||
|
||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||
leftType := getTypeName(leftValue)
|
||||
leftText := getFormatted(leftValue, Truncate)
|
||||
rightType := getTypeName(rightValue)
|
||||
rightText := getFormatted(rightValue, Truncate)
|
||||
return self.tk.Errorf(
|
||||
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
||||
leftValue, leftValue,
|
||||
rightValue, rightValue,
|
||||
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
|
||||
leftText, leftType,
|
||||
rightText, rightType,
|
||||
self.source())
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ func TestGetRoom(t *testing.T) {
|
||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChildrenCount(t *testing.T) {
|
||||
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ func IsList(v any) (ok bool) {
|
||||
}
|
||||
|
||||
func IsDict(v any) (ok bool) {
|
||||
_, ok = v.(map[any]any)
|
||||
_, ok = v.(*DictType)
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -147,6 +147,12 @@ func fromGenericAny(v any) (exprAny any, ok bool) {
|
||||
if exprAny, ok = anyFloat(v); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(*DictType); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(*ListType); ok {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+139
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user