Compare commits
40 Commits
539a4b44e9
...
v0.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
| f028485caa | |||
| ab07405cda | |||
| 47be0c66cf | |||
| 50e7168214 | |||
| 0a9543543d | |||
| 775751c67b | |||
| 4aaffd6c44 | |||
| 924051fbcd | |||
| 5a9b6525a2 | |||
| 8c66d90532 | |||
| 4aa0113c6a | |||
| d035fa0d5e | |||
| cf73b5c98d | |||
| 8346e28340 | |||
| 9c2eca40d7 | |||
| c3198e4c79 | |||
| 99e0190b9c | |||
| d4f63a3837 | |||
| 389d48b646 | |||
| 1f7b9131fc | |||
| dce49fd2b7 | |||
| 8ee0bb5701 | |||
| b2b0bb04c5 | |||
| f3abf5e77c | |||
| 34b7799177 | |||
| 8c3c54913a | |||
| 5910345c08 | |||
| 8b4dad1381 | |||
| 6ef468408c | |||
| 3a30d890c6 | |||
| 71ab417a56 | |||
| 7cfb89c25c | |||
| 569dbfda9d | |||
| f342dfe9f3 | |||
| 5378952394 | |||
| a9d6a82011 | |||
| 43b74131fb | |||
| cb0eac54b2 | |||
| b219b55878 | |||
| 56c86f917e |
@@ -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")
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ 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 {
|
||||||
|
|||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// dict_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDictParser(t *testing.T) {
|
||||||
|
section := "Dict"
|
||||||
|
|
||||||
|
type inputType struct {
|
||||||
|
source string
|
||||||
|
wantResult any
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`{}`, map[any]any{}, nil},
|
||||||
|
/* 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},
|
||||||
|
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
|
||||||
|
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
// inputs1 := []inputType{
|
||||||
|
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||||
|
// }
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
var expr *ast
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
ctx := NewSimpleFuncStore()
|
||||||
|
ctx.SetVar("var1", int64(123))
|
||||||
|
ctx.SetVar("var2", "abc")
|
||||||
|
ImportMathFuncs(ctx)
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr)
|
||||||
|
|
||||||
|
r := strings.NewReader(input.source)
|
||||||
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
|
|
||||||
|
good := true
|
||||||
|
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||||
|
gotResult, gotErr = expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotList, okGot := gotResult.([]any); okGot {
|
||||||
|
if wantList, okWant := input.wantResult.([]any); okWant {
|
||||||
|
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
} else {
|
||||||
|
equal := len(gotList) == len(wantList)
|
||||||
|
if equal {
|
||||||
|
for i, gotItem := range gotList {
|
||||||
|
wantItem := wantList[i]
|
||||||
|
equal = gotItem == wantItem
|
||||||
|
if !equal {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !equal {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotErr != input.wantErr {
|
||||||
|
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
+195
-16
@@ -22,15 +22,93 @@ Expressions calculator
|
|||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
#TODO: Work in progress#
|
#TODO: Work in progress (last update on 2024/05/10, 06:52 a.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, 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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]
|
||||||
|
----
|
||||||
|
# Assume the expr source directory. 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.
|
||||||
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
|
|
||||||
|
>>> help
|
||||||
|
--- REPL commands:
|
||||||
|
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.
|
||||||
|
<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]$ 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
|
||||||
|
>>> 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.
|
||||||
|
|
||||||
@@ -63,7 +141,44 @@ Numbers can be integers (GO int64) or float (GO float64). In mixed operations in
|
|||||||
|
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== String
|
=== Fractions
|
||||||
|
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
|
||||||
|
|
||||||
|
.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]`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]_Wrong 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.
|
||||||
|
|
||||||
|
`>>>` [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]`"`. Example: [blue]`"I'm a string"`.
|
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
|
||||||
|
|
||||||
Some arithmetic operators can also be used with strings.
|
Some arithmetic operators can also be used with strings.
|
||||||
@@ -79,6 +194,25 @@ 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.
|
||||||
|
|
||||||
|
.Item access syntax
|
||||||
|
[source,bnf]
|
||||||
|
----
|
||||||
|
<item> ::= <string-expr>"."<index-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` +
|
||||||
|
|
||||||
=== Boolean
|
=== Boolean
|
||||||
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
|
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
|
||||||
@@ -131,7 +265,7 @@ 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.
|
||||||
|
|
||||||
.List examples
|
.List examples
|
||||||
@@ -144,7 +278,6 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
|
|||||||
[ [1,"one"], [2,"two"]] // List of lists
|
[ [1,"one"], [2,"two"]] // List of lists
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
.List operators
|
.List operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
|===
|
|===
|
||||||
@@ -155,8 +288,47 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
|
|||||||
| [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] ]_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
The items of array can be accessed using the dot `.` operator.
|
||||||
|
|
||||||
|
.Item access syntax
|
||||||
|
[source,bnf]
|
||||||
|
----
|
||||||
|
<item> ::= <list-expr>"."<index-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
|
||||||
|
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.
|
||||||
|
|
||||||
|
.Dictionary examples
|
||||||
|
[source,go]
|
||||||
|
----
|
||||||
|
{1:"one", 2:"two"}
|
||||||
|
{"one":1, "two": 2}
|
||||||
|
{"sum":1+2+3, "prod":1*2*3}
|
||||||
|
----
|
||||||
|
|
||||||
|
WARNING: Support for dictionaries is still ongoing.
|
||||||
|
|
||||||
== Variables
|
== Variables
|
||||||
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
|
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_.
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
[source,go]
|
||||||
@@ -182,7 +354,7 @@ a=1; b=2; c=3; a+b+c // returns 6
|
|||||||
----
|
----
|
||||||
|
|
||||||
=== [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]`)`.
|
||||||
|
|
||||||
@@ -210,23 +382,30 @@ The _selector operator_ is very similar to the _switch/case/default_ statement a
|
|||||||
<multi-expression> ::= <expression> {";" <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 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.
|
||||||
|
|
||||||
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 `:` 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.
|
||||||
|
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
[source,go]
|
||||||
----
|
----
|
||||||
1 ? {"a"} : {"b"} // returns "b"
|
`>>>` [blue]`1 ? {"a"} : {"b"}`
|
||||||
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
|
[green]`b`
|
||||||
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
|
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}`
|
||||||
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
|
[green]`c'
|
||||||
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
|
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`
|
||||||
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
|
[green]`b`
|
||||||
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
|
`>>>` [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`
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
== Priorities of operators
|
== Priorities of operators
|
||||||
|
|||||||
+1526
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
+6
-8
@@ -21,13 +21,8 @@ func TestExpr(t *testing.T) {
|
|||||||
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
||||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
/* 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},
|
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||||
}
|
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
||||||
|
/* 10 */ {`
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
inputs1 := []inputType{
|
|
||||||
/* 1 */ {`
|
|
||||||
ds={
|
ds={
|
||||||
"init":func(end){@end=end; @current=0 but true},
|
"init":func(end){@end=end; @current=0 but true},
|
||||||
"current":func(){current},
|
"current":func(){current},
|
||||||
@@ -41,7 +36,10 @@ func TestExpr(t *testing.T) {
|
|||||||
`, int64(1), nil},
|
`, int64(1), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, input := range inputs1 {
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
var expr Expr
|
var expr Expr
|
||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+124
-4
@@ -18,8 +18,47 @@ func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
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 +78,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 +158,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
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -102,13 +103,105 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
if len(args) < 1 {
|
||||||
|
return result, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
for i, targetSpec := range args[1:] {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasPrefix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
if len(args) < 1 {
|
||||||
|
return result, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
for i, targetSpec := range args[1:] {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasSuffix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source, sep string
|
||||||
|
var count int = -1
|
||||||
|
var parts []string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
return result, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
if len(args) >= 2 {
|
||||||
|
if sep, ok = args[1].(string); !ok {
|
||||||
|
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
|
||||||
|
}
|
||||||
|
if len(args) >= 3 {
|
||||||
|
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
|
||||||
|
count = int(count64)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
parts = strings.SplitN(source, sep, count)
|
||||||
|
} else if count < 0 {
|
||||||
|
parts = strings.Split(source, sep)
|
||||||
|
} else {
|
||||||
|
parts = []string{}
|
||||||
|
}
|
||||||
|
list := make(ListType, len(parts))
|
||||||
|
for i, part := range parts {
|
||||||
|
list[i] = part
|
||||||
|
}
|
||||||
|
result = &list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// --- End of function definitions
|
// --- End of function definitions
|
||||||
|
|
||||||
// Import above functions in the context
|
// Import above functions in the context
|
||||||
func ImportStringFuncs(ctx ExprContext) {
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
||||||
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
|
||||||
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
|
||||||
|
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the import function in the import-register.
|
// Register the import function in the import-register.
|
||||||
|
|||||||
+34
-2
@@ -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},
|
||||||
@@ -38,8 +38,40 @@ func TestFuncs(t *testing.T) {
|
|||||||
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||||
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
|
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
|
||||||
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
|
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
|
||||||
|
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
|
||||||
|
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
|
||||||
|
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
|
||||||
|
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
|
||||||
|
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
|
||||||
|
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
|
||||||
|
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
|
||||||
|
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
|
||||||
|
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
|
||||||
|
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
|
||||||
|
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
|
||||||
|
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||||
|
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
||||||
|
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, 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`)},
|
||||||
|
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
|
||||||
|
/* 45 */ {`isInt(2+1)`, true, 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},
|
||||||
}
|
}
|
||||||
|
|
||||||
// parserTest(t, "Func", inputs[25:26])
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
//parserTest(t, "Func", inputs[54:55])
|
||||||
parserTest(t, "Func", inputs)
|
parserTest(t, "Func", inputs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -33,6 +33,13 @@ func TestListParser(t *testing.T) {
|
|||||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
/* 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},
|
/* 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},
|
/* 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},
|
||||||
|
/* 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},
|
||||||
|
|
||||||
// /* 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},
|
||||||
|
|||||||
+23
-24
@@ -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 {
|
||||||
|
|||||||
+9
-2
@@ -23,13 +23,20 @@ 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 = 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 = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
|
||||||
}
|
}
|
||||||
if info.MaxArgs() >= 0 && 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 = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
|
||||||
}
|
}
|
||||||
|
if err == nil && owner != ctx {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ func (ls *ListType) String() string {
|
|||||||
return ls.ToString(0)
|
return ls.ToString(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------- list term
|
// -------- list term
|
||||||
func newListTermA(args ...*term) *term {
|
func newListTermA(args ...*term) *term {
|
||||||
return newListTerm(args)
|
return newListTerm(args)
|
||||||
|
|||||||
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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++ {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package expr
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -24,6 +25,134 @@ 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:]
|
||||||
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
+13
-9
@@ -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,11 @@ 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 {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -82,15 +86,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 +106,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+15
-73
@@ -11,6 +11,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type inputType struct {
|
||||||
|
source string
|
||||||
|
wantResult any
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
func TestGeneralParser(t *testing.T) {
|
func TestGeneralParser(t *testing.T) {
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
||||||
@@ -29,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},
|
||||||
@@ -150,68 +156,12 @@ func TestGeneralParser(t *testing.T) {
|
|||||||
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||||
/* 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},
|
||||||
check_env_expr_path := 113
|
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr Expr
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
// ctx.SetVar("var1", int64(123))
|
|
||||||
// ctx.SetVar("var2", "abc")
|
|
||||||
// ImportMathFuncs(ctx)
|
|
||||||
// ImportImportFuncs(ctx)
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, "General", input.source, input.wantResult, input.wantErr)
|
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
|
||||||
|
|
||||||
good := true
|
|
||||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
|
||||||
gotResult, gotErr = expr.Eval(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
parserTest(t, "General", inputs)
|
||||||
if !eq /*gotResult != input.wantResult*/ {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotErr != input.wantErr {
|
|
||||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
|
||||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if good {
|
|
||||||
succeeded++
|
|
||||||
} else {
|
|
||||||
failed++
|
|
||||||
if i+1 == check_env_expr_path {
|
|
||||||
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parserTest(t *testing.T, section string, inputs []inputType) {
|
func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||||
@@ -219,23 +169,15 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
|||||||
succeeded := 0
|
succeeded := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
var expr Expr
|
var expr Expr
|
||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleFuncStore()
|
||||||
// ctx.SetVar("var1", int64(123))
|
|
||||||
// ctx.SetVar("var2", "abc")
|
|
||||||
// ImportMathFuncs(ctx)
|
|
||||||
// ImportImportFuncs(ctx)
|
|
||||||
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())
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user