Compare commits

..

77 Commits

Author SHA1 Message Date
camoroso 6ee365bacc new operator 'map' ans general variable access by ${} 2026-04-24 20:11:25 +02:00
camoroso 20d8236325 Iterators: Removed NewAnyIterator(), added NewIterator() 2026-04-24 06:26:00 +02:00
camoroso c39ee7cec0 dict-iterator.go: fixed behaviour of NewDictIterator() with no args 2026-04-23 22:06:23 +02:00
camoroso 9e4252173b Iterator interface now embeds fmt.Stringer 2026-04-23 22:04:41 +02:00
camoroso 2b80ba6789 t_plugin_test.go: TestLoadPluginName() removed due to linkage mismatch 2026-04-23 22:02:41 +02:00
camoroso d7247f97c5 doc: added description of set() and unset() functions defined in the base buiiltin 2026-04-23 19:09:09 +02:00
camoroso 02df7f1c1f Plugin file name extension adapted to the host OS convention (.so on linux, .dll on windows, .dylib on Darwin/MacOS) 2026-04-23 19:06:11 +02:00
camoroso b6b09b2fb1 builtin-os-file: add function fileReadIterator() that creates an iterator over lines of a text file 2026-04-23 19:01:07 +02:00
camoroso 0677180456 builtin-iterator.go/run(): removed default operator (print item) 2026-04-23 18:55:03 +02:00
camoroso b6952444ab builtin-base: added functions set() and unset() 2026-04-21 17:14:51 +02:00
camoroso 6c3a071a02 doc: last change date-time updated 2026-04-21 06:49:58 +02:00
camoroso d7d03b4af8 README: added link to the main Expr documentation hosted on cdn.paas.portale-stac.it 2026-04-21 06:47:15 +02:00
camoroso 49c5cb6a22 doc: progress about builtin functions 2026-04-21 06:45:41 +02:00
camoroso 53d805a832 doc: test commit 2026-04-19 21:27:43 +02:00
camoroso b99ad5def1 Merge branch 'iterator/dict' 2026-04-19 15:18:07 +02:00
camoroso 0d44b8697b doc: more details about iterators over dicts 2026-04-19 15:17:21 +02:00
camoroso 1a7e537921 more tests on iterator over dicts 2026-04-19 15:16:25 +02:00
camoroso 807df0f3a8 Iterator: added support for iterators over dictionaries 2026-04-19 15:08:14 +02:00
camoroso d7dd628fc9 doc: further details about iterator definition 2026-04-19 08:20:40 +02:00
camoroso 1f57ba28dd Doc: progress about builtin modules 2026-04-18 12:21:42 +02:00
camoroso 92bd366380 builtin-base: bool() supports lists and dicts, int() supports fractions 2026-04-18 12:20:05 +02:00
camoroso 7b93c5b4ac doc: begin of iterators description 2026-04-16 10:13:41 +02:00
camoroso 3ba8194ddb Expr.doc, a lot of fixes 2026-04-15 18:17:27 +02:00
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
69 changed files with 4714 additions and 749 deletions
+1 -1
View File
@@ -144,4 +144,4 @@ Variables and functions can be added to a context both programmatically and ad a
== Expressions syntax
See #TODO link to doc/Expr.html#
See https://cdn.paas.portale-stac.it/howto/go-pkg/expr/doc/Expr.html[Expr documentation] for a complete reference of the expression syntax and available functions.
+98
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"math"
"strconv"
"strings"
)
const (
@@ -71,6 +72,10 @@ func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, er
result = v
case string:
result = len(v) > 0
case *ListType:
result = len(*v) > 0
case *DictType:
result = len(*v) > 0
default:
err = ErrCantConvert(name, v, "bool")
}
@@ -94,6 +99,8 @@ func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
case *FractionType:
result = int64(v.num / v.den)
default:
err = ErrCantConvert(name, v, "int")
}
@@ -188,6 +195,78 @@ 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
}
func setFunc(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 {
ctx.GetParent().UnsafeSetVar(varName, result)
} else {
err = ErrWrongParamType(name, ParamValue, TypeAny, args[ParamValue])
}
return
}
func unsetFunc(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])
} else {
ctx.GetParent().DeleteVar(varName)
result = nil
}
return
}
//// import
func ImportBuiltinsFuncs(ctx ExprContext) {
anyParams := []ExprFuncParam{
NewFuncParam(ParamValue),
@@ -211,6 +290,25 @@ 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),
})
ctx.RegisterFunc("set", NewGolangFunctor(setFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParam(ParamValue),
})
ctx.RegisterFunc("unset", NewGolangFunctor(unsetFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParam(ParamValue),
})
}
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 {
+18 -10
View File
@@ -23,10 +23,12 @@ func parseRunArgs(localCtx ExprContext, args map[string]any) (it Iterator, op Fu
return
}
if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil {
if args[iterParamOperator] != nil {
if op, ok = args[iterParamOperator].(Functor); !ok || op == nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
return
}
}
var vars *DictType
if vars, ok = args[iterParamVars].(*DictType); !ok && args[iterParamVars] != nil {
@@ -51,7 +53,7 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
var ok bool
var op Functor
var v any
var usingDefaultOp = false
// var usingDefaultOp = false
var params map[string]any
var item any
@@ -60,18 +62,20 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
if it, op, err = parseRunArgs(localCtx, args); err != nil {
return
} else if op == nil {
op = NewGolangFunctor(printLnFunc)
usingDefaultOp = true
// } else if op == nil {
// op = NewGolangFunctor(printLnFunc)
// usingDefaultOp = true
}
for item, err = it.Next(); err == nil; item, err = it.Next() {
if usingDefaultOp {
params = map[string]any{ParamItem: []any{item}}
} else {
params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
}
// if usingDefaultOp {
// params = map[string]any{ParamItem: []any{item}}
// } else {
// params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
// }
if op != nil {
params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
break
} else {
@@ -81,11 +85,15 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
}
}
}
}
if err == io.EOF {
err = nil
}
if err == nil {
if op == nil {
ctx.UnsafeSetVar(iterVarStatus, it.Count())
}
result, _ = localCtx.GetVar(iterVarStatus)
}
return
+149
View File
@@ -0,0 +1,149 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"fmt"
"io"
"slices"
)
const paramHandleOrPath = "handle-or-path"
const fileReadTextIteratorType = "fileReadTextIterator"
type fileReadTextIterator struct {
osReader *osReader
index int
count int
line string
autoClose bool
}
func newReadTextIterator(r *osReader, autoClose bool) *fileReadTextIterator {
return &fileReadTextIterator{osReader: r, index: -1, autoClose: autoClose}
}
func (it *fileReadTextIterator) TypeName() string {
return fileReadTextIteratorType
}
func (it *fileReadTextIterator) String() string {
if it.osReader != nil && it.osReader.fh != nil {
return fmt.Sprintf("$(%s@%q)", fileReadTextIteratorType, it.osReader.fh.Name())
}
return fmt.Sprintf("$(%s@<nil>)", fileReadTextIteratorType)
}
func (it *fileReadTextIterator) Count() int {
return it.count
}
func (it *fileReadTextIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.osReader.fh != nil {
if it.line, err = it.osReader.reader.ReadString('\n'); err == nil {
it.index++
it.count++
item = it.line[0 : len(it.line)-1]
} else if it.autoClose {
it.Clean()
}
}
return
}
func (it *fileReadTextIterator) Current() (item any, err error) {
if len(it.line) > 0 {
item = it.line[0 : len(it.line)-1]
}
return
}
func (it *fileReadTextIterator) Index() int {
return it.index
}
func (it *fileReadTextIterator) Reset() (err error) {
if _, err = it.osReader.fh.Seek(0, io.SeekStart); err == nil {
it.index = -1
it.count = 0
it.line = ""
}
return
}
func (it *fileReadTextIterator) HasOperation(name string) bool {
return slices.Contains([]string{NextName, ResetName, IndexName, CountName, CurrentName, CleanName}, name)
}
func (it *fileReadTextIterator) Clean() (err error) {
if it.osReader.fh != nil {
if err = it.osReader.fh.Close(); err == nil {
it.osReader = nil
}
}
return nil
}
func (it *fileReadTextIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case NextName:
v, err = it.Next()
case ResetName:
err = it.Reset()
case CleanName:
err = it.Clean()
case IndexName:
v = int64(it.Index())
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
default:
err = errNoOperation(name)
}
return
}
func fileReadIteratorFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var handle *osReader
var invalidFileHandle any
var ok, autoClose bool
result = nil
if handle, ok = args[paramHandleOrPath].(*osReader); !ok {
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
var handleAny any
if handleAny, err = openFileFunc(ctx, name, map[string]any{ParamFilepath: fileName}); err != nil {
return
}
if handleAny != nil {
handle = handleAny.(*osReader)
autoClose = true
}
} else {
invalidFileHandle = args[paramHandleOrPath]
}
}
if handle != nil {
result = newReadTextIterator(handle, autoClose)
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
// func ImportOsIterFuncs(ctx ExprContext) {
// ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
// NewFuncParam(paramHandleOrPath),
// })
// }
// func init() {
// RegisterBuiltinModule("os.file", ImportOsIterFuncs, "Operating system file iterator functions")
// }
+4
View File
@@ -250,6 +250,10 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamHandle),
})
ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
NewFuncParam(paramHandleOrPath),
})
}
func init() {
+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
View File
@@ -13,6 +13,7 @@ const (
TypeFileHandle = "file-handle"
TypeInt = "integer"
TypeItem = "item"
TypeIterator = "iterator"
TypeNumber = "number"
TypePair = "pair"
TypeString = "string"
+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)
+221
View File
@@ -0,0 +1,221 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"strings"
)
type dictIterMode int
const (
dictIterModeKeys dictIterMode = iota
dictIterModeValues
dictIterModeItems
)
type DictIterator struct {
a *DictType
count int
index int
keys []any
iterMode dictIterMode
}
type sortType int
const (
sortTypeNone sortType = iota
sortTypeAsc
sortTypeDesc
sortTypeDefault = sortTypeAsc
)
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
it.keys = make([]any, 0, len(m))
if sort == sortTypeNone {
for keyAny := range m {
it.keys = append(it.keys, keyAny)
}
} else {
scalarMap := make(map[string]any, len(m))
scalerKeys := make([]string, 0, len(m))
for keyAny := range m {
keyStr := fmt.Sprint(keyAny)
scalarMap[keyStr] = keyAny
scalerKeys = append(scalerKeys, keyStr)
}
switch sort {
case sortTypeAsc:
slices.Sort(scalerKeys)
case sortTypeDesc:
slices.Sort(scalerKeys)
slices.Reverse(scalerKeys)
}
for _, keyStr := range scalerKeys {
it.keys = append(it.keys, scalarMap[keyStr])
}
}
}
func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone
var s string
var argAny any
dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
if len(args) > 0 {
argAny = args[0]
} else {
argAny = "default"
}
if s, err = ToGoString(argAny, "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
}
if err == nil {
if len(args) > 1 {
argAny = args[1]
} else {
argAny = "default"
}
if s, err = ToGoString(argAny, "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
dictIt.iterMode = dictIterModeKeys
case "v", "value", "values":
dictIt.iterMode = dictIterModeValues
case "i", "item", "items":
dictIt.iterMode = dictIterModeItems
case "", "default":
dictIt.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
}
}
}
dictIt.makeKeys(*dict, sortType)
return dictIt, err
}
func NewMapIterator(m map[any]any) (it *DictIterator) {
it = &DictIterator{a: (*DictType)(&m), count: 0, index: -1, keys: nil}
it.makeKeys(m, sortTypeNone)
return
}
func (it *DictIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$({#%d})", l)
}
func (it *DictIterator) TypeName() string {
return "DictIterator"
}
func (it *DictIterator) HasOperation(name string) bool {
// yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
yes := slices.Contains([]string{NextName, ResetName, IndexName, CountName, CurrentName, CleanName, KeyName, ValueName}, name)
return yes
}
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case NextName:
v, err = it.Next()
case ResetName:
err = it.Reset()
case CleanName:
err = it.Clean()
case IndexName:
v = int64(it.Index())
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
case KeyName:
if it.index >= 0 && it.index < len(it.keys) {
v = it.keys[it.index]
} else {
err = io.EOF
}
case ValueName:
if it.index >= 0 && it.index < len(it.keys) {
a := *(it.a)
v = a[it.keys[it.index]]
} else {
err = io.EOF
}
default:
err = errNoOperation(name)
}
return
}
func (it *DictIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.keys) {
switch it.iterMode {
case dictIterModeKeys:
item = it.keys[it.index]
case dictIterModeValues:
a := *(it.a)
item = a[it.keys[it.index]]
case dictIterModeItems:
a := *(it.a)
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
item = newList(pair)
}
} else {
err = io.EOF
}
return
}
func (it *DictIterator) Next() (item any, err error) {
it.index++
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *DictIterator) Index() int {
return it.index
}
func (it *DictIterator) Count() int {
return it.count
}
func (it *DictIterator) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *DictIterator) Clean() error {
return nil
}
+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))
+998 -96
View File
File diff suppressed because it is too large Load Diff
+1553 -147
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -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=
+12
View File
@@ -76,6 +76,12 @@ func isFile(filePath string) bool {
func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
suffix := SHAREDLIBRARY_EXTENSION
if strings.HasSuffix(filename, ".debug") {
suffix += ".debug"
}
for _, dir := range dirList {
if dir, err = ExpandPath(dir); err != nil {
continue
@@ -84,6 +90,12 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
filePath = fullPath
break
}
subdir := strings.TrimSuffix(filename, suffix)
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-factory.go
package expr
func NewIterator(value any) (it Iterator, err error) {
if value == nil {
return NewArrayIterator([]any{}), nil
}
switch v := value.(type) {
case *ListType:
it = NewListIterator(v, nil)
case *DictType:
it, err = NewDictIterator(v, nil)
case []any:
it = NewArrayIterator(v)
case Iterator:
it = v
default:
it = NewArrayIterator([]any{value})
}
return
}
+9 -6
View File
@@ -5,7 +5,7 @@
package expr
import (
"errors"
// "errors"
"fmt"
)
@@ -21,28 +21,31 @@ const (
CountName = "count"
FilterName = "filter"
MapName = "map"
KeyName = "key"
ValueName = "value"
)
type Iterator interface {
Typer
fmt.Stringer
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
Count() int
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
type ExtIterator interface {
Iterator
Reset() error
Clean() error
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
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")
// }
+9
View File
@@ -0,0 +1,9 @@
//go:build darwin
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-darwin.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dylib"
+9
View File
@@ -0,0 +1,9 @@
//go:build linux
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-linux.go
package expr
const SHAREDLIBRARY_EXTENSION = ".so"
+9
View File
@@ -0,0 +1,9 @@
//go:build windows
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-windows.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dll"
+3 -18
View File
@@ -62,27 +62,12 @@ func NewArrayIterator(array []any) (it *ListIterator) {
return
}
func NewAnyIterator(value any) (it *ListIterator) {
if value == nil {
it = NewArrayIterator([]any{})
} else if list, ok := value.(*ListType); ok {
it = NewListIterator(list, nil)
} else if array, ok := value.([]any); ok {
it = NewArrayIterator(array)
} else if it1, ok := value.(*ListIterator); ok {
it = it1
} else {
it = NewArrayIterator([]any{value})
}
return
}
func (it *ListIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
return fmt.Sprintf("$([#%d])", l)
}
func (it *ListIterator) TypeName() string {
@@ -149,12 +134,12 @@ func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (error) {
func (it *ListIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *ListIterator) Clean() (error) {
func (it *ListIterator) Clean() error {
return nil
}
+17
View File
@@ -129,6 +129,20 @@ func (ls *ListType) contains(t *ListType) (answer bool) {
return
}
func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls1) == len(ls2) {
answer = true
for index, i1 := range *ls1 {
// if i1 != (ls2)[index] {
if reflect.DeepEqual(i1, ls2[index]) {
answer = false
break
}
}
}
return
}
func (list *ListType) indexDeepSameCmp(target any) (index int) {
var eq bool
var err error
@@ -193,3 +207,6 @@ func (list *ListType) setItem(index int64, value any) (err error) {
return
}
func (list *ListType) appendItem(value any) {
*list = append(*list, value)
}
+13 -1
View File
@@ -86,11 +86,13 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
return
}
err = nil
if ds != nil {
if len(ds) > 0 {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
@@ -117,6 +119,16 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
}
v = dc
} else {
if dictIt, ok := firstChildValue.(*DictType); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
v, err = NewDictIterator(dictIt, args)
}
} else {
err = opTerm.children[0].Errorf("the data-source must be a dictionary")
}
}
} else if list, ok := firstChildValue.(*ListType); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
+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)
}
+4 -1
View File
@@ -31,7 +31,10 @@ func evalBuiltin(ctx ExprContext, opTerm *term) (v any, err error) {
count, err = ImportInContextByGlobPattern(module)
} else {
var moduleSpec any
it := NewAnyIterator(childValue)
var it Iterator
if it, err = NewIterator(childValue); err != nil {
return
}
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(module) {
+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
+7 -3
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,6 +48,7 @@ func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
den = -den
num = -num
}
if num != 0 {
g := gcd(num, den)
num = num / g
den = den / g
@@ -57,10 +57,14 @@ func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
} else {
v = &FractionType{num, den}
}
} else {
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)
}
+2 -2
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,
}
}
@@ -34,5 +34,5 @@ func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
// init
func init() {
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
registerTermConstructor(SymDereference, newIterValueTerm)
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-map.go
package expr
import (
"fmt"
"io"
)
//-------- map term
func newMapTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priIterOp,
evalFunc: evalMap,
}
}
func evalMap(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var it Iterator
var item any
if err = opTerm.checkOperands(); err != nil {
return
}
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return
}
if it, err = NewIterator(leftValue); err != nil {
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", TypeName(leftValue))
}
values := newListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index())
ctx.SetVar("_count", it.Count())
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
values.appendItem(rightValue)
}
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
registerTermConstructor(SymKwMap, newMapTerm)
}
@@ -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
@@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
}
if it, ok := childValue.(Iterator); ok {
var namePrefix string
v, err = it.Next()
if opTerm.children[0].symbol() == SymVariable {
namePrefix = opTerm.children[0].source()
}
ctx.UnsafeSetVar(namePrefix+"_index", it.Index())
if c, err1 := it.Current(); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_current", c)
}
if it.HasOperation(KeyName) {
if k, err1 := it.CallOperation(KeyName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_key", k)
}
}
if it.HasOperation(ValueName) {
if v1, err1 := it.CallOperation(ValueName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_value", v1)
}
}
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
+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)
}
+65 -30
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"strings"
)
@@ -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,11 +36,21 @@ 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) {
@@ -60,18 +63,12 @@ func newDivideTerm(tk *Token) (inst *term) {
}
}
func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
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 = errors.New("division by zero")
err = opTerm.errDivisionByZero()
} else {
v = numAsFloat(leftValue) / d
}
@@ -80,17 +77,52 @@ func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = errors.New("division by zero")
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
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
return divValues(opTerm, leftValue, rightValue)
}
//-------- divide as float term
func newDivideAsFloatTerm(tk *Token) (inst *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
}
@@ -131,31 +163,34 @@ func newRemainderTerm(tk *Token) (inst *term) {
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)
}
+11 -1
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) {
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)
}
+24 -14
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,12 +53,24 @@ 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 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
func newMinusTerm(tk *Token) (inst *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)
+164 -123
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.IsSymbol(SymColon) {
if tk.IsOneOfA(SymColon, SymRange) {
// 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 {
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()
}
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
}
}
}
}
} else {
err = tk.Errorf("left operand of %q must be a variable or a variable expression", tk)
}
return
}
+7 -5
View File
@@ -31,11 +31,11 @@ func pluginExists(name string) (exists bool) {
func makePluginName(name string) (decorated string) {
var template string
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
template = "expr-%s-plugin.so"
template = "expr-%s-plugin%s"
} else {
template = "expr-%s-plugin.so.debug"
template = "expr-%s-plugin%s.debug"
}
decorated = fmt.Sprintf(template, name)
decorated = fmt.Sprintf(template, name, SHAREDLIBRARY_EXTENSION)
return
}
@@ -93,10 +93,12 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
func importPluginFromSearchPath(name any) (count int, err error) {
var moduleSpec any
var it Iterator
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count = 0
it := NewAnyIterator(name)
if it, err = NewIterator(name); err != nil {
return
}
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
+60 -9
View File
@@ -40,7 +40,7 @@ func DefaultTranslations() map[Symbol]Symbol {
SymKwAnd: SymAnd,
SymDoubleVertBar: SymOr,
SymKwOr: SymOr,
SymTilde: SymNot,
// 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 '^':
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)
@@ -200,14 +210,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.makeToken(SymQuote, ch)
escape = false
} else {
tk = scanner.fetchString(ch)
tk = scanner.fetchString(ch, true)
}
case '"':
if escape {
tk = scanner.makeToken(SymDoubleQuote, ch)
escape = false
} else {
tk = scanner.fetchString(ch)
tk = scanner.fetchString(ch, true)
}
case '`':
tk = scanner.makeToken(SymBackTick, ch)
@@ -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 '%':
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)
}
@@ -283,6 +315,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk.source += ")"
} else if next == '$' {
tk = scanner.moveOn(SymDoubleDollar, ch, next)
} else if next == '{' {
scanner.readChar()
if tk = scanner.fetchString('}', false); tk != nil {
tk.Sym = SymIdentifier
}
} else if next == '_' || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
scanner.readChar()
tk = scanner.fetchIdentifier(next)
} else {
tk = scanner.makeToken(SymDollar, ch)
}
@@ -458,7 +498,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)
@@ -558,7 +598,7 @@ func (scanner *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (
return
}
func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
func (scanner *scanner) fetchString(termCh byte, addQuote bool) (tk *Token) {
var err error
var ch, prev byte
var sb strings.Builder
@@ -590,13 +630,17 @@ 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)
}
} else {
txt := sb.String()
if addQuote {
tk = scanner.makeValueToken(SymString, `"`+txt+`"`, txt)
} else {
tk = scanner.makeValueToken(SymString, txt, txt)
}
}
return
}
@@ -628,9 +672,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
}
+17 -2
View File
@@ -60,8 +60,8 @@ const (
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymInsert // 52: '>>'
SymAppend // 53: '<<'
SymDoubleGreater // 52: '>>'
SymDoubleLess // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
@@ -69,8 +69,20 @@ const (
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> "}"
@@ -106,6 +119,7 @@ const (
SymKwPlugin
SymKwIn
SymKwInclude
SymKwMap
SymKwNil
SymKwUnset
)
@@ -122,6 +136,7 @@ func init() {
"FUNC": SymKwFunc,
"IN": SymKwIn,
"INCLUDE": SymKwInclude,
"MAP": SymKwMap,
"NOT": SymKwNot,
"OR": SymKwOr,
"NIL": SymKwNil,
+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)
}
+17 -11
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,32 @@ 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},
/* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 41 */ {`bool([1])`, true, nil},
/* 42 */ {`bool([])`, false, nil},
/* 43 */ {`bool({})`, false, nil},
/* 44 */ {`bool({1:"one"})`, true, nil},
/* 45 */ {`dec(false)`, float64(0), nil},
/* 46 */ {`dec(1:2)`, float64(0.5), nil},
/* 47 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 48 */ {`eval("a=3"); a`, int64(3), nil},
/* 49 */ {`int(5:2)`, int64(2), 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, 49)
runTestSuite(t, section, inputs)
}
+3 -2
View File
@@ -21,10 +21,11 @@ func TestFuncRun(t *testing.T) {
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
/* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil},
/* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`},
/* 10 */ {`builtin ["os.file", "iterator"]; it = fileReadIterator("test-file.txt"); run(it)`, nil, nil},
}
//t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
runTestSuiteSpec(t, section, inputs, 10)
// runTestSuite(t, section, inputs)
}
+6 -2
View File
@@ -5,6 +5,7 @@
package expr
import (
"io"
"testing"
)
@@ -26,10 +27,13 @@ func TestFuncOs(t *testing.T) {
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`},
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`},
/* 16 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++`, "uno", nil},
/* 17 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++;it++;it++`, nil, io.EOF.Error()},
/* 18 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it.clean`, nil, nil},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
runTestSuiteSpec(t, section, inputs, 18)
// 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: {
}
+13 -1
View File
@@ -72,6 +72,7 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
var expr Expr
var gotResult any
var gotErr error
var eq, eqDone bool
wantErr := getWantedError(input)
@@ -90,7 +91,18 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if input.wantResult != nil && gotResult != nil {
if ls1, ok := input.wantResult.(*ListType); ok {
if ls2, ok := gotResult.(*ListType); ok {
eq = ls1.Equals(*ls2)
eqDone = true
}
}
}
if !eqDone {
eq = reflect.DeepEqual(gotResult, input.wantResult)
}
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
+12 -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},
@@ -40,9 +43,11 @@ func TestExpr(t *testing.T) {
it++;
it++
`, int64(1), nil},
/* 20 */ {`a=2; ${a}`, int64(2), nil},
/* 21 */ {`$_=2; $_`, int64(2), nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 17)
// runTestSuiteSpec(t, section, inputs, 21)
runTestSuite(t, section, inputs)
}
+22 -19
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,8 +59,7 @@ 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\n 2"
got := source.ToString(MultiLine | TTY)
+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)
}
+8 -8
View File
@@ -59,7 +59,7 @@ func TestNewIterList2(t *testing.T) {
func TestNewIterList3(t *testing.T) {
list := []any{"a", "b", "c", "d"}
it := NewAnyIterator(list)
it, _ := NewIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
@@ -71,7 +71,7 @@ func TestNewIterList3(t *testing.T) {
func TestNewIterList4(t *testing.T) {
list := any(nil)
it := NewAnyIterator(list)
it, _ := NewIterator(list)
if _, err := it.Next(); err != io.EOF {
t.Errorf("error: %v", err)
}
@@ -79,7 +79,7 @@ func TestNewIterList4(t *testing.T) {
func TestNewIterList5(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
it, _ := NewIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "123" {
@@ -91,8 +91,8 @@ func TestNewIterList5(t *testing.T) {
func TestNewIterList6(t *testing.T) {
list := newListA("a", "b", "c", "d")
it1 := NewAnyIterator(list)
it := NewAnyIterator(it1)
it1, _ := NewIterator(list)
it, _ := NewIterator(it1)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
@@ -103,9 +103,9 @@ func TestNewIterList6(t *testing.T) {
}
func TestNewString(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
if s := it.String(); s != "$(#1)" {
t.Errorf("expected $(#1), got %s", s)
it, _ := NewIterator(list)
if s := it.String(); s != "$([#1])" {
t.Errorf("expected $([#1]), got %s", s)
}
}
+9 -4
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},
@@ -27,8 +27,13 @@ func TestIteratorParser(t *testing.T) {
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
/* 19 */ {`it=$({1:"one",2:"two",3:"three"}); it++`, int64(1), nil},
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
/* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil},
/* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, NewList([]any{int64(1), "one"}), nil},
/* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${_index}`, NewList([]any{int64(0), int64(1)}), nil},
}
//runTestSuiteSpec(t, section, inputs, 18)
runTestSuite(t, section, inputs)
runTestSuiteSpec(t, section, inputs, 23)
// runTestSuite(t, section, inputs)
}
+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", ".")
+23 -2
View File
@@ -14,10 +14,31 @@ 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},
/* 20 */ {`a=1; a^=2`, int64(3), nil},
/* 21 */ {`a=1; ++a`, int64(2), nil},
/* 22 */ {`a=1; --a`, int64(0), nil},
/* 23 */ {`[1,2,3] map var("_")`, []any{1, 2, 3}, nil},
/* 24 */ {`[1,2,3] map $_`, newList([]any{1, 2, 3}), nil},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
runTestSuiteSpec(t, section, inputs, 24)
// 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)
}
+32 -12
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"
@@ -30,9 +30,29 @@ func TestPluginExists(t *testing.T) {
func TestMakePluginName(t *testing.T) {
name := "json"
want := "expr-" + name + "-plugin.so"
want := "expr-" + name + "-plugin" + SHAREDLIBRARY_EXTENSION
if got := makePluginName(name); got != want {
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", name, got, want)
}
}
// func TestLoadPluginName(t *testing.T) {
// name := "json"
// // want := "expr-" + name + "-plugin
// want := 1
// os.Setenv("PLUGINS", "${HOME}/go/src/git.portale-stac.it/go")
// // os.Setenv("EXPR_PLUGIN_PATH", "${PLUGINS}/expr-json-plugin:${PLUGINS}/expr-csv-plugin")
// os.Setenv("EXPR_PLUGIN_PATH", "${PLUGINS}")
// got, err := importPluginFromSearchPath(name)
// if err != nil {
// t.Errorf("importPluginFromSearchPath(%q) failed: %v", name, err)
// return
// }
// if got != want {
// t.Errorf("importPluginFromSearchPath(%q) failed: Got: %q, Want: %q", name, got, want)
// }
// }
+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)
}
+13 -2
View File
@@ -13,22 +13,29 @@ type termPriority uint32
const (
priNone termPriority = iota
priRange
priIterOp // map, filter, digest, etc
priBut
priAssign
priInsert
priOr
priAnd
priNot
priRelational
priBitwiseOr
priBitwiseAnd
priBitwiseNot
priSum
priProduct
priFraction
priSelector
priBinShift
priSign
priFact
priIterValue
priDefault
priIncDec
priDot
priDereference
priValue
)
@@ -198,6 +205,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 +222,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
}
+9
View File
@@ -218,6 +218,15 @@ func ToGoInt(value any, description string) (i int, err error) {
return
}
func ToGoString(value any, description string) (s string, err error) {
if s, ok := value.(string); ok {
return s, nil
} else {
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
}
return
}
func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {