Compare commits

..

54 Commits

Author SHA1 Message Date
camoroso 037565c41e New var() function added to the builtin set 2026-04-15 16:04:12 +02:00
camoroso f8d12b1a93 added TestGoFunction() to test Go functions 2026-04-15 16:03:41 +02:00
camoroso 518d4d8d65 simple-store.go: added function Init() 2026-01-03 09:11:43 +01:00
camoroso d64602cb00 builtin-string.go: fix return type of strLower() and strUpper() 2025-11-15 06:18:38 +01:00
camoroso 4709248828 string builtin: strUpper() and strLower() added 2025-11-13 20:53:07 +01:00
camoroso 5ecf81412e Doc: pre & post incremente/decrement 2025-01-05 12:53:50 +01:00
camoroso ff4db34f7b t_operator_test.go: test on -- and ++ prefix operators 2025-01-05 12:49:36 +01:00
camoroso 0f848071c2 New prefix operators ++ and -- 2025-01-04 18:12:38 +01:00
camoroso 6b3351b324 new prefix operator "!" as logic NOT 2025-01-04 17:47:59 +01:00
camoroso 760c1ee6da New bitwise XOR operator specified by symbol ^ (caret). Iterator dereference is now done by prefixed * (star) 2025-01-03 07:33:17 +01:00
camoroso 5ab6876ea1 term.go: new priority priDereference 2025-01-03 07:28:30 +01:00
camoroso 6268abda8c token.go: new member function Token.SetSymbol() 2025-01-03 07:27:49 +01:00
camoroso d25bd325b7 symbol-map.go: improved detection of incomplete operations 2025-01-03 06:32:55 +01:00
camoroso 01c04feea5 Doc: bitwise operators in the main operator table and special assignment operators table 2025-01-03 05:43:50 +01:00
camoroso 6fc689c46c Added a test to the context type 2025-01-03 05:40:24 +01:00
camoroso eccb0c4dc9 Added new special operators like &= and <<=.
Also made a litle change to scanner function moveOn(): now it moves on
the last char passed and only if there are more than one chars.
2024-12-29 19:26:02 +01:00
camoroso e43823740f Doc: added string splitter operator '/' 2024-12-28 19:22:26 +01:00
camoroso 526982a564 new string operator '/' 2024-12-28 19:16:45 +01:00
camoroso 252514943e parser.go: fix currentItem after parse a subexpr 2024-12-28 19:16:03 +01:00
camoroso 32686fac62 term.go: new member function errDivisionByZero() 2024-12-28 19:14:34 +01:00
camoroso 52a627c747 Edited according to preious commit 2024-12-28 09:28:12 +01:00
camoroso d91e7eb979 The list operator '<<' (append) and '>>' (prepend) have been replaced
with the new operators '<+' and '+>' respectively.
'<<' and '>>' are now used only for left and right binary shit
respectively. Further, their priority has been increased moving them to
a higher priority than that of the assignment operator.
2024-12-28 09:17:27 +01:00
camoroso cca3b76baa solved a number of problems highlighted by the syntax analyzer 2024-12-27 07:46:11 +01:00
camoroso 24e31997fc context-helpers.go: export functions no longer export control flags 2024-12-27 07:22:28 +01:00
camoroso 646710e180 builtin-base.go: new eval() function 2024-12-27 07:14:26 +01:00
camoroso b38327b841 t_builtin-string_test.go: corrected an undefinite article 2024-12-27 07:14:01 +01:00
camoroso fd912b2eb1 common-errors.go: undefinte article selection in error messages 2024-12-27 07:13:03 +01:00
camoroso 0e55f83d56 Forced the exlamation mark as a postfix operator 2024-12-26 08:57:14 +01:00
camoroso 4725145d1c Doc: changed fraction symbol and introduced binary operators 2024-12-25 07:43:06 +01:00
camoroso edf8818f51 New dedicated priority for binary operators between relational and sum ones 2024-12-25 07:41:08 +01:00
camoroso 6211be8a8f Completed transition of the symbol '|' from fraction to operator binary or. New fraction symbol is ':'.
Also, fixed and improved some parsing sections concerning collection indeces and ranges
2024-12-23 06:59:39 +01:00
camoroso f50ddf48db operator-range.go: range-term registered with symbol SymRange 2024-12-23 06:55:57 +01:00
camoroso 76e01f12d2 term.go: two error messages corrected 2024-12-23 06:53:37 +01:00
camoroso 406bced450 operator-sum.go: sum of two fraction fixed 2024-12-23 06:52:10 +01:00
camoroso 409dc86a92 symbol.go: SymRange added 2024-12-23 06:50:02 +01:00
camoroso 4184221428 symbol-map.go: changed symbol classification of some symbols like quotes and post-op 2024-12-23 06:49:17 +01:00
camoroso 8cf8b36a26 t_parser_test.go: replaced ~ with NOT 2024-12-19 15:36:16 +01:00
camoroso de87050188 scanner.go: removed SymTilde from DefaultTranslatios() -> It is not an alias for the SymNot symbol any more 2024-12-19 15:30:29 +01:00
camoroso a1ec0cc611 All assignment operators set the firstToken flag 2024-12-19 15:27:38 +01:00
camoroso 8e5550bfa7 New operator %= 2024-12-19 15:14:30 +01:00
camoroso 6ee21e10af New, more flexible, parser context datatype that includes and extends
the previous flags allowForest and allowVarRef.
Added binary operator (work in progress).
Better implementation of +=,-=,*=, and /= (new) operators.
2024-12-19 14:48:27 +01:00
camoroso 5c44532790 << && >>: left and right shift with integer operands 2024-12-07 07:06:08 +01:00
camoroso cb66c1ab19 symbol-map.go: removed unsed definitons 2024-10-13 08:44:21 +02:00
camoroso a28d24ba68 parser.go: improved terminal symbols thanks to new symbol-map.go functions 2024-10-13 08:42:55 +02:00
camoroso 523349a204 symbol-map.go: new file that helps to identify symbols by source and class 2024-10-13 08:41:30 +02:00
camoroso b185f1df3a token.go: added a few error functions 2024-10-13 08:39:56 +02:00
camoroso 5da5a61a42 Expr.doc: notes about function context 2024-10-05 05:30:22 +02:00
camoroso 6e9205abc4 t_funcs_test.go: A test add on parameters check about two params with the same name 2024-10-05 05:25:29 +02:00
camoroso f61004fb5d A test added on new implicit boolean cases in selector operator 2024-10-05 05:23:55 +02:00
camoroso 321030c8d3 parser.go: function parameter list can't specify same parameter more than once 2024-10-01 06:39:51 +02:00
camoroso 98fc89e84f operator-selector.go: Simplified selector for Boolean expressions 2024-10-01 06:37:35 +02:00
camoroso 778d00677d Doc: closure example 2024-09-18 20:48:12 +02:00
camoroso ba3dbb7f02 Doc: continuation 2024-09-16 06:52:29 +02:00
camoroso 7285109115 parser.go: number sign is now allowed after the assign operator 2024-09-16 06:49:26 +02:00
47 changed files with 2193 additions and 603 deletions
+53
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"math"
"strconv"
"strings"
)
const (
@@ -188,6 +189,49 @@ func fractFunc(ctx ExprContext, name string, args map[string]any) (result any, e
// return
// }
func evalFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
var expr Expr
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStore()
}
r := strings.NewReader(source)
scanner := NewScanner(r, DefaultTranslations())
if expr, err = parser.Parse(scanner); err == nil {
CtrlEnable(ctx, control_export_all)
result, err = expr.Eval(ctx)
}
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
func varFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[ParamName].(string); !ok {
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
}
if result, ok = args[ParamValue]; ok && result != nil {
ctx.GetParent().UnsafeSetVar(varName, result)
// } else {
// err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
// }
} else if result, ok = ctx.GetVar(varName); !ok {
err = ErrUnknownVar(name, varName)
}
return
}
//// import
func ImportBuiltinsFuncs(ctx ExprContext) {
anyParams := []ExprFuncParam{
NewFuncParam(ParamValue),
@@ -211,6 +255,15 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
NewFuncParam(ParamValue),
NewFuncParamFlagDef(ParamDenominator, PfDefault, int64(1)),
})
ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("var", NewGolangFunctor(varFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParamFlagDef(ParamValue, PfDefault, nil),
})
}
func init() {
+1 -1
View File
@@ -44,7 +44,7 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser()
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, SymEos); err == nil {
result, err = expr.Eval(ctx)
}
if err != nil {
+26
View File
@@ -200,6 +200,24 @@ func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any
return
}
func upperStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
result = strings.ToUpper(source)
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
func lowerStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
result = strings.ToLower(source)
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
// --- End of function definitions
// Import above functions in the context
@@ -236,6 +254,14 @@ func ImportStringFuncs(ctx ExprContext) {
NewFuncParam(ParamSuffix),
NewFuncParamFlag(strParamOther, PfRepeat),
})
ctx.RegisterFunc("strUpper", NewGolangFunctor(upperStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("strLower", NewGolangFunctor(lowerStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
}
// Register the import function in the import-register.
+22 -1
View File
@@ -55,14 +55,35 @@ func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
}
func undefArticle(s string) (article string) {
if len(s) > 0 && strings.Contains("aeiou", s[0:1]) {
article = "an"
} else {
article = "a"
}
return
}
func prependUndefArticle(s string) (result string) {
return undefArticle(s) + " " + s
}
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
var artWantType, artGotType string
gotType := TypeName(paramValue)
artGotType = prependUndefArticle(gotType)
artWantType = prependUndefArticle(paramType)
return fmt.Errorf("%s(): the %q parameter must be %s, got %s (%v)", funcName, paramName, artWantType, artGotType, paramValue)
}
func ErrUnknownParam(funcName, paramName string) error {
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
}
func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
}
// --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
+1 -1
View File
@@ -29,7 +29,7 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := CtrlIsEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return (exportAll || name[0] == '@') && !(name[0] == '_') }) {
// fmt.Printf("\tExporting %q\n", refName)
refValue, _ := sourceCtx.GetVar(refName)
exportVar(destCtx, refName, refValue)
+1 -1
View File
@@ -33,7 +33,7 @@ func NewDict(dictAny map[any]any) (dict *DictType) {
}
func newDict(dictAny map[any]*term) (dict *DictType) {
// TODO Change wi a call to NewDict()
// TODO Change with a call to NewDict()
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
+293 -82
View File
@@ -58,7 +58,7 @@ The expression context is analogous to the stack-frame of other programming lang
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the calling program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
@@ -79,20 +79,20 @@ Here are some examples of execution.
# Type 'exit' or Ctrl+D to quit the program.
[user]$ ./dev-expr
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.19.0
dev-expr -- Expressions calculator v1.12.0(build 1),2024/09/14 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.26.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
output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
--- Command line options:
-b <builtin> Import builtin modules.
@@ -127,22 +127,22 @@ dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoro
9.5
>>> 0xFD + 0b1 + 0o1 <1>
255
>>> 1|2 + 2|3 <2>
7|6
>>> 1:2 + 2:3 <2>
7:6
>>> ml <3>
>>> 1|2 + 2|3
>>> 1:2 + 2:3
7
-
6
>>> 4+2 but 5|2+0.5 <4>
>>> 4+2 but 5:2+0.5 <4>
3
>>> 4+2; 5|2+0.5 <5>
>>> 4+2; 5:2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = _hexadecimal_, 0o = _octal_, 0b = _binary_.
<2> Fractions: _numerator_ | _denominator_.
<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.
@@ -155,9 +155,9 @@ _Expr_ supports three type of numbers:
. [blue]#Integers#
. [blue]#Floats#
. [blue]#Factions#
. [blue]#Fractions#
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type is performed.
==== Integers
__Expr__'s integers are a subset of the integer set. Internally they are stored as Golang _int64_ values.
@@ -183,11 +183,11 @@ Value range: *-9223372036854775808* to *9223372036854775807*
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [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
| [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]`/` | _Integer division_ | Divide the left value by the right one^(*)^ | [blue]`-11 / 2` -> _-5_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> _1_
|===
^(*)^ See also the _float division_ [blue]`./` below.
@@ -228,19 +228,19 @@ _dec-seq_ = _see-integer-literal-syntax_
[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
| [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]`/` | _Float division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
| [blue]`./`| _Forced 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 `|`.
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a colon character `:`.
.Fraction literal syntax
====
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
*_fraction_* = [__sign__] (_num-den-spec_ "**:**" _float-spec_) +
_sign_ = "**+**" | "**-**" +
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
@@ -249,44 +249,44 @@ _digit-seq_ = _see-integer-literal-syntax_
====
.Examples
`>>>` [blue]`1 | 2` +
[green]`1|2`
`>>>` [blue]`1 : 2` +
[green]`1:2`
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
[green]`2|3`
`>>>` [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]`7:6`
`>>>` [blue]`1|2 * 2|3` +
[green]`1|3`
`>>>` [blue]`1:2 * 2:3` +
[green]`1:3`
`>>>` [blue]`1|2 / 1|3` +
[green]`3|2`
`>>>` [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` +
[green]`-1:2`
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_
`>>>` [blue]`1:-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator ":" requires two non-nil operands, got 1_
`>>>` [blue]`1|(-2)` +
[green]`-1|2`
`>>>` [blue]`1:(-2)` +
[green]`-1:2`
Fractions can be used together with integers and floats in expressions.
.Examples
`>>>` [blue]`1|2 + 5` +
[green]`11|2`
`>>>` [blue]`1:2 + 5` +
[green]`11:2`
`>>>` [blue]`4 - 1|2` +
[green]`7|2`
`>>>` [blue]`4 - 1:2` +
[green]`7:2`
`>>>` [blue]`1.0 + 1|2` +
`>>>` [blue]`1.0 + 1:2` +
[green]`1.5`
@@ -308,7 +308,7 @@ Strings are character sequences enclosed between two double quote [blue]`"`.
`>>>` [blue]`"123\tabc"` +
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
Some arithmetic operators can also be used with strings.
Some arithmetic operators also apply to strings.
.String operators
[cols="^1,^2,6,4"]
@@ -439,11 +439,12 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value_} "**]**" +
| [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]`+>` | _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]`[]` | _Item at index_ | 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_
| [blue]`#` | _Size_ | Number of items in a list | [blue]`#[1,2,3]` -> _3_
|===
Array's items can be accessed using the index `[]` operator.
@@ -458,10 +459,16 @@ Array's items can be accessed using the index `[]` operator.
*_slice_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
====
.Items of list
.Examples: Getting items from lists
`>>>` [blue]`[1,2,3][1]` +
[green]`2`
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
[green]`c`
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
[green]`["c", "d"]`
`>>>` [blue]`list=[1,2,3]; list[1]` +
[green]`2`
@@ -471,25 +478,44 @@ Array's items can be accessed using the index `[]` operator.
`>>>` [blue]`list=["one","two","three"]; list[2-1]` +
[green]`two`
`>>>` [blue]`list[1]="six"; list` +
[green]`["one", "six", "three"]`
`>>>` [blue]`list[-1]` +
[green]`three`
`>>>` [blue]`list[10]` +
[red]`Eval Error: [1:9] index 10 out of bounds`
.Example: Number of elements in a list
`>>>` [blue]`#list` +
[green]`3`
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
[green]`c`
.Examples: Element insertion
`>>>` [blue]`"first" >> list` +
[green]`["first", "one", "six", "three"]`
`>>>` [blue]`list << "last"` +
[green]`["first", "one", "six", "three", "last"]`
.Examples: Element in list
`>>>` [blue]`"six" in list` +
[green]`true`
`>>>` [blue]`"ten" in list` +
[green]`false`
.Examples: Concatenation and filtering
`>>>` [blue]`[1,2,3] + ["one", "two", "three"]` +
[green]`[1, 2, 3, "one", "two", "three"]`
`>>>` [blue]`[1,2,3,4] - [2,4]` +
[green]`[1, 3]`
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
[green]`["c", "d"]`
=== Dictionaries
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
The _dictionary_, or _dict_, data-type represents sets of pairs _key/value_. It is also known as _map_ or _associative array_.
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
@@ -515,6 +541,7 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
| [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_
| [blue]`#` | _Size_ | Number of items in a dict | [blue]`#{1:"a",2:"b",3:"c"}` -> _3_
|===
.Examples
@@ -533,6 +560,9 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
`>>>` [blue]`d={"one":1, "two":2}; d["six"]=6; d` +
[green]`{"two": 2, "one": 1, "six": 6}`
`>>>` [blue]`#d` +
[green]`3`
== Variables
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
@@ -556,7 +586,7 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
`>>>` [blue]`a_b` +
[green]`3`
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the typical approximation error of the float data-type_ +
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value here has the typical approximation error of the float data-type_ +
[green]`31.200000000000003`
`>>>` [blue]`x = 1; y = 2*x` +
@@ -664,8 +694,8 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
[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 defined; otherwise they return the value of the right expression.
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
The left operand of first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
@@ -673,6 +703,10 @@ The [blue]`??` operator do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
The third one, [blue]`?!`, is the alternate operator. If the variable on the left size is not defined, it returns [blue]_nil_. Otherwise it returns the result of the expressione on the right side.
IMPORTANT: If the left variable is NOT defined, the right expression is not evaluated at all.
.Examples
`>>>` [blue]`var ?? (1+2)` +
[green]`3`
@@ -686,34 +720,51 @@ The [blue]`?=` assigns the calculated value of the right expression to the left
`>>>` [blue]`var` +
[green]`3`
`>>>` [blue]`x ?! 5` +
[green]`nil`
`>>>` [blue]`x=1; x ?! 5` +
[green]`5`
`>>>` [blue]`y ?! (c=5); c` +
[red]`Eval Error: undefined variable or function "c"`
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,^6"]
[cols="^3,^2,^2,^5,^6"]
|===
| Priority | Operators | Position | Operation | Operands and results
| Priority | Operator | Position | Operation | Operands and results
.2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
.2+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
.5+|*INC/DEC*| [blue]`++` | _Postfix_ | _Iterator next item_ | _iterator_ `++` -> _any_
| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Prefix_ | _Pre increment_ | `++` _integer-variable_ -> _integer_
| [blue]`--` | _Postfix_ | _Post decrement_ | _integer-variable_ `--` -> _integer_
| [blue]`--` | _Prefix_ | _Pre decrement_ | `--` _integer-variable_ -> _integer_
.3+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
.1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
| [blue]`?!` | _Infix_ | _Alternate value_| _variable_ `?!` _any-expr_ -> _any_
//.1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `!` -> _integer_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
| [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
.2+|*BIT SHIFT*| [blue]`<<` | _Infix_ | _Left-Shift_ | _integer_ `<<` _integer_ -> _integer_
| [blue]`>>` | _Infix_ | _Right-Shift_ | _integer_ `>>` _iterator_ -> _integer_
.2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
| [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
.1+|*FRACT*| [blue]`\|` | _Infix_ | _Fraction_ | _integer_ `\|` _integer_ -> _fraction_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
.1+|*FRACT*| [blue]`:` | _Infix_ | _Fraction_ | _integer_ `:` _integer_ -> _fraction_
.7+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
| [blue]`/` | _Infix_ | _Split_ | _string_ `/` _string_ -> _list_
| [blue]`/` | _Infix_ | _Split_ | _string_ `/` integer -> _list_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
@@ -721,6 +772,10 @@ The table below shows all supported operators by decreasing priorities.
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
.1+|*BITWISE NOT*| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _integer_ -> _integer_
.1+|*BITWISE AND*| [blue]`&` | _Infix_ | _Binary And_ | _integer_ `&` _integer_ -> _integer_
.2+|*BITWISE OR*| [blue]`\|` | _Infix_ | _Binary Or_ | _integer_ `\|` _integer_ -> _integer_
| [blue]`^` | _Infix_ | _Binary Xor_ | _integer_ `^` _integer_ -> _integer_
.8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
@@ -729,20 +784,54 @@ The table below shows all supported operators by decreasing priorities.
| [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_
.1+|*LOGIC NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
.2+|*LOGIC 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_
.2+|*LOGIC OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _list_
| [blue]`<<` | _Infix_ | _Back-insert_ | _list_ `<<` _any_ -> _list_
.2+|*INSERT*| [blue]`+>` | _Infix_ | _Prepend_ | _any_ `+>` _list_ -> _list_
| [blue]`<+` | _Infix_ | _Append_ | _list_ `<+` _any_ -> _list_
.2+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
4+| _See also the table of special allocation operators below_
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|===
^1^ Experimental
//^1^ Experimental
.Special assignment perators
[cols="^2,^2,^4,^6"]
|===
| Priority | Operator | Operation |Equivalent operation
.9+|*ASSIGN*| [blue]`+=` | _Sum & Assign_ | _var_ `\+=` _expr_ +
short for +
_var_ `=` _value-of-var_ `+` _expr_
| [blue]`-=` | _Subtract & Assign_ | _var_ `-=` _expr_ +
short for +
_var_ `=` _value-of-var_ `-` _expr_
| [blue]`*=` | _Multiply & Assign_ | _var_ `\*=` _expr_ +
short for +
_var_ `=` _value-of-var_ `*` _expr_
| [blue]`/=` | _Divide & Assign_ | _var_ `/=` _expr_ +
short for +
_var_ `=` _value-of-var_ `/` _expr_
| [blue]`%=` | _Remainder & Assign_ | _var_ `%=` _expr_ +
short for +
_var_ `=` _value-of-var_ `%` _expr_
| [blue]`&=` | _Bitwise and & Assign_ | _var_ `&=` _expr_ +
short for +
_var_ `=` _value-of-var_ `&` _expr_
| [blue]`\|=` | _Bitwise or & Assign_ | _var_ `\|=` _expr_ +
short for +
_var_ `=` _value-of-var_ `\|` _expr_
| [blue]`<\<=` | _Left shift & Assign_ | _var_ `<\<=` _expr_ +
short for +
_var_ `=` _value-of-var_ `<<` _expr_
| [blue]`>>=` | _Right shift & Assign_ | _var_ `>>=` _expr_ +
short for +
_var_ `=` _value-of-var_ `>>` _expr_
|===
== Functions
Functions in _Expr_ are very similar to functions available in many programming languages. Currently, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
@@ -752,28 +841,150 @@ Functions in _Expr_ are very similar to functions available in many programming
=== _Expr_ function definition
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters.
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters and passing paramters by name.
.Expr's function definition syntax
====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_param-list_] "**)**" "**{**" _multi-expression_ "**}**" +
_param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
*_function-definition_* = _identifier_ "**=**" "**func(**" [_formal-param-list_] "**)**" "**{**" _multi-expression_ "**}**" +
_formal-param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
_required-param-list_ = _identifier_ { "**,**" _identifier_ } +
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } +
_optional-param_ = _identifier_ "**=**" _any-expr_
_optional-param_ = _param-name_ "**=**" _any-expr_ +
_param-name_ = _identifier_
====
.Examples
#TODO#
`>>>` [gray]_// A simple function: it takes two parameters and returns their "sum"_**^(*)^** +
`>>>` [blue]`sum = func(a, b){ a + b }` +
[green]`sum(a, b):any{}`
^(\*)^ Since the plus, *+*, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
`>>>` [gray]_// A more complex example: recursive calculation of the n-th value of Fibonacci's sequence_ +
`>>>` [blue]`fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }` +
[green]`fib(n):any{}`
`>>>` [gray]_// Same function fib() but entered by splitting it over mulple text lines_ +
`>>>` [blue]`fib = func(n){ \` +
`\...` [blue]`{nbsp}{nbsp}n ? \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}[0] {0} : \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}[1] {1} :: \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}{ \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}fib(n-1) + fib(n-2) \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}} \` +
`\...` [blue]`}` +
[green]`fib(n):any{}`
`>>>` [gray]_// Required and optional parameters_ +
`>>>` [blue]`measure = func(value, unit="meter"){ value + " " + unit + (value > 1) ? [true] {"s"} :: {""}}` +
[green]`measure(value, unit="meter"):any{}`
=== _Golang_ function definition
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe.
Description of how to define Golang functions and how to bind them to _Expr_ are topics covered in another document that I'll write, one day, maybe.
=== Function calls
#TODO: function calls operations#
To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
.Function invocation syntax
====
*_function-call_* = _identifier_ "**(**" _actual-param-list_ "**)**" +
_actual-param-list_ = [_positional-params_] [_named-parameters_] +
_positional-params_ = _any-value_ { "*,*" _any-value_ } +
_named-params_ = _param-name_ "**=**" _any-value_ { "*,*" _param-name_ "**=**" _any-value_ } +
_param-name_ = _identifier_
====
.Examples of calling the `sum()` functions defined above
`>>>` [gray]_// sum of two integers_ +
`>>>` [blue]`sum(-6, 2)` +
[green]`-4` +
`>>>` [gray]_// same as above but passing the parameters by name_ +
`>>>` [blue]`sum(a=-6, b=2)` +
[green]`-4` +
`>>>` [gray]_// again, but swapping parameter positions (see the diff() examples below)_ +
`>>>` [blue]`sum(b=2, a=-6)` +
[green]`-4` +
`>>>` [gray]_// sum of a fraction and an integer_ +
`>>>` [blue]`sum(3|2, 2)` +
[green]`7|2` +
`>>>` [gray]_// sum of two strings_ +
`>>>` [blue]`sum("bye", "-bye")` +
[green]`"bye-bye"` +
`>>>` [gray]_// sum of two lists_ +
`>>>` [blue]`sum(["one", 1], ["two", 2])` +
[green]`["one", 1, "two", 2]`
.Examples of calling a function with parameters passed by name
`>>>` [gray]_// diff(a,b) calculates a-b_ +
`>>>` [blue]`diff = func(a,b){a-b}` +
[green]`diff(a, b):any{}` +
`>>>` [gray]_// simple invocation_ +
`>>>` [blue]`diff(10,8)` +
[green]`2` +
`>>>` [gray]_// swapped parameters passed by name_ +
`>>>` [blue]`diff(b=8,a=10)` +
[green]`2`
.Examples of calling the `fib()` function defined above
`>>>` [gray]_// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ..._ +
`>>>` [blue]`fib(6)` +
[green]`8` +
`>>>` [blue]`fib(9)` +
[green]`34`
.Examples of calling the `measure()` functions defined above
`>>>` [gray]_// simple call_ +
`>>>` [blue]`measure(10,"litre")` +
[green]`"10 litres"` +
`>>>` [gray]_// accept the default unit_ +
`>>>` [blue]`measure(8)` +
[green]`"8 meters"` +
`>>>` [gray]_// without the required parameter 'value'_ +
`>>>` [blue]`measure(unit="degrees"))` +
[red]`Eval Error: measure(): missing params -- value`
.Examples of context binding (closures)
`>>>` [blue]`factory = func(n=2){ func(x){x*n} }` +
[green]`factory(n=2):any{}` +
`>>>` [blue]`double = factory()` +
[green]`double(x):any{}` +
`>>>` [blue]`triple = factory(3)` +
[green]`triple(x):any{}` +
`>>>` [blue]`double(5)` +
[green]`10` +
`>>>` [blue]`triple(5)` +
[green]`15`
=== Function context
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and un related variables.
Clone variables are normal local variables. The only diffence will appear when the defining function terminate, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
.Example
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
[green]`f():any{}` +
`>>>` [blue]`f()` [gray]_// The multi-expression (two) in f() is calculated and the last result is returned_ +
[green]`5` +
`>>>` [blue]`x` [gray]_// The `x` variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the `@x` variable, local to f() after its termnation._ +
[green]`3`
NOTE: The clone modifier [blue]`@` does not make a variable a reference variable, as the ampersand character `&` does in languages such as C and C++.
[IMPORTANT]
====
The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too.
.Example
`>>>` [blue]`g = func(@p) {2+@p}`
g(@p):any{}`
`>>>` [blue]`g(9)`
11`
`>>>` [blue]`p
9
====
== Iterators
#TODO: function calls operations#
+506 -111
View File
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -50,9 +50,9 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
} else if s[0] == '+' {
s = s[1:]
}
// if strings.HasSuffix(s, "()") {
// s = s[0 : len(s)-2]
// }
// if strings.HasSuffix(s, "()") {
// s = s[0 : len(s)-2]
// }
s = strings.TrimSuffix(s, "()")
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
@@ -124,36 +124,38 @@ func (f *FractionType) String() string {
func (f *FractionType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
sb.WriteString(fmt.Sprintf("%d:%d", f.num, f.den))
} else {
var s, num string
var sign, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
sign = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
sNum := fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, sign+num))
sb.WriteString(sNum)
} else {
if len(s) > 0 {
if len(sign) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(s)
if len(sign) > 0 {
sb.WriteString(sign)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
if len(sign) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
sb.WriteString(sDen)
}
return sb.String()
+5 -1
View File
@@ -1,3 +1,7 @@
module git.portale-stac.it/go-pkg/expr
go 1.21.6
go 1.22.0
toolchain go1.23.3
require golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
+4
View File
@@ -0,0 +1,4 @@
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+4 -4
View File
@@ -5,7 +5,7 @@
package expr
import (
"errors"
// "errors"
"fmt"
)
@@ -43,6 +43,6 @@ func errNoOperation(name string) error {
return fmt.Errorf("no %s() function defined in the data-source", name)
}
func errInvalidDataSource() error {
return errors.New("invalid data-source")
}
// func errInvalidDataSource() error {
// return errors.New("invalid data-source")
// }
+115
View File
@@ -99,7 +99,122 @@ func evalAssign(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
//-------- assign term
func newOpAssignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalOpAssign,
}
}
func getCollectionItemValue(ctx ExprContext, collectionTerm, keyListTerm *term) (value any, err error) {
var collectionValue, keyListValue, keyValue any
var keyList *ListType
var ok bool
if collectionValue, err = collectionTerm.compute(ctx); err != nil {
return
}
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
return
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
return
}
if keyValue = (*keyList)[0]; keyValue == nil {
err = keyListTerm.Errorf("index/key is nil")
return
}
switch collection := collectionValue.(type) {
case *ListType:
if index, ok := keyValue.(int64); ok {
value = (*collection)[index]
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
}
case *DictType:
value = (*collection)[keyValue]
default:
err = collectionTerm.Errorf("collection expected")
}
return
}
func getAssignValue(ctx ExprContext, leftTerm *term) (value any, err error) {
if leftTerm.symbol() == SymIndex {
value, err = getCollectionItemValue(ctx, leftTerm.children[0], leftTerm.children[1])
} else {
value, _ = ctx.GetVar(leftTerm.source())
}
return
}
func evalOpAssign(ctx ExprContext, opTerm *term) (v any, err error) {
var rightValue, leftValue any
if err = opTerm.checkOperands(); err != nil {
return
}
leftTerm := opTerm.children[0]
leftSym := leftTerm.symbol()
if leftSym != SymVariable && leftSym != SymIndex {
err = leftTerm.tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.tk.source)
return
}
rightChild := opTerm.children[1]
if rightValue, err = rightChild.compute(ctx); err == nil {
if leftValue, err = getAssignValue(ctx, leftTerm); err == nil {
switch opTerm.symbol() {
case SymPlusEqual:
v, err = sumValues(opTerm, leftValue, rightValue)
case SymMinusEqual:
v, err = diffValues(opTerm, leftValue, rightValue)
case SymStarEqual:
v, err = mulValues(opTerm, leftValue, rightValue)
case SymSlashEqual:
v, err = divValues(opTerm, leftValue, rightValue)
case SymPercEqual:
v, err = remainderValues(opTerm, leftValue, rightValue)
case SymAmpersandEqual:
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
case SymVertBarEqual:
v, err = bitwiseOr(opTerm, leftValue, rightValue)
case SymCaretEqual:
v, err = bitwiseXor(opTerm, leftValue, rightValue)
case SymDoubleLessEqual:
v, err = bitLeftShift(opTerm, leftValue, rightValue)
case SymDoubleGreaterEqual:
v, err = bitRightShift(opTerm, leftValue, rightValue)
default:
err = opTerm.Errorf("unsupported assign operator %q", opTerm.source())
}
if err == nil {
err = assignValue(ctx, leftTerm, v)
}
}
}
return
}
// init
func init() {
registerTermConstructor(SymEqual, newAssignTerm)
registerTermConstructor(SymPlusEqual, newOpAssignTerm)
registerTermConstructor(SymMinusEqual, newOpAssignTerm)
registerTermConstructor(SymStarEqual, newOpAssignTerm)
registerTermConstructor(SymSlashEqual, newOpAssignTerm)
registerTermConstructor(SymPercEqual, newOpAssignTerm)
registerTermConstructor(SymDoubleLessEqual, newOpAssignTerm)
registerTermConstructor(SymDoubleGreaterEqual, newOpAssignTerm)
registerTermConstructor(SymAmpersandEqual, newOpAssignTerm)
registerTermConstructor(SymVertBarEqual, newOpAssignTerm)
registerTermConstructor(SymCaretEqual, newOpAssignTerm)
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-bitwise.go
package expr
//-------- Bitwise NOT term
func newBitwiseNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priBitwiseNot,
evalFunc: evalBitwiseNot,
}
}
func evalBitwiseNot(ctx ExprContext, opTerm *term) (v any, err error) {
var value any
if value, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(value) {
i, _ := value.(int64)
v = ^i
} else {
err = opTerm.errIncompatibleType(value)
}
return
}
//-------- Bitwise AND term
func newBitwiseAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBitwiseAnd,
evalFunc: evalBitwiseAnd,
}
}
func bitwiseAnd(opTerm *term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt & rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseAnd(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
return
}
//-------- Bitwise OR term
func newBitwiseOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBitwiseOr,
evalFunc: evalBitwiseOr,
}
}
func bitwiseOr(opTerm *term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt | rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseOr(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitwiseOr(opTerm, leftValue, rightValue)
return
}
//-------- Bitwise XOR term
func newBitwiseXorTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBitwiseOr,
evalFunc: evalBitwiseXor,
}
}
func bitwiseXor(opTerm *term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt ^ rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseXor(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitwiseXor(opTerm, leftValue, rightValue)
return
}
// init
func init() {
registerTermConstructor(SymTilde, newBitwiseNotTerm)
registerTermConstructor(SymAmpersand, newBitwiseAndTerm)
registerTermConstructor(SymVertBar, newBitwiseOrTerm)
registerTermConstructor(SymCaret, newBitwiseXorTerm)
}
+1 -1
View File
@@ -20,7 +20,7 @@ func evalContextValue(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
var sourceCtx ExprContext
if opTerm.children == nil || len(opTerm.children) == 0 {
if len(opTerm.children) == 0 {
sourceCtx = ctx
} else if opTerm.children[0].symbol() == SymVariable && opTerm.children[0].source() == "global" {
sourceCtx = globalCtx
+13 -9
View File
@@ -7,7 +7,6 @@ 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"
)
@@ -41,7 +40,7 @@ func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if den == 0 {
err = errors.New("division by zero")
err = opTerm.errDivisionByZero()
return
}
@@ -49,18 +48,23 @@ func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
den = -den
num = -num
}
g := gcd(num, den)
num = num / g
den = den / g
if den == 1 {
v = num
if num != 0 {
g := gcd(num, den)
num = num / g
den = den / g
if den == 1 {
v = num
} else {
v = &FractionType{num, den}
}
} else {
v = &FractionType{num, den}
v = &FractionType{0, den}
}
return
}
// init
func init() {
registerTermConstructor(SymVertBar, newFractionTerm)
// registerTermConstructor(SymVertBar, newFractionTerm)
registerTermConstructor(SymColon, newFractionTerm)
}
+3
View File
@@ -113,6 +113,9 @@ func evalIndex(ctx ExprContext, opTerm *term) (v any, err error) {
} else if IsDict(leftValue) {
d := leftValue.(*DictType)
v, err = getDictItem(d, indexTerm, indexList, rightValue)
} else {
rightChild := opTerm.children[1]
err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
}
return
}
+8 -8
View File
@@ -4,15 +4,15 @@
// operator-insert.go
package expr
//-------- insert term
//-------- prepend term
func newInsertTerm(tk *Token) (inst *term) {
func newPrependTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalInsert,
priority: priInsert,
evalFunc: evalPrepend,
}
}
@@ -21,12 +21,12 @@ func newAppendTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
priority: priInsert,
evalFunc: evalAppend,
}
}
func evalInsert(ctx ExprContext, opTerm *term) (v any, err error) {
func evalPrepend(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
@@ -86,6 +86,6 @@ func evalAppend(ctx ExprContext, opTerm *term) (v any, err error) {
// init
func init() {
registerTermConstructor(SymInsert, newInsertTerm)
registerTermConstructor(SymAppend, newAppendTerm)
registerTermConstructor(SymPlusGreater, newPrependTerm)
registerTermConstructor(SymLessPlus, newAppendTerm)
}
+3 -3
View File
@@ -11,7 +11,7 @@ func newIterValueTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIterValue,
priority: priDereference,
evalFunc: evalIterValue,
}
}
@@ -33,6 +33,6 @@ func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
// init
func init() {
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymDereference, newIterValueTerm)
}
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
// operator-post-inc-dec.go
package expr
// -------- post increment term
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-pre-inc-dec.go
package expr
// -------- pre increment term
func newPreIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalPreInc,
}
}
func evalPreInc(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
i := childValue.(int64) + 1
ctx.SetVar(opTerm.children[0].source(), i)
v = i
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// -------- pre decrement term
func newPreDecTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalPreDec,
}
}
func evalPreDec(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
i := childValue.(int64) - 1
ctx.SetVar(opTerm.children[0].source(), i)
v = i
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymPreInc, newPreIncTerm)
registerTermConstructor(SymPreDec, newPreDecTerm)
}
+81 -46
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"strings"
)
@@ -13,7 +12,7 @@ import (
func newMultiplyTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
@@ -21,13 +20,7 @@ func newMultiplyTerm(tk *Token) (inst *term) {
}
}
func evalMultiply(ctx ExprContext, prodTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = prodTerm.evalInfix(ctx); err != nil {
return
}
func mulValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsString(leftValue) && IsInteger(rightValue) {
s, _ := leftValue.(string)
n, _ := rightValue.(int64)
@@ -43,16 +36,26 @@ func evalMultiply(ctx ExprContext, prodTerm *term) (v any, err error) {
v = leftInt * rightInt
}
} else {
err = prodTerm.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalMultiply(ctx ExprContext, prodTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = prodTerm.evalInfix(ctx); err != nil {
return
}
return mulValues(prodTerm, leftValue, rightValue)
}
//-------- divide term
func newDivideTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
@@ -60,6 +63,56 @@ func newDivideTerm(tk *Token) (inst *term) {
}
}
func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = opTerm.errDivisionByZero()
} else {
v = numAsFloat(leftValue) / d
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = opTerm.errDivisionByZero()
} else {
v = leftInt / rightInt
}
}
} else if IsString(leftValue) && IsString(rightValue) {
source := leftValue.(string)
sep := rightValue.(string)
v = ListFromStrings(strings.Split(source, sep))
} else if IsString(leftValue) && IsInteger(rightValue) {
source := leftValue.(string)
partSize := int(rightValue.(int64))
if partSize == 0 {
err = opTerm.errDivisionByZero()
} else {
partCount := len(source) / partSize
remainder := len(source) % partSize
listSize := partCount
if remainder > 0 {
listSize++
}
parts := make([]any, 0, listSize)
for i := 0; i < partCount; i++ {
parts = append(parts, source[i*partSize:(i+1)*partSize])
}
if remainder > 0 {
parts = append(parts, source[len(source)-remainder:])
}
v = newList(parts)
}
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
@@ -67,28 +120,7 @@ func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
} else {
v = numAsFloat(leftValue) / d
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = errors.New("division by zero")
} else {
v = leftInt / rightInt
}
}
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
return divValues(opTerm, leftValue, rightValue)
}
//-------- divide as float term
@@ -113,7 +145,7 @@ func evalDivideAsFloat(ctx ExprContext, floatDivTerm *term) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
err = floatDivTerm.errDivisionByZero()
} else {
v = numAsFloat(leftValue) / d
}
@@ -127,35 +159,38 @@ func evalDivideAsFloat(ctx ExprContext, floatDivTerm *term) (v any, err error) {
func newRemainderTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalReminder,
evalFunc: evalRemainder,
}
}
func evalReminder(ctx ExprContext, ramainderTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = ramainderTerm.evalInfix(ctx); err != nil {
return
}
func remainderValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
err = errors.New("division by zero")
err = opTerm.errDivisionByZero()
} else {
leftInt, _ := leftValue.(int64)
v = leftInt % rightInt
}
} else {
err = ramainderTerm.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalRemainder(ctx ExprContext, remainderTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = remainderTerm.evalInfix(ctx); err != nil {
return
}
return remainderValues(remainderTerm, leftValue, rightValue)
}
// init
func init() {
registerTermConstructor(SymStar, newMultiplyTerm)
+18 -5
View File
@@ -34,12 +34,16 @@ func newRangeTerm(tk *Token) (inst *term) {
}
}
func changeColonToRange(t *term) {
if t.tk.IsSymbol(SymColon) {
t.tk.Sym = SymRange
t.evalFunc = evalRange
}
}
func evalRange(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
// if err = self.checkOperands(); err != nil {
// return
// }
if len(opTerm.children) == 0 {
leftValue = int64(0)
rightValue = int64(-1)
@@ -52,7 +56,8 @@ func evalRange(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
// err = opTerm.errIncompatibleTypes(leftValue, rightValue)
err = errRangeInvalidSpecification(opTerm)
return
}
@@ -63,7 +68,15 @@ func evalRange(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
func errRangeInvalidSpecification(t *term) error {
return t.Errorf("invalid range specification")
}
func errRangeUnexpectedExpression(t *term) error {
return t.Errorf("unexpected range expression")
}
// init
func init() {
registerTermConstructor(SymColon, newRangeTerm)
registerTermConstructor(SymRange, newRangeTerm)
}
+13 -3
View File
@@ -22,9 +22,19 @@ func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (ma
selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true
if len(filterList) == 0 {
var valueAsInt = int64(0)
if b, ok := exprValue.(bool); ok {
if !b {
valueAsInt = 1
}
} else if valueAsInt, ok = exprValue.(int64); !ok {
return
}
if valueAsInt == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true
}
} else {
var caseValue any
for _, caseTerm := range filterList {
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-shift.go
package expr
//-------- bit right shift term
func newRightShiftTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinShift,
evalFunc: evalRightShift,
}
}
func bitRightShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt >> rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalRightShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitRightShift(opTerm, leftValue, rightValue)
return
}
func newLeftShiftTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinShift,
evalFunc: evalLeftShift,
}
}
func bitLeftShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt << rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalLeftShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitLeftShift(opTerm, leftValue, rightValue)
return
}
// init
func init() {
registerTermConstructor(SymDoubleGreater, newRightShiftTerm)
registerTermConstructor(SymDoubleLess, newLeftShiftTerm)
}
+25 -15
View File
@@ -21,13 +21,7 @@ func newPlusTerm(tk *Token) (inst *term) {
}
}
func evalPlus(ctx ExprContext, plusTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = plusTerm.evalInfix(ctx); err != nil {
return
}
func sumValues(plusTerm *term, leftValue, rightValue any) (v any, err error) {
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if IsNumber(leftValue) && IsNumber(rightValue) {
@@ -59,10 +53,22 @@ func evalPlus(ctx ExprContext, plusTerm *term) (v any, err error) {
c := leftDict.clone()
c.merge(rightDict)
v = c
} else if isFraction(leftValue) && isFraction(rightValue) {
v, err = sumAnyFract(leftValue, rightValue)
} else {
err = plusTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
return v, err
}
func evalPlus(ctx ExprContext, plusTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = plusTerm.evalInfix(ctx); err != nil {
return
}
return sumValues(plusTerm, leftValue, rightValue)
}
//-------- minus term
@@ -77,13 +83,7 @@ func newMinusTerm(tk *Token) (inst *term) {
}
}
func evalMinus(ctx ExprContext, minusTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = minusTerm.evalInfix(ctx); err != nil {
return
}
func diffValues(minusTerm *term, leftValue, rightValue any) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue)
@@ -110,6 +110,16 @@ func evalMinus(ctx ExprContext, minusTerm *term) (v any, err error) {
return
}
func evalMinus(ctx ExprContext, minusTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = minusTerm.evalInfix(ctx); err != nil {
return
}
return diffValues(minusTerm, leftValue, rightValue)
}
// init
func init() {
registerTermConstructor(SymPlus, newPlusTerm)
+166 -125
View File
@@ -6,10 +6,46 @@ package expr
import (
"errors"
"golang.org/x/exp/constraints"
)
//-------- parser
type parserContext uint16
const (
parserNoFlags = 0
allowMultiExpr parserContext = 1 << iota
allowVarRef
selectorContext
listContext // squareContext for list
indexContext // squareContext for index
allowIndex // allow index in squareContext
squareContext = listContext | indexContext // Square parenthesis for list or index
)
func hasFlag[T constraints.Unsigned](set T, singleFlag T) bool {
return (set & singleFlag) != 0
}
func addFlags[T constraints.Unsigned](set T, flags T) T {
return set | flags
}
func addFlagsCond[T constraints.Unsigned](set T, flags T, cond bool) (newSet T) {
if cond {
newSet = set | flags
} else {
newSet = set
}
return
}
func remFlags[T constraints.Unsigned](set T, flags T) T {
return set & (^flags)
}
type parser struct {
}
@@ -24,13 +60,13 @@ func (parser *parser) Next(scanner *scanner) (tk *Token) {
return
}
func (parser *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
func (parser *parser) parseFuncCall(scanner *scanner, ctx parserContext, tk *Token) (tree *term, err error) {
args := make([]*term, 0, 10)
itemExpected := false
lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
if subTree, err = parser.parseItem(scanner, ctx, SymComma, SymClosedRound); err != nil {
break
}
prev := scanner.Previous()
@@ -66,12 +102,18 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
tk = parser.Next(scanner)
if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk)
if len(args) > 0 {
if pos := paramAlreadyDefined(args, param); pos > 0 {
err = tk.Errorf("parameter %q at position %d already defined at position %d", param.source(), len(args)+1, pos)
break
}
}
args = append(args, param)
tk = parser.Next(scanner)
if tk.Sym == SymEqual {
var paramExpr *ast
defaultParamsStarted = true
if paramExpr, err = parser.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
if paramExpr, err = parser.parseItem(scanner, parserNoFlags, SymComma, SymClosedRound); err != nil {
break
}
param.forceChild(paramExpr.root)
@@ -94,7 +136,7 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
if err == nil {
tk = parser.Next(scanner)
if tk.IsSymbol(SymOpenBrace) {
body, err = parser.parseGeneral(scanner, true, true, SymClosedBrace)
body, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, SymClosedBrace)
} else {
err = tk.ErrorExpectedGot("{")
}
@@ -110,27 +152,43 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
return
}
func (parser *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
func paramAlreadyDefined(args []*term, param *term) (position int) {
position = 0
for i, arg := range args {
if arg.source() == param.source() {
position = i + 1
}
}
return
}
func (parser *parser) parseList(scanner *scanner, ctx parserContext) (listTerm *term, err error) {
r, c := scanner.lastPos()
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
itemCtx := remFlags(ctx, allowIndex)
for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast
zeroRequired := scanner.current.Sym == SymColon
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
root := subTree.root
var itemTree *ast
if itemTree, err = parser.parseItem(scanner, itemCtx, SymComma, SymClosedSquare); err == nil {
root := itemTree.root
if root != nil {
if !parsingIndeces && root.symbol() == SymColon {
err = root.Errorf("unexpected range expression")
if hasFlag(ctx, allowIndex) && root.symbol() == SymColon {
changeColonToRange(root)
}
if !hasFlag(ctx, allowIndex) && root.symbol() == SymRange {
// err = root.Errorf("unexpected range expression")
err = errRangeUnexpectedExpression(root)
break
}
args = append(args, root)
if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
if hasFlag(ctx, allowIndex) && root.symbol() == SymRange && zeroRequired { //len(root.children) == 0 {
if len(root.children) == 1 {
root.children = append(root.children, root.children[0])
} else if len(root.children) > 1 {
err = root.Errorf("invalid range specification")
// err = root.Errorf("invalid range specification")
err = errRangeInvalidSpecification(root)
break
}
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
@@ -147,26 +205,28 @@ func (parser *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarR
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
if itemExpected = lastSym == SymComma; itemExpected {
remFlags(ctx, allowIndex)
}
}
if err == nil {
if lastSym != SymClosedSquare {
err = scanner.Previous().ErrorExpectedGot("]")
} else {
subtree = newListTerm(r, c, args)
listTerm = newListTerm(r, c, args)
}
}
return
}
func (parser *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
func (parser *parser) parseIterDef(scanner *scanner, ctx parserContext) (subtree *term, err error) {
tk := scanner.Previous()
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree, err = parser.parseItem(scanner, ctx, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
@@ -212,7 +272,7 @@ func (parser *parser) parseDictKey(scanner *scanner) (key any, err error) {
return
}
func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
func (parser *parser) parseDictionary(scanner *scanner, ctx parserContext) (subtree *term, err error) {
args := make(map[any]*term, 0)
lastSym := SymUnknown
itemExpected := false
@@ -229,7 +289,7 @@ func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtr
}
break
}
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
if subTree, err = parser.parseItem(scanner, ctx, SymComma, SymClosedBrace); err == nil {
if subTree.root != nil {
args[key] = subTree.root
} else /*if key != nil*/ {
@@ -248,15 +308,15 @@ func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtr
err = scanner.Previous().ErrorExpectedGot("}")
} else {
subtree = newDictTerm(args)
// subtree = newMapTerm(args)
}
}
return
}
func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
func (parser *parser) parseSelectorCase(scanner *scanner, ctx parserContext, defaultCase bool) (caseTerm *term, err error) {
var filterList *term
var caseExpr *ast
ctx = remFlags(ctx, allowIndex)
tk := parser.Next(scanner)
startRow := tk.row
startCol := tk.col
@@ -265,7 +325,7 @@ func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defa
err = tk.Errorf("case list in default clause")
return
}
if filterList, err = parser.parseList(scanner, false, allowVarRef); err != nil {
if filterList, err = parser.parseList(scanner, remFlags(ctx, allowIndex)); err != nil {
return
}
tk = parser.Next(scanner)
@@ -276,7 +336,7 @@ func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defa
}
if tk.Sym == SymOpenBrace {
if caseExpr, err = parser.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil {
if caseExpr, err = parser.parseGeneral(scanner, ctx|allowMultiExpr, SymClosedBrace); err != nil {
return
}
} else {
@@ -302,25 +362,28 @@ func addSelectorCase(selectorTerm, caseTerm *term) {
caseTerm.parent = selectorTerm
}
func (parser *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
func (parser *parser) parseSelector(scanner *scanner, tree *ast, ctx parserContext) (selectorTerm *term, err error) {
var caseTerm *term
ctx = remFlags(ctx, allowIndex)
tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken(tk); err != nil {
return
}
if caseTerm, err = parser.parseSelectorCase(scanner, allowVarRef, false); err == nil {
if caseTerm, err = parser.parseSelectorCase(scanner, ctx|allowVarRef, false); err == nil {
addSelectorCase(selectorTerm, caseTerm)
}
return
}
func (parser *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
return parser.parseGeneral(scanner, false, allowVarRef, termSymbols...)
func (parser *parser) parseItem(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
return parser.parseGeneral(scanner, ctx|allowVarRef, termSymbols...)
}
func (parser *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
return parser.parseGeneral(scanner, true, false, termSymbols...)
termSymbols = append(termSymbols, SymEos)
return parser.parseGeneral(scanner, allowMultiExpr, termSymbols...)
}
func couldBeACollection(t *term) bool {
@@ -331,15 +394,39 @@ func couldBeACollection(t *term) bool {
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
}
// func areSymbolsOutOfCtx(tk *Token, ctxTerm *term, syms ...Symbol) bool {
// var areOut = false
// if ctxTerm != nil {
// areOut = tk.IsOneOf(syms)
// }
// return areOut
// }
func listSubTree(tree *ast, listTerm *term, allowIndeces bool) (root *term, err error) {
var tk *Token
if allowIndeces {
tk = NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
root = newTerm(tk)
if err = tree.addTerm(root); err == nil {
err = tree.addTerm(listTerm)
}
} else {
root = listTerm
err = tree.addTerm(listTerm)
}
return
}
func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
func changePrefix(tk *Token) {
switch tk.Sym {
case SymMinus:
tk.SetSymbol(SymChangeSign)
case SymPlus:
tk.SetSymbol(SymUnchangeSign)
case SymStar:
tk.SetSymbol(SymDereference)
case SymExclamation:
tk.SetSymbol(SymNot)
case SymDoublePlus:
tk.SetSymbol(SymPreInc)
case SymDoubleMinus:
tk.SetSymbol(SymPreDec)
}
}
func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
var tk *Token
@@ -353,7 +440,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
// }
if tk.Sym == SymSemiColon {
if allowForest {
if hasFlag(ctx, allowMultiExpr) {
tree.ToForest()
firstToken = true
currentTerm = nil
@@ -367,57 +454,55 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
//fmt.Println("Token:", tk)
if firstToken {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else if tk.Sym == SymPlus {
tk.Sym = SymUnchangeSign
}
changePrefix(tk)
// if tk.Sym == SymMinus {
// tk.Sym = SymChangeSign
// } else if tk.Sym == SymPlus {
// tk.Sym = SymUnchangeSign
// } else if tk.IsSymbol(SymStar) {
// tk.SetSymbol(SymDereference)
// } else if tk.IsSymbol(SymExclamation) {
// tk.SetSymbol(SymNot)
// }
firstToken = false
}
switch tk.Sym {
case SymOpenRound:
var subTree *ast
if subTree, err = parser.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
subTree.root.priority = priValue
err = tree.addTerm(newExprTerm(subTree.root))
currentTerm = subTree.root
if subTree, err = parser.parseGeneral(scanner, ctx, SymClosedRound); err == nil {
exprTerm := newExprTerm(subTree.root)
err = tree.addTerm(exprTerm)
currentTerm = exprTerm
// subTree.root.priority = priValue
// err = tree.addTerm(newExprTerm(subTree.root))
// currentTerm = subTree.root
}
case SymFuncCall:
var funcCallTerm *term
if funcCallTerm, err = parser.parseFuncCall(scanner, allowVarRef, tk); err == nil {
if funcCallTerm, err = parser.parseFuncCall(scanner, ctx, tk); err == nil {
err = tree.addTerm(funcCallTerm)
currentTerm = funcCallTerm
}
case SymOpenSquare:
var listTerm *term
parsingIndeces := couldBeACollection(currentTerm)
if listTerm, err = parser.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
if parsingIndeces {
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
indexTerm := newTerm(indexTk)
if err = tree.addTerm(indexTerm); err == nil {
err = tree.addTerm(listTerm)
}
} else {
err = tree.addTerm(listTerm)
}
currentTerm = listTerm
newCtx := addFlagsCond(addFlags(ctx, squareContext), allowIndex, couldBeACollection(currentTerm))
if listTerm, err = parser.parseList(scanner, newCtx); err == nil {
currentTerm, err = listSubTree(tree, listTerm, hasFlag(newCtx, allowIndex))
}
case SymOpenBrace:
if currentTerm != nil && currentTerm.symbol() == SymColon {
err = currentTerm.Errorf(`selector-case outside of a selector context`)
} else {
var mapTerm *term
if mapTerm, err = parser.parseDictionary(scanner, allowVarRef); err == nil {
if mapTerm, err = parser.parseDictionary(scanner, ctx); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
}
case SymEqual:
// if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
case SymEqual, SymPlusEqual, SymMinusEqual, SymStarEqual, SymSlashEqual, SymPercEqual, SymAmpersandEqual, SymVertBarEqual, SymDoubleLessEqual, SymDoubleGreaterEqual, SymCaretEqual:
currentTerm, err = tree.addToken(tk)
// }
firstToken = true
case SymFuncDef:
var funcDefTerm *term
if funcDefTerm, err = parser.parseFuncDef(scanner); err == nil {
@@ -426,24 +511,25 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
}
case SymDollarRound:
var iterDefTerm *term
if iterDefTerm, err = parser.parseIterDef(scanner, allowVarRef); err == nil {
if iterDefTerm, err = parser.parseIterDef(scanner, ctx); err == nil {
err = tree.addTerm(iterDefTerm)
currentTerm = iterDefTerm
}
case SymIdentifier:
if tk.source[0] == '@' && !allowVarRef {
if tk.source[0] == '@' && !hasFlag(ctx, allowVarRef) {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
} else {
currentTerm, err = tree.addToken(tk)
}
case SymQuestion:
if selectorTerm, err = parser.parseSelector(scanner, tree, allowVarRef); err == nil {
if selectorTerm, err = parser.parseSelector(scanner, tree, ctx); err == nil {
currentTerm = selectorTerm
addFlags(ctx, selectorContext)
}
case SymColon, SymDoubleColon:
var caseTerm *term
if selectorTerm != nil {
if caseTerm, err = parser.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
if caseTerm, err = parser.parseSelectorCase(scanner, ctx, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
@@ -452,79 +538,34 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
}
} else {
currentTerm, err = tree.addToken(tk)
if tk.IsOneOfA(SymColon, SymRange) {
// Colon outside a selector term acts like a separator
firstToken = true
}
}
if tk.IsSymbol(SymColon) {
// Colon outside a selector term acts like a separator
firstToken = true
}
case SymPlusEqual, SymMinusEqual, SymStarEqual:
currentTerm, err = parser.expandOpAssign(scanner, tree, tk, allowVarRef)
default:
currentTerm, err = tree.addToken(tk)
}
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
selectorTerm = nil
remFlags(ctx, selectorContext)
}
// lastSym = tk.Sym
}
if err == nil {
err = tk.Error()
}
return
}
// func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
// if lastSym != wantedSym {
// err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
// }
// return
// }
func (parser *parser) expandOpAssign(scanner * scanner, tree *ast, tk *Token, allowVarRef bool) (t *term, err error) {
var opSym Symbol
var opString string
if tree.root != nil {
switch tk.Sym {
case SymPlusEqual:
opString = "+"
opSym = SymPlus
case SymMinusEqual:
opString = "-"
opSym = SymMinus
case SymStarEqual:
opString = "*"
opSym = SymStar
default:
err = tk.Errorf("unsopported operator %q", tk.source)
return
}
leftExpr := tree.root.Clone()
leftExpr.setParent(nil)
if t, err = tree.addToken(NewToken(tk.row, tk.col, SymEqual, "=")); err == nil {
t = leftExpr
if err = tree.addTerm(leftExpr); err == nil {
if t, err = tree.addToken(NewToken(tk.row, tk.col, opSym, opString)); err == nil {
var subTree *ast
if subTree, err = parser.parseGeneral(scanner, false, allowVarRef, SymSemiColon, SymClosedRound, SymClosedBrace, SymClosedSquare); err == nil {
if scanner.Previous().IsOneOfA(SymSemiColon, SymClosedRound, SymClosedBrace, SymClosedSquare) {
if err = scanner.UnreadToken(); err != nil {
return
}
}
subTree.root.priority = priValue
err = tree.addTerm(newExprTerm(subTree.root))
t = subTree.root
}
}
if !tk.IsOneOf(termSymbols) {
var symDesc string
if tk.IsSymbol(SymError) {
symDesc = tk.ErrorText()
} else {
symDesc = SymToString(tk.Sym)
}
err = tk.ErrorExpectedGotStringWithPrefix("expected one of", SymListToString(termSymbols, true), symDesc)
} else {
err = tk.Error()
}
} else {
err = tk.Errorf("left operand of %q must be a variable or a variable expression", tk)
}
return
}
+49 -10
View File
@@ -40,9 +40,9 @@ func DefaultTranslations() map[Symbol]Symbol {
SymKwAnd: SymAnd,
SymDoubleVertBar: SymOr,
SymKwOr: SymOr,
SymTilde: SymNot,
SymKwNot: SymNot,
SymLessGreater: SymNotEqual,
// SymTilde: SymNot,
SymKwNot: SymNot,
SymLessGreater: SymNotEqual,
}
}
@@ -124,6 +124,8 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.moveOn(SymDoublePlus, ch, next)
} else if next == '=' {
tk = scanner.moveOn(SymPlusEqual, ch, next)
} else if next == '>' {
tk = scanner.moveOn(SymPlusGreater, ch, next)
} else {
tk = scanner.makeToken(SymPlus, ch)
}
@@ -149,6 +151,8 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '*' {
scanner.readChar()
tk = scanner.fetchBlockComment()
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymSlashEqual, ch, next)
} else if next == '/' {
scanner.readChar()
tk = scanner.fetchOnLineComment()
@@ -165,13 +169,19 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
case '|':
if next, _ := scanner.peek(); next == '|' {
tk = scanner.moveOn(SymDoubleVertBar, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymVertBarEqual, ch, next)
} else {
tk = scanner.makeToken(SymVertBar, ch)
}
case ',':
tk = scanner.makeToken(SymComma, ch)
case '^':
tk = scanner.makeToken(SymCaret, ch)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymCaretEqual, ch, next)
} else {
tk = scanner.makeToken(SymCaret, ch)
}
case ':':
if next, _ := scanner.peek(); next == ':' {
tk = scanner.moveOn(SymDoubleColon, ch, next)
@@ -230,11 +240,17 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
case '&':
if next, _ := scanner.peek(); next == '&' {
tk = scanner.moveOn(SymDoubleAmpersand, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymAmpersandEqual, ch, next)
} else {
tk = scanner.makeToken(SymAmpersand, ch)
}
case '%':
tk = scanner.makeToken(SymPercent, ch)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymPercEqual, ch, next)
} else {
tk = scanner.makeToken(SymPercent, ch)
}
case '#':
tk = scanner.makeToken(SymHash, ch)
case '@':
@@ -263,9 +279,18 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' {
tk = scanner.moveOn(SymAppend, ch, next)
scanner.readChar()
next2, _ := scanner.readChar()
scanner.unreadChar()
if next2 == '=' {
tk = scanner.moveOn(SymDoubleLessEqual, ch, next, next2)
} else {
tk = scanner.accept(SymDoubleLess, ch, next)
}
} else if next == '>' {
tk = scanner.moveOn(SymLessGreater, ch, next)
} else if next == '+' {
tk = scanner.moveOn(SymLessPlus, ch, next)
} else {
tk = scanner.makeToken(SymLess, ch)
}
@@ -273,7 +298,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' {
tk = scanner.moveOn(SymInsert, ch, next)
scanner.readChar()
next2, _ := scanner.readChar()
scanner.unreadChar()
if next2 == '=' {
tk = scanner.moveOn(SymDoubleGreaterEqual, ch, next, next2)
} else {
tk = scanner.accept(SymDoubleGreater, ch, next)
}
} else {
tk = scanner.makeToken(SymGreater, ch)
}
@@ -458,7 +490,7 @@ func (scanner *scanner) parseNumber(firstCh byte) (tk *Token) {
tk = scanner.makeErrorToken(err)
} else {
var value any
err = scanner.sync(err) // TODO: Check this function
_ = scanner.sync(err) // TODO: Check this function
txt := sb.String()
if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64)
@@ -590,7 +622,7 @@ func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
}
if err != nil {
if err == io.EOF {
tk = scanner.makeErrorToken(errors.New("missing string termination \""))
tk = scanner.makeErrorToken(errors.New(string(termCh)))
} else {
tk = scanner.makeErrorToken(err)
}
@@ -628,9 +660,16 @@ func (scanner *scanner) translate(sym Symbol) Symbol {
func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
for i := 1; i < len(chars); i++ {
// for i := 1; i < len(chars); i++ {
if len(chars) > 1 {
scanner.readChar()
}
// }
return
}
func (scanner *scanner) accept(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
return
}
+5
View File
@@ -24,6 +24,11 @@ func NewSimpleStore() *SimpleStore {
return ctx
}
func (ss *SimpleStore) Init() {
ss.varStore = make(map[string]any)
ss.funcStore = make(map[string]ExprFunc)
}
func filterRefName(name string) bool { return name[0] != '@' }
//func filterPrivName(name string) bool { return name[0] != '_' }
+198
View File
@@ -0,0 +1,198 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Symbol.go
package expr
import (
"strings"
)
var symbolMap map[Symbol]symbolSpec
type symbolClass int16
const (
symClassOperator symbolClass = iota
symClassCommand
symClassIdentifier
symClassDelimiter
symClassParenthesis
symClassDeclaration
symClassValue
symClassOther
)
type symbolSpec struct {
repr string
kind symbolClass
opType termPosition
}
func init() {
symbolMap = map[Symbol]symbolSpec{
SymUnknown: {"<unknown>", symClassOther, posLeaf}, // -1: Unknown symbol
SymNone: {"<null>", symClassOther, posLeaf}, // 0: Null value for variable of type symbol
SymError: {"<error>", symClassOther, posLeaf}, // 1: Error reading from stream
SymEos: {"<eos>", symClassOther, posLeaf}, // 2: End of stream
SymMinus: {"-", symClassOperator, posInfix}, // 3: '-'
SymMinusEqual: {"-=", symClassOperator, posInfix}, // 4: '-='
SymDoubleMinus: {"--", symClassOperator, posPostfix}, // 5: '--'
SymPlus: {"+", symClassOperator, posInfix}, // 6: '+'
SymPlusEqual: {"+=", symClassOperator, posInfix}, // 7: '+='
SymDoublePlus: {"++", symClassOperator, posPostfix}, // 8: '++'
SymStar: {"*", symClassOperator, posInfix}, // 9: '*'
SymDoubleStar: {"**", symClassOperator, posInfix}, // 10: '**'
SymSlash: {"/", symClassOperator, posInfix}, // 11: '/'
SymBackSlash: {"\\", symClassOperator, posLeaf}, // 12: '\'
SymVertBar: {"|", symClassOperator, posInfix}, // 13: '|'
SymDoubleVertBar: {"||", symClassOperator, posInfix}, // 14: '||'
SymComma: {",", symClassOperator, posInfix}, // 15: ','
SymColon: {":", symClassOperator, posInfix}, // 16: ':'
SymSemiColon: {";", symClassOperator, posInfix}, // 17: ';'
SymDot: {".", symClassOperator, posInfix}, // 18: '.'
SymDotSlash: {"./", symClassOperator, posInfix}, // 19: './'
SymQuote: {"'", symClassDelimiter, posLeaf}, // 20: '\''
SymDoubleQuote: {"\"", symClassDelimiter, posLeaf}, // 21: '"'
SymBackTick: {"`", symClassDelimiter, posLeaf}, // 22: '`'
SymExclamation: {"!", symClassOperator, posPostfix}, // 23: '!'
SymQuestion: {"?", symClassOperator, posInfix}, // 24: '?'
SymAmpersand: {"&", symClassOperator, posInfix}, // 25: '&'
SymDoubleAmpersand: {"&&", symClassOperator, posInfix}, // 26: '&&'
SymPercent: {"%", symClassOperator, posInfix}, // 27: '%'
SymAt: {"@", symClassOperator, posPrefix}, // 28: '@'
SymUndescore: {"_", symClassIdentifier, posLeaf}, // 29: '_'
SymEqual: {"=", symClassOperator, posInfix}, // 30: '='
SymDoubleEqual: {"==", symClassOperator, posInfix}, // 31: '=='
SymLess: {"<", symClassOperator, posInfix}, // 32: '<'
SymLessOrEqual: {"<=", symClassOperator, posInfix}, // 33: '<='
SymGreater: {">", symClassOperator, posInfix}, // 34: '>'
SymGreaterOrEqual: {">=", symClassOperator, posInfix}, // 35: '>='
SymLessGreater: {"<>", symClassOperator, posInfix}, // 36: '<>'
SymNotEqual: {"!=", symClassOperator, posInfix}, // 37: '!='
SymDollar: {"$", symClassOperator, posPrefix}, // 38: '$'
SymHash: {"#", symClassOperator, posPrefix}, // 39: '#'
SymOpenRound: {"(", symClassParenthesis, posPrefix}, // 40: '('
SymClosedRound: {")", symClassParenthesis, posPostfix}, // 41: ')'
SymOpenSquare: {"[", symClassParenthesis, posPrefix}, // 42: '['
SymClosedSquare: {"]", symClassParenthesis, posPostfix}, // 43: ']'
SymOpenBrace: {"{", symClassParenthesis, posPrefix}, // 44: '{'
SymClosedBrace: {"}", symClassParenthesis, posPostfix}, // 45: '}'
SymTilde: {"~", symClassOperator, posPrefix}, // 46: '~'
SymDoubleQuestion: {"??", symClassOperator, posInfix}, // 47: '??'
SymQuestionEqual: {"?=", symClassOperator, posInfix}, // 48: '?='
SymQuestionExclam: {"?!", symClassOperator, posInfix}, // 49: '?!'
SymDoubleAt: {"@@", symClassCommand, posLeaf}, // 50: '@@'
SymDoubleColon: {"::", symClassOperator, posInfix}, // 51: '::'
SymDoubleGreater: {">>", symClassOperator, posInfix}, // 52: '>>'
SymDoubleLess: {"<<", symClassOperator, posInfix}, // 53: '<<'
SymCaret: {"^", symClassOperator, posInfix}, // 54: '^'
SymDollarRound: {"$(", symClassOperator, posPrefix}, // 55: '$('
SymOpenClosedRound: {"()", symClassOperator, posPostfix}, // 56: '()'
SymDoubleDollar: {"$$", symClassCommand, posLeaf}, // 57: '$$'
SymDoubleDot: {"..", symClassOperator, posInfix}, // 58: '..'
SymTripleDot: {"...", symClassOperator, posPostfix}, // 59: '...'
SymStarEqual: {"*=", symClassOperator, posInfix}, // 60: '*='
SymSlashEqual: {"/=", symClassOperator, posInfix}, // 61: '/='
SymPercEqual: {"%=", symClassOperator, posInfix}, // 62: '%='
SymDoubleLessEqual: {"<<=", symClassOperator, posInfix}, // 63: '<<='
SymDoubleGreaterEqual: {">>=", symClassOperator, posInfix}, // 64: '>>='
SymAmpersandEqual: {"&=", symClassOperator, posInfix}, // 65: '&='
SymVertBarEqual: {"|=", symClassOperator, posInfix}, // 65: '|='
SymCaretEqual: {"^=", symClassOperator, posInfix}, // 66: '^='
SymPlusGreater: {"+>", symClassOperator, posInfix}, // 67: '+>'
SymLessPlus: {"<+", symClassOperator, posInfix}, // 68: '<+'
SymPreInc: {"++", symClassOperator, posPrefix}, // : '++'
SymPreDec: {"--", symClassOperator, posPrefix}, // : '--'
// SymChangeSign
// SymUnchangeSign
// SymIdentifier
// SymBool
// SymInteger
// SymVariable
// SymFloat
// SymFraction
// SymString
// SymIterator
// SymOr: "or",
// SymAnd: "and",
// SymNot: "not",
// SymComment
// SymFuncCall
// SymFuncDef
// SymList
// SymDict
// SymIndex
// SymExpression
// SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
// SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
// // SymOpenComment // 0: '/*'
// // SymClosedComment // 0: '*/'
// // SymOneLineComment // 0: '//'
// keywordBase
SymKwAnd: {"and", symClassOperator, posInfix},
SymKwNot: {"not", symClassOperator, posInfix},
SymKwOr: {"or", symClassOperator, posInfix},
SymKwBut: {"but", symClassOperator, posInfix},
SymKwFunc: {"func(", symClassDeclaration, posPrefix},
SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
SymKwPlugin: {"plugin", symClassOperator, posPrefix},
SymKwIn: {"in", symClassOperator, posInfix},
SymKwInclude: {"include", symClassOperator, posPrefix},
SymKwNil: {"nil", symClassValue, posLeaf},
SymKwUnset: {"unset", symClassOperator, posPrefix},
}
}
func SymToString(sym Symbol) string {
if s, ok := symbolMap[sym]; ok {
return s.repr
}
return ""
}
func SymListToString(symList []Symbol, quote bool) string {
var sb strings.Builder
if len(symList) == 0 {
sb.WriteString("<nothing>")
} else {
for _, sym := range symList {
if sb.Len() > 0 {
sb.WriteByte(',')
sb.WriteByte(' ')
}
if quote {
sb.WriteByte('`')
}
sb.WriteString(SymToString(sym))
if quote {
sb.WriteByte('`')
}
}
}
return sb.String()
}
func StringEndsWithOperator(s string) bool {
return endingOperator(s) != SymNone
}
func endingOperator(s string) (sym Symbol) {
var matchLength = 0
sym = SymNone
lower := strings.TrimRight(strings.ToLower(s), " \t")
for symbol, spec := range symbolMap {
if strings.HasSuffix(lower, spec.repr) {
if len(spec.repr) > matchLength {
matchLength = len(spec.repr)
if spec.kind == symClassOperator && (spec.opType == posInfix || spec.opType == posPrefix) {
sym = symbol
} else {
sym = SymNone
}
}
}
}
return
}
+75 -62
View File
@@ -7,70 +7,82 @@ package expr
type Symbol int16
const (
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
SymNone // 0: Null value for variable of type symbol
SymError // 1: Error reading from stream
SymEos // 2: End of stream
SymMinus // 3: '-'
SymMinusEqual // 4: '-='
SymDoubleMinus // 5: '--'
SymPlus // 6: '+'
SymPlusEqual // 7: '+='
SymDoublePlus // 8: '++'
SymStar // 9: '*'
SymDoubleStar // 10: '**'
SymSlash // 11: '/'
SymBackSlash // 12: '\'
SymVertBar // 13: '|'
SymDoubleVertBar // 14: '||'
SymComma // 15: ','
SymColon // 16: ':'
SymSemiColon // 17: ';'
SymDot // 18: '.'
SymDotSlash // 19: './'
SymQuote // 20: '\''
SymDoubleQuote // 21: '"'
SymBackTick // 22: '`'
SymExclamation // 23: '!'
SymQuestion // 24: '?'
SymAmpersand // 25: '&'
SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%'
SymAt // 28: '@'
SymUndescore // 29: '_'
SymEqual // 30: '='
SymDoubleEqual // 31: '=='
SymLess // 32: '<'
SymLessOrEqual // 33: '<='
SymGreater // 34: '>'
SymGreaterOrEqual // 35: '>='
SymLessGreater // 36: '<>'
SymNotEqual // 37: '!='
SymDollar // 38: '$'
SymHash // 39: '#'
SymOpenRound // 40: '('
SymClosedRound // 41: ')'
SymOpenSquare // 42: '['
SymClosedSquare // 43: ']'
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymInsert // 52: '>>'
SymAppend // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
SymNone // 0: Null value for variable of type symbol
SymError // 1: Error reading from stream
SymEos // 2: End of stream
SymMinus // 3: '-'
SymMinusEqual // 4: '-='
SymDoubleMinus // 5: '--'
SymPlus // 6: '+'
SymPlusEqual // 7: '+='
SymDoublePlus // 8: '++'
SymStar // 9: '*'
SymDoubleStar // 10: '**'
SymSlash // 11: '/'
SymBackSlash // 12: '\'
SymVertBar // 13: '|'
SymDoubleVertBar // 14: '||'
SymComma // 15: ','
SymColon // 16: ':'
SymSemiColon // 17: ';'
SymDot // 18: '.'
SymDotSlash // 19: './'
SymQuote // 20: '\''
SymDoubleQuote // 21: '"'
SymBackTick // 22: '`'
SymExclamation // 23: '!'
SymQuestion // 24: '?'
SymAmpersand // 25: '&'
SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%'
SymAt // 28: '@'
SymUndescore // 29: '_'
SymEqual // 30: '='
SymDoubleEqual // 31: '=='
SymLess // 32: '<'
SymLessOrEqual // 33: '<='
SymGreater // 34: '>'
SymGreaterOrEqual // 35: '>='
SymLessGreater // 36: '<>'
SymNotEqual // 37: '!='
SymDollar // 38: '$'
SymHash // 39: '#'
SymOpenRound // 40: '('
SymClosedRound // 41: ')'
SymOpenSquare // 42: '['
SymClosedSquare // 43: ']'
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymDoubleGreater // 52: '>>'
SymDoubleLess // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymSlashEqual // 61: '/='
SymPercEqual // 62: '%='
SymDoubleLessEqual // 63: '<<='
SymDoubleGreaterEqual // 64: '>>='
SymAmpersandEqual // 65: '&='
SymVertBarEqual // 65: '|='
SymCaretEqual // 66: '^='
SymPlusGreater // 67: '+>'
SymLessPlus // 68: '<+'
SymChangeSign
SymUnchangeSign
SymDereference
SymPreInc
SymPreDec
SymIdentifier
SymBool
SymInteger
@@ -88,6 +100,7 @@ const (
SymList
SymDict
SymIndex
SymRange // [index : index]
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
+2 -1
View File
@@ -26,12 +26,13 @@ func TestBool(t *testing.T) {
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, true, nil},
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
/* 15 */ {`!true`, false, nil},
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
// runTestSuiteSpec(t, section, inputs, 15)
runTestSuite(t, section, inputs)
}
+9 -8
View File
@@ -30,9 +30,9 @@ func TestFuncBase(t *testing.T) {
/* 16 */ {`isString("3" + 1)`, true, nil},
/* 17 */ {`isList(["3", 1])`, true, nil},
/* 18 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 19 */ {`isFract(1|3)`, true, nil},
/* 20 */ {`isFract(3|1)`, false, nil},
/* 21 */ {`isRational(3|1)`, true, nil},
/* 19 */ {`isFract(1:3)`, true, nil},
/* 20 */ {`isFract(3:1)`, false, nil},
/* 21 */ {`isRational(3:1)`, true, nil},
/* 22 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 23 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
/* 24 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
@@ -41,26 +41,27 @@ func TestFuncBase(t *testing.T) {
/* 27 */ {`dec(2.0)`, float64(2), nil},
/* 28 */ {`dec("2.0")`, float64(2), nil},
/* 29 */ {`dec(true)`, float64(1), nil},
/* 30 */ {`dec(true")`, nil, `[1:11] missing string termination "`},
/* 30 */ {`dec(true")`, nil, "[1:11] expected one of `,`, `)`, got `\"`"},
/* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`},
/* 32 */ {`dec(1,2,3)`, nil, `dec(): too many params -- expected 1, got 3`},
/* 33 */ {`isBool(false)`, true, nil},
/* 34 */ {`fract(1|2)`, newFraction(1, 2), nil},
/* 34 */ {`fract(1:2)`, newFraction(1, 2), nil},
/* 35 */ {`fract(12,2)`, newFraction(6, 1), nil},
/* 36 */ {`bool(2)`, true, nil},
/* 37 */ {`bool(1|2)`, true, nil},
/* 37 */ {`bool(1:2)`, true, nil},
/* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, `bool(): can't convert list to bool`},
/* 42 */ {`dec(false)`, float64(0), nil},
/* 43 */ {`dec(1|2)`, float64(0.5), nil},
/* 43 */ {`dec(1:2)`, float64(0.5), nil},
/* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 45 */ {`eval("a=3"); a`, int64(3), nil},
// /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
}
t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 10)
// runTestSuiteSpec(t, section, inputs, 45)
runTestSuite(t, section, inputs)
}
+3 -1
View File
@@ -15,7 +15,7 @@ func TestFuncString(t *testing.T) {
/* 1 */ {`builtin "string"; strJoin("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; strJoin("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, `strJoin(): the "separator" parameter must be a string, got a integer (1)`},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, `strJoin(): the "separator" parameter must be a string, got an integer (1)`},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, `strJoin(): expected string, got integer (2)`},
/* 6 */ {`builtin "string"; "<"+strTrim(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; strSub("0123456789", 1,2)`, "12", nil},
@@ -31,6 +31,8 @@ func TestFuncString(t *testing.T) {
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, `strJoin(): expected string, got integer (1)`},
/* 19 */ {`builtin "string"; strJoin()`, nil, `strJoin(): too few params -- expected 1 or more, got 0`},
/* 20 */ {`builtin "string"; strUpper("StOp")`, "STOP", nil},
/* 21 */ {`builtin "string"; strLower("StOp")`, "stop", nil},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
+10 -7
View File
@@ -17,18 +17,21 @@ func TestExpr(t *testing.T) {
/* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(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},
/* 6 */ {`a=3; a+=1`, int64(4), nil},
/* 7 */ {`a=3; a-=1`, int64(2), nil},
/* 8 */ {`a=3; a*=2`, int64(6), nil},
/* 6 */ {`a=3; a+=1; a`, int64(4), nil},
/* 7 */ {`a=3; a-=1; a`, int64(2), nil},
/* 8 */ {`a=3; a*=2; a`, int64(6), nil},
/* 9 */ {`v=[10,20,30]; v[0]+=1; v[0]`, int64(11), nil},
/* 10 */ {`r={"a":10}; r["a"]+=1; r["a"]`, int64(11), nil},
/* 11 */ {`a=3; a/=2`, nil, `[1:8] left operand of "=" must be a variable or a collection's item`},
/* 12 */ {`*=2`, nil, `[1:2] left operand of "*=" must be a variable or a variable expression`},
/* 11 */ {`a=3; a/=2`, int64(1), nil},
/* 12 */ {`*=2`, nil, `[1:2] infix operator "*=" requires two non-nil operands, got 1`},
/* 13 */ {`a=3; a*=2+1; a`, int64(9), nil},
/* 14 */ {`a=3; (a*=2)+1; a`, int64(6), nil},
/* 15 */ {`a=3; a*=2)+1; a`, nil, `[1:11] unexpected token ")"`},
/* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), nil},
/* 17 */ {`
/* 17 */ {`true ? {"a"} :: {"b"}`, "a", nil},
/* 18 */ {`$$`, NewDict(map[any]any{"variables": NewDict(nil), "functions": NewDict(nil)}), nil},
///* 19 */ {`$$global`, NewDict(map[any]any{"variables": NewDict(nil), "functions": NewDict(nil)}), nil},
/* 19 */ {`
ds={
"init":func(@end){@current=0 but true},
//"current":func(){current},
@@ -43,6 +46,6 @@ func TestExpr(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 17)
// runTestSuiteSpec(t, section, inputs, 18)
runTestSuite(t, section, inputs)
}
+23 -20
View File
@@ -11,35 +11,39 @@ import (
func TestFractionsParser(t *testing.T) {
section := "Fraction"
inputs := []inputType{
/* 1 */ {`1|2`, newFraction(1, 2), nil},
/* 2 */ {`1|2 + 1`, newFraction(3, 2), nil},
/* 3 */ {`1|2 - 1`, newFraction(-1, 2), nil},
/* 4 */ {`1|2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 7 */ {`1|"5"`, nil, `denominator must be integer, got string (5)`},
/* 8 */ {`"1"|5`, nil, `numerator must be integer, got string (1)`},
/* 9 */ {`1|+5`, nil, `[1:3] infix operator "|" requires two non-nil operands, got 1`},
/* 10 */ {`1|(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
/* 12 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1|0`, nil, `division by zero`},
/* 1 */ {`1:2`, newFraction(1, 2), nil},
/* 2 */ {`1:2 + 1`, newFraction(3, 2), nil},
/* 3 */ {`1:2 - 1`, newFraction(-1, 2), nil},
/* 4 */ {`1:2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1:2 * 2:3`, newFraction(2, 6), nil},
/* 6 */ {`1:2 / 2:3`, newFraction(3, 4), nil},
/* 7 */ {`1:"5"`, nil, `denominator must be integer, got string (5)`},
/* 8 */ {`"1":5`, nil, `numerator must be integer, got string (1)`},
/* 9 */ {`1:+5`, newFraction(1, 5), nil},
/* 10 */ {`1:(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1:2, 2:3)`, newFraction(7, 6), nil},
/* 12 */ {`builtin "math.arith"; add(1:2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1:2, 2:3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1:2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1:0`, nil, `[1:3] division by zero`},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
/* 17 */ {`fract("")`, (*FractionType)(nil), `bad syntax`},
/* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
/* 19 */ {`fract("+1")`, newFraction(1, 1), nil},
/* 20 */ {`fract("1a")`, (*FractionType)(nil), `strconv.ParseInt: parsing "1a": invalid syntax`},
/* 21 */ {`fract(1,0)`, nil, `fract(): division by zero`},
/* 22 */ {`string(1|2)`, "1|2", nil},
/* 22 */ {`string(1:2)`, "1:2", nil},
/* 23 */ {`1+1:2+0.5`, float64(2), nil},
/* 24 */ {`1:(2-2)`, nil, `[1:3] division by zero`},
/* 25 */ {`[0,1][1-1]:1`, newFraction(0, 1), nil},
}
// runTestSuiteSpec(t, section, inputs, 25)
runTestSuite(t, section, inputs)
}
func TestFractionToStringSimple(t *testing.T) {
source := newFraction(1, 2)
want := "1|2"
want := "1:2"
got := source.ToString(0)
if got != want {
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
@@ -55,10 +59,9 @@ func TestFractionToStringMultiline(t *testing.T) {
}
}
// TODO Check this test: the output string ends with a space
func _TestToStringMultilineTty(t *testing.T) {
func TestToStringMultilineTty(t *testing.T) {
source := newFraction(-1, 2)
want := "\x1b[4m-1\x1b[0m\n2"
want := "\x1b[4m-1\x1b[0m\n 2"
got := source.ToString(MultiLine | TTY)
if got != want {
t.Errorf(`(1,2) -> result = %#v [%T], want = %#v [%T]`, got, got, want, want)
+25
View File
@@ -35,6 +35,7 @@ func TestFuncs(t *testing.T) {
/* 22 */ {`f=func(a,b){a*2+b}; f(b=2,a=1)`, int64(4), nil},
/* 23 */ {`f=func(a=10,b=10){a*2+b}; f(b=1)`, int64(21), nil},
/* 24 */ {`f=func(a,b){a*2+b}; f(a=1,2)`, nil, `f(): positional param nr 2 not allowed after named params`},
/* 25 */ {`f=func(x,x){x}`, nil, `[1:11] parameter "x" at position 2 already defined at position 1`},
// /* 20 */ {`a=[func(){3}]; a[0]()`, int64(3), nil},
// /* 20 */ {`m={}; m["f"]=func(){3}; m["f"]()`, int64(3), nil},
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
@@ -67,3 +68,27 @@ func TestFunctionGetFunc(t *testing.T) {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
func TestGoFunction(t *testing.T) {
section := "Funcs"
inputs := []inputType{
/* 1 */ {`myName()`, "Celestino Amoroso", nil},
/* 2 */ {`myName("Peppino")`, "Peppino", nil},
}
myName := func(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var ok bool
if result, ok = args["name"].(string); !ok {
err = ErrWrongParamType(name, "name", TypeString, args["name"])
}
return
}
ctx := NewSimpleStore()
ctx.RegisterFunc("myName", NewGolangFunctor(myName), TypeString, []ExprFuncParam{
NewFuncParamFlagDef("name", PfOptional|PfDefault, "Celestino Amoroso"),
})
runCtxTestSuite(t, ctx, section, inputs)
}
+4 -2
View File
@@ -16,11 +16,13 @@ func TestCollections(t *testing.T) {
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
/* 5 */ {`"abcdef"[1:2:3]`, nil, `[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`},
/* 5 */ {`"abcdef"[1:2:3]`, nil, `[1:14] invalid range specification`},
/* 6 */ {`"abcdef"[((1>0)?{1}:{0}):3]`, "bc", nil},
/* 7 */ {`"abcdef"[[0,1][0]:1]`, "a", nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5)
// runTestSuiteSpec(t, section, inputs, 5)
runTestSuite(t, section, inputs)
}
+2 -2
View File
@@ -9,10 +9,10 @@ import "testing"
func TestIteratorParser(t *testing.T) {
section := "Iterator"
inputs := []inputType{
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ^it`, int64(0), nil},
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); *it`, int64(0), nil},
/* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(3), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ^it`, int64(0), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; *it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
+18 -18
View File
@@ -23,8 +23,8 @@ func TestListParser(t *testing.T) {
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, `add(): param nr 2 (2 in 1) has wrong type string, number expected`},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
/* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 12 */ {`[1,2,3] <+ 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
/* 13 */ {`2-1 +> [2,3]`, newListA(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},
@@ -33,25 +33,25 @@ func TestListParser(t *testing.T) {
/* 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`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil},
/* 22 */ {`a=[1,2]; (a)<+3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<+3; a`, newListA(int64(1), int64(2)), nil},
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, `[1:19] one index only is allowed`},
/* 26 */ {`[0,1,2,3,4][:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 27 */ {`["a", "b", "c"] << ;`, nil, `[1:18] infix operator "<<" requires two non-nil operands, got 1`},
/* 28 */ {`2 << 3;`, nil, `[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator "<<"`},
/* 29 */ {`but >> ["a", "b", "c"]`, nil, `[1:6] infix operator ">>" requires two non-nil operands, got 0`},
/* 30 */ {`2 >> 3;`, nil, `[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`},
/* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 33 */ {`a=[1,2]; 5>>a`, newListA(int64(5), int64(1), int64(2)), nil},
/* 34 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, `index 5 out of bounds (0, 1)`},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, `[1:12] index/key specification expected, got [] [list]`},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, `[1:12] index/key is nil`},
/* 38 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
/* 39 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
/* 41 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 27 */ {`["a", "b", "c"] <+ ;`, nil, `[1:18] infix operator "<+" requires two non-nil operands, got 1`},
/* 28 */ {`2 << 3;`, int64(16), nil},
/* 29 */ {`but +> ["a", "b", "c"]`, nil, `[1:6] infix operator "+>" requires two non-nil operands, got 0`},
/* 30 */ {`2 >> 3;`, int64(0), nil},
/* 31 */ {`a=[1,2]; a<+3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 32 */ {`a=[1,2]; 5+>a`, newListA(int64(5), int64(1), int64(2)), nil},
/* 33 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
/* 34 */ {`L=[1,2]; L[5]=9; L`, nil, `index 5 out of bounds (0, 1)`},
/* 35 */ {`L=[1,2]; L[]=9; L`, nil, `[1:12] index/key specification expected, got [] [list]`},
/* 36 */ {`L=[1,2]; L[nil]=9;`, nil, `[1:12] index/key is nil`},
/* 37 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
/* 38 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 30 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
}
// t.Setenv("EXPR_PATH", ".")
+20 -1
View File
@@ -14,10 +14,29 @@ func TestOperator(t *testing.T) {
/* 1 */ {`a=1; unset "a"; a`, nil, `undefined variable or function "a"`},
/* 2 */ {`a=1; unset ["a", "b"]`, int64(1), nil},
/* 3 */ {`f=func(){3}; unset "f()"`, int64(1), nil},
/* 4 */ {`a=1; a<<=1+0`, int64(2), nil},
/* 5 */ {`a=2; a>>=1+0`, int64(1), nil},
/* 6 */ {`1<<1`, int64(2), nil},
/* 7 */ {`1>>1`, int64(0), nil},
/* 8 */ {`1|2`, int64(3), nil},
/* 9 */ {`a=1; a|=2`, int64(3), nil},
/* 10 */ {`3&1`, int64(1), nil},
/* 11 */ {`a=3; a&=1`, int64(1), nil},
/* 12 */ {`~1`, int64(-2), nil},
/* 13 */ {`0x10`, int64(16), nil},
/* 14 */ {`0x1X`, nil, `[1:5] two adjacent operators: "1" and "X"`},
/* 15 */ {`0o10`, int64(8), nil},
/* 16 */ {`0b10`, int64(2), nil},
/* 17 */ {`~true`, nil, `[1:2] prefix/postfix operator "~" do not support operand 'true' [bool]`},
/* 18 */ {`1^2`, int64(3), nil},
/* 19 */ {`3^2`, int64(1), nil},
/* 19 */ {`a=1; a^=2`, int64(3), nil},
/* 20 */ {`a=1; ++a`, int64(2), nil},
/* 21 */ {`a=1; --a`, int64(0), nil},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
}
+15 -17
View File
@@ -77,9 +77,9 @@ func TestGeneralParser(t *testing.T) {
/* 63 */ {`"1.5" == `, nil, `[1:8] infix operator "==" requires two non-nil operands, got 1`},
/* 64 */ {`"1.5" != `, nil, `[1:8] infix operator "!=" requires two non-nil operands, got 1`},
/* 65 */ {"+1.5", float64(1.5), nil},
/* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one not nil operand`},
/* 67 */ {"4 / 0", nil, `division by zero`},
/* 68 */ {"4.0 / 0", nil, `division by zero`},
/* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one non-nil operand`},
/* 67 */ {"4 / 0", nil, `[1:4] division by zero`},
/* 68 */ {"4.0 / 0", nil, `[1:6] division by zero`},
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
/* 70 */ {`123`, int64(123), nil},
/* 71 */ {`1.`, float64(1.0), nil},
@@ -94,10 +94,10 @@ func TestGeneralParser(t *testing.T) {
/* 80 */ {`5 % 2.0`, nil, `[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},
/* 84 */ {`~ 2 > 1`, false, nil},
/* 85 */ {`~ true && true`, false, nil},
/* 86 */ {`~ false || true`, true, nil},
/* 83 */ {`"a" < "b" AND NOT 2 == 1`, true, nil},
/* 84 */ {`NOT 2 > 1`, false, nil},
/* 85 */ {`nOT true && true`, false, nil},
/* 86 */ {`NOT false || true`, true, nil},
/* 87 */ {`false but true`, true, nil},
/* 88 */ {`2+3 but 5*2`, int64(10), nil},
/* 89 */ {`x=2`, int64(2), nil},
@@ -132,18 +132,16 @@ func TestGeneralParser(t *testing.T) {
/* 118 */ {`{"key":}`, nil, "[1:9] expected `dictionary-value`, got `}`"},
/* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`v=10; v++; v`, int64(11), nil},
/* 121 */ {`1+1|2+0.5`, float64(2), nil},
/* 122 */ {`1.2()`, newFraction(6, 5), nil},
/* 123 */ {`1|(2-2)`, nil, `division by zero`},
/* 124 */ {`x="abc"; x ?! #x`, int64(3), nil},
/* 125 */ {`x ?! #x`, nil, `[1:7] prefix/postfix operator "#" do not support operand '<nil>' [nil]`},
/* 126 */ {`x ?! (x+1)`, nil, nil},
/* 127 */ {`"abx" ?! (x+1)`, nil, `[1:6] left operand of "?!" must be a variable`},
/* 128 */ {`"abx" ?? "pqr"`, nil, `[1:6] left operand of "??" must be a variable`},
/* 129 */ {`"abx" ?= "pqr"`, nil, `[1:6] left operand of "?=" must be a variable`},
/* 121 */ {`1.2()`, newFraction(6, 5), nil},
/* 122 */ {`x="abc"; x ?! #x`, int64(3), nil},
/* 123 */ {`x ?! #x`, nil, `[1:7] prefix/postfix operator "#" do not support operand '<nil>' [nil]`},
/* 124 */ {`x ?! (x+1)`, nil, nil},
/* 125 */ {`"abx" ?! (x+1)`, nil, `[1:6] left operand of "?!" must be a variable`},
/* 126 */ {`"abx" ?? "pqr"`, nil, `[1:6] left operand of "??" must be a variable`},
/* 127 */ {`"abx" ?= "pqr"`, nil, `[1:6] left operand of "?=" must be a variable`},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 130)
// runTestSuiteSpec(t, section, inputs, 114)
runTestSuite(t, section, inputs)
}
+11 -11
View File
@@ -8,18 +8,18 @@ import (
"testing"
)
func _TestImportPlugin(t *testing.T) {
t.Setenv("PLUGINS", "${HOME}/go/src/git.portale-stac.it/go")
t.Setenv("EXPR_PLUGIN_PATH","${PLUGINS}/expr-json-plugin:${PLUGINS}/expr-csv-plugin")
// func TestImportPlugin(t *testing.T) {
// t.Setenv("PLUGINS", "${HOME}/go/src/git.portale-stac.it/go")
// t.Setenv("EXPR_PLUGIN_PATH","${PLUGINS}/expr-json-plugin:${PLUGINS}/expr-csv-plugin")
gotCount, gotErr := importPluginFromSearchPath("json")
if gotCount != 1 {
t.Errorf("Import count: got=%d, want=1", gotCount)
}
if gotErr != nil {
t.Errorf("importPlugin() failed: %v", gotErr)
}
}
// gotCount, gotErr := importPluginFromSearchPath("json")
// if gotCount != 1 {
// t.Errorf("Import count: got=%d, want=1", gotCount)
// }
// if gotErr != nil {
// t.Errorf("importPlugin() failed: %v", gotErr)
// }
// }
func TestPluginExists(t *testing.T) {
name := "json"
+7 -7
View File
@@ -21,9 +21,9 @@ func TestRelational(t *testing.T) {
/* 8 */ {`true != false`, true, nil},
/* 9 */ {`1.0 != 3.0-2`, false, nil},
/* 10 */ {`[1,2] != [2,1]`, true, nil},
/* 11 */ {`1|2 == 1|3`, false, nil},
/* 12 */ {`1|2 != 1|3`, true, nil},
/* 13 */ {`1|2 == 4|8`, true, nil},
/* 11 */ {`1:2 == 1:3`, false, nil},
/* 12 */ {`1:2 != 1:3`, true, nil},
/* 13 */ {`1:2 == 4:8`, true, nil},
/* 14 */ {`1 < 8`, true, nil},
/* 15 */ {`1 <= 8`, true, nil},
/* 16 */ {`"a" < "b"`, true, nil},
@@ -32,10 +32,10 @@ func TestRelational(t *testing.T) {
/* 19 */ {`1.0 <= 8`, true, nil},
/* 20 */ {`1.0 <= 1.0`, true, nil},
/* 21 */ {`1.0 == 1`, true, nil},
/* 22 */ {`1|2 < 1|3`, false, nil},
/* 23 */ {`1|2 <= 1|3`, false, nil},
/* 24 */ {`1|2 > 1|3`, true, nil},
/* 25 */ {`1|2 >= 1|3`, true, nil},
/* 22 */ {`1:2 < 1:3`, false, nil},
/* 23 */ {`1:2 <= 1:3`, false, nil},
/* 24 */ {`1:2 > 1:3`, true, nil},
/* 25 */ {`1:2 >= 1:3`, true, nil},
/* 26 */ {`[1,2,3] > [2]`, true, nil},
/* 27 */ {`[1,2,3] > [9]`, false, nil},
/* 28 */ {`[1,2,3] >= [6]`, false, nil},
+8 -1
View File
@@ -9,6 +9,7 @@ import (
)
func TestStringsParser(t *testing.T) {
section := "String"
inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil},
@@ -16,6 +17,12 @@ func TestStringsParser(t *testing.T) {
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc"[1]`, `b`, nil},
/* 6 */ {`#"abc"`, int64(3), nil},
/* 7 */ {`"192.168.0.240" / "."`, newListA("192", "168", "0", "240"), nil},
/* 8 */ {`("192.168.0.240" / ".")[1]`, "168", nil},
/* 9 */ {`"AF3B0Dz" / 2`, newListA("AF", "3B", "0D", "z"), nil},
/* 10 */ {`"AF3B0Dz" / 0`, nil, "[1:12] division by zero"},
}
runTestSuite(t, "String", inputs)
// runTestSuiteSpec(t, section, inputs, 8)
runTestSuite(t, section, inputs)
}
+14 -4
View File
@@ -15,20 +15,26 @@ const (
priRange
priBut
priAssign
priInsert
priOr
priAnd
priNot
priRelational
priBitwiseOr
priBitwiseAnd
priBitwiseNot
priSum
priProduct
priFraction
priSelector
priBinShift
priSign
priFact
priIterValue
priDefault
priIncDec
priDot
priDereference
priValue
)
@@ -62,8 +68,8 @@ func (s *term) Clone() (d *term) {
}
}
d = &term{
tk: *s.tk.Clone(),
parent: s.parent,
tk: *s.tk.Clone(),
parent: s.parent,
children: children,
position: s.position,
priority: s.priority,
@@ -198,6 +204,10 @@ func (term *term) errIncompatibleType(value any) error {
term.source(), value, TypeName(value))
}
func (term *term) errDivisionByZero() error {
return term.tk.Errorf("division by zero")
}
func (term *term) Errorf(template string, args ...any) (err error) {
err = term.tk.Errorf(template, args...)
return
@@ -211,11 +221,11 @@ func (term *term) checkOperands() (err error) {
}
case posPrefix:
if term.children == nil || len(term.children) != 1 || term.children[0] == nil {
err = term.tk.Errorf("prefix operator %q requires one not nil operand", term.tk.String())
err = term.tk.Errorf("prefix operator %q requires one non-nil operand", term.tk.String())
}
case posPostfix:
if term.children == nil || len(term.children) != 1 || term.anyChildrenNil() {
err = term.tk.Errorf("postfix operator %q requires one not nil operand", term.tk.String())
err = term.tk.Errorf("postfix operator %q requires one non-nil operand", term.tk.String())
}
case posMultifix:
if term.children == nil || len(term.children) < 3 || term.anyChildrenNil() {
+18 -1
View File
@@ -79,6 +79,10 @@ func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym
}
func (tk *Token) SetSymbol(sym Symbol) {
tk.Sym = sym
}
func (tk *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", tk.row, tk.col)+template, args...)
return
@@ -93,6 +97,15 @@ func (tk *Token) Error() (err error) {
return
}
func (tk *Token) ErrorText() (err string) {
if tk.Sym == SymError {
if msg, ok := tk.Value.(error); ok {
err = msg.Error()
}
}
return
}
func (tk *Token) Errors(msg string) (err error) {
err = fmt.Errorf("[%d:%d] %v", tk.row, tk.col, msg)
return
@@ -104,6 +117,10 @@ func (tk *Token) ErrorExpectedGot(symbol string) (err error) {
}
func (tk *Token) ErrorExpectedGotString(symbol, got string) (err error) {
err = fmt.Errorf("[%d:%d] expected `%s`, got `%s`", tk.row, tk.col, symbol, got)
return tk.ErrorExpectedGotStringWithPrefix("expected", symbol, got)
}
func (tk *Token) ErrorExpectedGotStringWithPrefix(prefix, symbol, got string) (err error) {
err = fmt.Errorf("[%d:%d] %s %s, got `%s`", tk.row, tk.col, prefix, symbol, got)
return
}