Compare commits

...

20 Commits

Author SHA1 Message Date
camoroso f63ff5953e graph.go: conditioned compilation by 'graph' tag 2026-05-03 07:14:30 +02:00
camoroso b9d37a5b4c kern/compare.go: added copyright comment 2026-05-03 07:13:39 +02:00
camoroso 23b8eec74a builtin-base: removed useless function unset(). See UNSET operator 2026-05-03 06:46:51 +02:00
camoroso bb6b6d17ec operator-map.go: return nil on error 2026-05-03 06:30:56 +02:00
camoroso 53acacbadf kern/common-errors.go: little changes to ErrExpectedGot() and ErrInvalidParameterValue() 2026-05-03 06:30:00 +02:00
camoroso 2ebc52891c Iterator operator: automatic temporary variables _index and _count changed with '__' and '_#' respectively. Note that, sinc '#' is not an identifier allowed char, '_#' requires himBHsnotation: -cover 2026-05-02 15:06:12 +02:00
camoroso 3b2ef7927b new operator 'groupby' 2026-05-02 14:53:19 +02:00
camoroso d5ced343c4 kern/compare.go: new function Equal(a,b) that returns true if a and b have the same value 2026-05-02 14:45:05 +02:00
camoroso 3ac8cab275 enhanced ending operator detection 2026-05-02 09:54:24 +02:00
camoroso 6c5e9db34b int-itrator: new iterator over integer ranges 2026-05-01 17:23:06 +02:00
camoroso 78871641d0 t_common_test.go: Error messages also contains the name of the section introduced by special symbol >>> 2026-05-01 17:17:40 +02:00
camoroso dacbec677a iterator interface chenged index and count members from int to tint 64 2026-05-01 17:15:18 +02:00
camoroso 75ed88915d ast.go, pasrse.go: fixed the recover routine 2026-05-01 17:03:44 +02:00
camoroso f2d1f23774 ast.go: added recover from panic in ast.Eval() 2026-05-01 07:25:05 +02:00
camoroso edd90054d7 parser.go: added recover from panic in parser.Parse() 2026-05-01 07:24:41 +02:00
camoroso 610e2df5f5 operator-dot: added support to access dict value by exprssion; example: D.key, D."key", D.expr 2026-04-30 07:06:20 +02:00
camoroso 32c0b45255 kern/dict-type.go: added GetItem() 2026-04-30 07:04:03 +02:00
camoroso 75a3f220df parser: token '()' returns error 2026-04-30 07:03:28 +02:00
camoroso 116b54836f token.go: little change to ErrorExpectedGotStringWithPrefix() 2026-04-30 07:01:02 +02:00
camoroso 8787973de0 util files moved form kern to util package 2026-04-29 11:03:36 +02:00
52 changed files with 1056 additions and 411 deletions
+13 -2
View File
@@ -5,6 +5,7 @@
package expr package expr
import ( import (
"errors"
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
@@ -106,13 +107,23 @@ func (ast *ast) Finish() {
} }
func (ast *ast) Eval(ctx kern.ExprContext) (result any, err error) { func (ast *ast) Eval(ctx kern.ExprContext) (result any, err error) {
defer func() {
if r := recover(); r != nil {
if errVal, ok := r.(error); ok {
err = errVal
} else {
err = errors.New("unexpected error while evaluating the expression")
}
}
}()
ast.Finish() ast.Finish()
if ast.root != nil { if ast.root != nil {
// initDefaultVars(ctx) // initDefaultVars(ctx)
if ast.forest != nil { if ast.forest != nil {
for _, root := range ast.forest { for _, tree := range ast.forest {
if result, err = root.Compute(ctx); err == nil { if result, err = tree.Compute(ctx); err == nil {
ctx.UnsafeSetVar(kern.ControlLastResult, result) ctx.UnsafeSetVar(kern.ControlLastResult, result)
} else { } else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err) //err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
+16 -16
View File
@@ -43,7 +43,7 @@ func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (resul
} }
func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFract(args[kern.ParamValue]) result = kern.IsFraction(args[kern.ParamValue])
return return
} }
@@ -254,18 +254,18 @@ func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
return return
} }
func unsetFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { // func unsetFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string // var varName string
var ok bool // var ok bool
if varName, ok = args[kern.ParamName].(string); !ok { // if varName, ok = args[kern.ParamName].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName]) // return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
} else { // } else {
ctx.GetParent().DeleteVar(varName) // ctx.GetParent().DeleteVar(varName)
result = nil // result = nil
} // }
return // return
} // }
//// import //// import
@@ -307,10 +307,10 @@ func ImportBuiltinsFuncs(ctx kern.ExprContext) {
NewFuncParam(kern.ParamValue), NewFuncParam(kern.ParamValue),
}) })
ctx.RegisterFunc("unset", kern.NewGolangFunctor(unsetFunc), kern.TypeAny, []kern.ExprFuncParam{ // ctx.RegisterFunc("unset", kern.NewGolangFunctor(unsetFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParam(kern.ParamName), // NewFuncParam(kern.ParamName),
NewFuncParam(kern.ParamValue), // NewFuncParam(kern.ParamValue),
}) // })
} }
func init() { func init() {
+1 -1
View File
@@ -34,7 +34,7 @@ func doImport(ctx kern.ExprContext, name string, dirList []string, it kern.Itera
var sourceFilepath string var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() { for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil { if err = checkStringParamExpected(name, v, int(it.Index())); err != nil {
break break
} }
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil { if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
+2 -2
View File
@@ -41,7 +41,7 @@ func doAdd(ctx kern.ExprContext, name string, it kern.Iterator, count, level int
return return
} }
} }
} else if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil { } else if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
break break
} }
count++ count++
@@ -116,7 +116,7 @@ func doMul(ctx kern.ExprContext, name string, it kern.Iterator, count, level int
} }
} }
} else { } else {
if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil { if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
break break
} }
} }
+4 -4
View File
@@ -17,8 +17,8 @@ const fileReadTextIteratorType = "fileReadTextIterator"
type fileReadTextIterator struct { type fileReadTextIterator struct {
osReader *osReader osReader *osReader
index int index int64
count int count int64
line string line string
autoClose bool autoClose bool
} }
@@ -38,7 +38,7 @@ func (it *fileReadTextIterator) String() string {
return fmt.Sprintf("$(%s@<nil>)", fileReadTextIteratorType) return fmt.Sprintf("$(%s@<nil>)", fileReadTextIteratorType)
} }
func (it *fileReadTextIterator) Count() int { func (it *fileReadTextIterator) Count() int64 {
return it.count return it.count
} }
@@ -62,7 +62,7 @@ func (it *fileReadTextIterator) Current() (item any, err error) {
return return
} }
func (it *fileReadTextIterator) Index() int { func (it *fileReadTextIterator) Index() int64 {
return it.index return it.index
} }
+4 -4
View File
@@ -16,8 +16,8 @@ type dataCursor struct {
ctx kern.ExprContext ctx kern.ExprContext
initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor) initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
// cursorValid bool // true if resource is nil or if clean has not yet been called // cursorValid bool // true if resource is nil or if clean has not yet been called
index int index int64
count int count int64
current any current any
lastErr error lastErr error
resource any resource any
@@ -298,10 +298,10 @@ func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF af
// return // return
// } // }
func (dc *dataCursor) Index() int { func (dc *dataCursor) Index() int64 {
return dc.index - 1 return dc.index - 1
} }
func (dc *dataCursor) Count() int { func (dc *dataCursor) Count() int64 {
return dc.count return dc.count
} }
+9 -9
View File
@@ -23,8 +23,8 @@ const (
type DictIterator struct { type DictIterator struct {
a *kern.DictType a *kern.DictType
count int count int64
index int index int64
keys []any keys []any
iterMode dictIterMode iterMode dictIterMode
} }
@@ -127,9 +127,9 @@ func NewMapIterator(m map[any]any) (it *DictIterator) {
} }
func (it *DictIterator) String() string { func (it *DictIterator) String() string {
var l = 0 var l = int64(0)
if it.a != nil { if it.a != nil {
l = len(*it.a) l = int64(len(*it.a))
} }
return fmt.Sprintf("$({#%d})", l) return fmt.Sprintf("$({#%d})", l)
} }
@@ -159,13 +159,13 @@ func (it *DictIterator) CallOperation(name string, args map[string]any) (v any,
case kern.CountName: case kern.CountName:
v = it.count v = it.count
case kern.KeyName: case kern.KeyName:
if it.index >= 0 && it.index < len(it.keys) { if it.index >= 0 && it.index < int64(len(it.keys)) {
v = it.keys[it.index] v = it.keys[it.index]
} else { } else {
err = io.EOF err = io.EOF
} }
case kern.ValueName: case kern.ValueName:
if it.index >= 0 && it.index < len(it.keys) { if it.index >= 0 && it.index < int64(len(it.keys)) {
a := *(it.a) a := *(it.a)
v = a[it.keys[it.index]] v = a[it.keys[it.index]]
} else { } else {
@@ -178,7 +178,7 @@ func (it *DictIterator) CallOperation(name string, args map[string]any) (v any,
} }
func (it *DictIterator) Current() (item any, err error) { func (it *DictIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.keys) { if it.index >= 0 && it.index < int64(len(it.keys)) {
switch it.iterMode { switch it.iterMode {
case dictIterModeKeys: case dictIterModeKeys:
item = it.keys[it.index] item = it.keys[it.index]
@@ -204,11 +204,11 @@ func (it *DictIterator) Next() (item any, err error) {
return return
} }
func (it *DictIterator) Index() int { func (it *DictIterator) Index() int64 {
return it.index return it.index
} }
func (it *DictIterator) Count() int { func (it *DictIterator) Count() int64 {
return it.count return it.count
} }
+2
View File
@@ -1,6 +1,8 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
//go:build graph
// graph.go // graph.go
package expr package expr
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
) )
func EvalString(ctx kern.ExprContext, source string) (result any, err error) { func EvalString(ctx kern.ExprContext, source string) (result any, err error) {
@@ -38,7 +39,7 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
func EvalStringV(source string, args []Arg) (result any, err error) { func EvalStringV(source string, args []Arg) (result any, err error) {
ctx := NewSimpleStoreWithoutGlobalContext() ctx := NewSimpleStoreWithoutGlobalContext()
for _, arg := range args { for _, arg := range args {
if kern.IsFunc(arg.Value) { if util.IsFunc(arg.Value) {
if f, ok := arg.Value.(kern.FuncTemplate); ok { if f, ok := arg.Value.(kern.FuncTemplate); ok {
functor := kern.NewGolangFunctor(f) functor := kern.NewGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1) // ctx.RegisterFunc(arg.Name, functor, 0, -1)
+3 -2
View File
@@ -13,6 +13,7 @@ import (
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
) )
const ( const (
@@ -85,7 +86,7 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
} }
for _, dir := range dirList { for _, dir := range dirList {
if dir, err = kern.ExpandPath(dir); err != nil { if dir, err = util.ExpandPath(dir); err != nil {
continue continue
} }
if fullPath := path.Join(dir, filename); isFile(fullPath) { if fullPath := path.Join(dir, filename); isFile(fullPath) {
@@ -108,7 +109,7 @@ func isPathRelative(filePath string) bool {
} }
func makeFilepath(filename string, dirList []string) (filePath string, err error) { func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if filename, err = kern.ExpandPath(filename); err != nil { if filename, err = util.ExpandPath(filename); err != nil {
return return
} }
+137
View File
@@ -0,0 +1,137 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// int-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type IntIterator struct {
count int64
index int64
start int64
stop int64
step int64
}
func NewIntIterator(args []any) (it *IntIterator, err error) {
var argc int = 0
if args != nil {
argc = len(args)
}
it = &IntIterator{count: 0, index: -1, start: 0, stop: 0, step: 1}
if argc >= 1 {
if it.stop, err = kern.ToGoInt64(args[0], "start index"); err != nil {
return
}
if argc >= 2 {
it.start = it.stop
if it.stop, err = kern.ToGoInt64(args[1], "stop index"); err != nil {
return
}
if argc >= 3 {
if it.step, err = kern.ToGoInt64(args[2], "step"); err != nil {
return
}
} else if it.start > it.stop {
it.step = -1
}
}
}
if it.step == 0 {
err = fmt.Errorf("step cannot be zero")
return
}
if it.start < it.stop && it.step < 0 {
err = fmt.Errorf("step cannot be negative when start < stop")
return
}
if it.start > it.stop && it.step > 0 {
err = fmt.Errorf("step cannot be positive when start > stop")
return
}
it.Reset()
return
}
func (it *IntIterator) String() string {
return fmt.Sprintf("$(%d..%d..%d)", it.start, it.stop, it.step)
}
func (it *IntIterator) TypeName() string {
return "IntIterator"
}
func (it *IntIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *IntIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *IntIterator) Current() (item any, err error) {
if it.start <= it.stop {
if it.index >= it.start && it.index < it.stop {
item = it.index
} else {
err = io.EOF
}
} else {
if it.index > it.stop && it.index <= it.start {
item = it.index
} else {
err = io.EOF
}
}
return
}
func (it *IntIterator) Next() (item any, err error) {
it.index += it.step
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *IntIterator) Index() int64 {
return it.index
}
func (it *IntIterator) Count() int64 {
return it.count
}
func (it *IntIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *IntIterator) Clean() error {
return nil
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// string.go
package kern
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func ToBool(v any) (b bool, ok bool) {
ok = true
switch x := v.(type) {
case string:
b = len(x) > 0
case float64:
b = x != 0.0
case int64:
b = x != 0
case bool:
b = x
default:
ok = false
}
return
}
+2 -2
View File
@@ -34,7 +34,7 @@ func ErrCantConvert(funcName string, value any, kind string) error {
} }
func ErrExpectedGot(funcName string, kind string, value any) error { func ErrExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s(): expected %s, got %s (%v)", funcName, kind, TypeName(value), value) return fmt.Errorf("%s(): expected %s, got %s (%#v)", funcName, kind, TypeName(value), value)
} }
func ErrFuncDivisionByZero(funcName string) error { func ErrFuncDivisionByZero(funcName string) error {
@@ -52,7 +52,7 @@ func ErrFuncDivisionByZero(funcName string) error {
// } // }
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error { func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName) return fmt.Errorf("%s(): invalid value %s (%#v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
} }
func undefArticle(s string) (article string) { func undefArticle(s string) (article string) {
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
package kern
import "reflect"
func Equal(value1, value2 any) (equal bool) {
if value1 == nil && value2 == nil {
equal = true
} else if value1 == nil || value2 == nil {
equal = false
} else if IsBool(value1) && IsBool(value2) {
equal = value1.(bool) == value2.(bool)
} else if IsList(value1) && IsList(value2) {
ls1 := value1.(*ListType)
ls2 := value2.(*ListType)
equal = ls1.Equals(*ls2)
} else if IsDict(value1) && IsDict(value2) {
d1 := value1.(*DictType)
d2 := value2.(*DictType)
equal = d1.Equals(*d2)
} else if IsInteger(value1) && IsInteger(value2) {
equal = value1.(int64) == value2.(int64)
} else if IsString(value1) && IsString(value2) {
equal = value1.(string) == value2.(string)
} else if IsFloat(value1) && IsFloat(value2) {
equal = value1.(float64) == value2.(float64)
} else if IsNumOrFract(value1) && IsNumOrFract(value2) {
if eq, err := CmpAnyFract(value1, value2); err == nil {
equal = eq == 0
}
} else if !reflect.DeepEqual(value1, value2) {
equal = false
}
return
}
+29 -2
View File
@@ -12,6 +12,11 @@ import (
type DictType map[any]any type DictType map[any]any
func IsDict(v any) (ok bool) {
_, ok = v.(*DictType)
return ok
}
func MakeDict() (dict *DictType) { func MakeDict() (dict *DictType) {
d := make(DictType) d := make(DictType)
dict = &d dict = &d
@@ -138,6 +143,15 @@ func (dict *DictType) HasKey(target any) (ok bool) {
return return
} }
func (dict *DictType) SetItem(key any, value any) {
(*dict)[key] = value
}
func (dict *DictType) GetItem(key any) (value any, exists bool) {
value, exists = (*dict)[key]
return
}
func (dict *DictType) Clone() (c *DictType) { func (dict *DictType) Clone() (c *DictType) {
c = newDict(nil) c = newDict(nil)
for k, v := range *dict { for k, v := range *dict {
@@ -154,8 +168,21 @@ func (dict *DictType) Merge(second *DictType) {
} }
} }
func (dict *DictType) SetItem(key any, value any) (err error) { func (dict *DictType) Equals(dict2 DictType) (answer bool) {
(*dict)[key] = value if dict2 != nil && len(*dict) == len(dict2) {
answer = true
for key, value1 := range *dict {
if value2, exists := dict2.GetItem(key); exists {
if !Equal(value1, value2) {
answer = false
break
}
} else {
answer = false
break
}
}
}
return return
} }
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// float.go
package kern
func IsFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func AnyFloat(v any) (float float64, ok bool) {
ok = true
switch floatval := v.(type) {
case float32:
float = float64(floatval)
case float64:
float = floatval
default:
ok = false
}
return
}
+18 -3
View File
@@ -220,11 +220,12 @@ func anyToFract(v any) (f *FractionType, err error) {
if f, ok = v.(*FractionType); !ok { if f, ok = v.(*FractionType); !ok {
if n, ok := v.(int64); ok { if n, ok := v.(int64); ok {
f = intToFraction(n) f = intToFraction(n)
} } else if dec, ok := v.(float64); ok {
} f, err = Float64ToFraction(dec)
if f == nil { } else {
err = ErrExpectedGot("fract", TypeFraction, v) err = ErrExpectedGot("fract", TypeFraction, v)
} }
}
return return
} }
@@ -273,6 +274,7 @@ func CmpAnyFract(af1, af2 any) (result int, err error) {
// =0 if af1 == af2 // =0 if af1 == af2
// >0 if af1 > af2 // >0 if af1 > af2
func cmpFract(f1, f2 *FractionType) (result int) { func cmpFract(f1, f2 *FractionType) (result int) {
if f1 != nil && f2 != nil {
f2.num = -f2.num f2.num = -f2.num
f := SumFract(f1, f2) f := SumFract(f1, f2)
if f.num < 0 { if f.num < 0 {
@@ -282,6 +284,7 @@ func cmpFract(f1, f2 *FractionType) (result int) {
} else { } else {
result = 0 result = 0
} }
}
return return
} }
@@ -367,3 +370,15 @@ func IsFraction(v any) (ok bool) {
_, ok = v.(*FractionType) _, ok = v.(*FractionType)
return ok return ok
} }
// func IsFract(v any) (ok bool) {
// _, ok = v.(*FractionType)
// return ok
// }
func IsRational(v any) (ok bool) {
if _, ok = v.(*FractionType); !ok {
_, ok = v.(int64)
}
return ok
}
+5
View File
@@ -14,6 +14,11 @@ type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (resul
type DeepFuncTemplate func(a, b any) (eq bool, err error) type DeepFuncTemplate func(a, b any) (eq bool, err error)
func IsFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
// ---- Common functor definition // ---- Common functor definition
type BaseFunctor struct { type BaseFunctor struct {
info ExprFunc info ExprFunc
+7 -2
View File
@@ -30,8 +30,8 @@ type Iterator interface {
fmt.Stringer fmt.Stringer
Next() (item any, err error) // must return io.EOF after the last item Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error) Current() (item any, err error)
Index() int Index() int64
Count() int Count() int64
HasOperation(name string) bool HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error) CallOperation(name string, args map[string]any) (value any, err error)
} }
@@ -45,3 +45,8 @@ type ExtIterator interface {
func ErrNoOperation(name string) error { func ErrNoOperation(name string) error {
return fmt.Errorf("no %s() function defined in the data-source", name) return fmt.Errorf("no %s() function defined in the data-source", name)
} }
func IsIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
+27 -19
View File
@@ -12,6 +12,11 @@ import (
type ListType []any type ListType []any
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
}
func NewListA(listAny ...any) (list *ListType) { func NewListA(listAny ...any) (list *ListType) {
if listAny == nil { if listAny == nil {
listAny = []any{} listAny = []any{}
@@ -45,13 +50,13 @@ func ListFromStrings(stringList []string) (list *ListType) {
return return
} }
func (ls *ListType) ToString(opt FmtOpt) (s string) { func (dict *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt) indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt) flags := GetFormatFlags(opt)
var sb strings.Builder var sb strings.Builder
sb.WriteByte('[') sb.WriteByte('[')
if len(*ls) > 0 { if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1) innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1) nest := strings.Repeat(" ", indent+1)
@@ -59,7 +64,7 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
sb.WriteByte('\n') sb.WriteByte('\n')
sb.WriteString(nest) sb.WriteString(nest)
} }
for i, item := range []any(*ls) { for i, item := range []any(*dict) {
if i > 0 { if i > 0 {
if flags&MultiLine != 0 { if flags&MultiLine != 0 {
sb.WriteString(",\n") sb.WriteString(",\n")
@@ -91,19 +96,19 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
return return
} }
func (ls *ListType) String() string { func (dict *ListType) String() string {
return ls.ToString(0) return dict.ToString(0)
} }
func (ls *ListType) TypeName() string { func (dict *ListType) TypeName() string {
return "list" return "list"
} }
func (ls *ListType) Contains(t *ListType) (answer bool) { func (dict *ListType) Contains(t *ListType) (answer bool) {
if len(*ls) >= len(*t) { if len(*dict) >= len(*t) {
answer = true answer = true
for _, item := range *t { for _, item := range *t {
if answer = ls.IndexDeepSameCmp(item) >= 0; !answer { if answer = dict.IndexDeepSameCmp(item) >= 0; !answer {
break break
} }
} }
@@ -115,8 +120,11 @@ func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls1) == len(ls2) { if ls2 != nil && len(*ls1) == len(ls2) {
answer = true answer = true
for index, i1 := range *ls1 { for index, i1 := range *ls1 {
// if i1 != (ls2)[index] { // if !reflect.DeepEqual(i1, ls2[index]) {
if !reflect.DeepEqual(i1, ls2[index]) { // answer = false
// break
// }
if !Equal(i1, ls2[index]) {
answer = false answer = false
break break
} }
@@ -125,11 +133,11 @@ func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
return return
} }
func (list *ListType) IndexDeepSameCmp(target any) (index int) { func (dict *ListType) IndexDeepSameCmp(target any) (index int) {
var eq bool var eq bool
var err error var err error
index = -1 index = -1
for i, item := range *list { for i, item := range *dict {
if eq, err = deepSame(item, target, SameContent); err != nil { if eq, err = deepSame(item, target, SameContent); err != nil {
break break
} else if eq { } else if eq {
@@ -180,15 +188,15 @@ func deepSame(a, b any, deepCmp DeepFuncTemplate) (eq bool, err error) {
return return
} }
func (list *ListType) SetItem(index int64, value any) (err error) { func (dict *ListType) SetItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*list)) { if index >= 0 && index < int64(len(*dict)) {
(*list)[index] = value (*dict)[index] = value
} else { } else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*list)-1) err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*dict)-1)
} }
return return
} }
func (list *ListType) AppendItem(value any) { func (dict *ListType) AppendItem(value any) {
*list = append(*list, value) *dict = append(*dict, value)
} }
+88
View File
@@ -0,0 +1,88 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// number.go
package kern
import (
"fmt"
)
func IsInteger(v any) (ok bool) {
_, ok = v.(int64)
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}
func IsNumOrFract(v any) (ok bool) {
return IsFloat(v) || IsInteger(v) || IsFraction(v)
}
func IsNumberString(v any) (ok bool) {
return IsString(v) || IsNumber(v)
}
func NumAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
if fract, ok := v.(*FractionType); ok {
f = fract.ToFloat()
} else {
i, _ := v.(int64)
f = float64(i)
}
}
return
}
func AnyInteger(v any) (i int64, ok bool) {
ok = true
switch intval := v.(type) {
case int:
i = int64(intval)
case uint8:
i = int64(intval)
case uint16:
i = int64(intval)
case uint64:
i = int64(intval)
case uint32:
i = int64(intval)
case int8:
i = int64(intval)
case int16:
i = int64(intval)
case int32:
i = int64(intval)
case int64:
i = intval
default:
ok = false
}
return
}
func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}
func ToGoInt64(value any, description string) (i int64, err error) {
if valueInt64, ok := value.(int64); ok {
i = valueInt64
} else if valueInt, ok := value.(int); ok {
i = int64(valueInt)
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// string.go
package kern
import (
"fmt"
)
func IsString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
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
}
-232
View File
@@ -1,232 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils.go
package kern
import (
"fmt"
"reflect"
)
func IsString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
func IsInteger(v any) (ok bool) {
_, ok = v.(int64)
return ok
}
func IsFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
}
func IsDict(v any) (ok bool) {
_, ok = v.(*DictType)
return ok
}
func IsFract(v any) (ok bool) {
_, ok = v.(*FractionType)
return ok
}
func IsRational(v any) (ok bool) {
if _, ok = v.(*FractionType); !ok {
_, ok = v.(int64)
}
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}
func IsNumOrFract(v any) (ok bool) {
return IsFloat(v) || IsInteger(v) || IsFraction(v)
}
func IsNumberString(v any) (ok bool) {
return IsString(v) || IsNumber(v)
}
func IsFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
func IsIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
func NumAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
if fract, ok := v.(*FractionType); ok {
f = fract.ToFloat()
} else {
i, _ := v.(int64)
f = float64(i)
}
}
return
}
func ToBool(v any) (b bool, ok bool) {
ok = true
switch x := v.(type) {
case string:
b = len(x) > 0
case float64:
b = x != 0.0
case int64:
b = x != 0
case bool:
b = x
default:
ok = false
}
return
}
func IsFunc(v any) bool {
return reflect.TypeOf(v).Kind() == reflect.Func
}
func AnyInteger(v any) (i int64, ok bool) {
ok = true
switch intval := v.(type) {
case int:
i = int64(intval)
case uint8:
i = int64(intval)
case uint16:
i = int64(intval)
case uint64:
i = int64(intval)
case uint32:
i = int64(intval)
case int8:
i = int64(intval)
case int16:
i = int64(intval)
case int32:
i = int64(intval)
case int64:
i = intval
default:
ok = false
}
return
}
func FromGenericAny(v any) (exprAny any, ok bool) {
if v != nil {
if exprAny, ok = v.(bool); ok {
return
}
if exprAny, ok = v.(string); ok {
return
}
if exprAny, ok = AnyInteger(v); ok {
return
}
if exprAny, ok = AnyFloat(v); ok {
return
}
if exprAny, ok = v.(*DictType); ok {
return
}
if exprAny, ok = v.(*ListType); ok {
return
}
}
return
}
func AnyFloat(v any) (float float64, ok bool) {
ok = true
switch floatval := v.(type) {
case float32:
float = float64(floatval)
case float64:
float = floatval
default:
ok = false
}
return
}
func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
for k, v := range source {
dest[k] = v
}
return dest
}
// func CloneMap[K comparable, V any](source map[K]V) map[K]V {
// dest := make(map[K]V, len(source))
// return CopyMap(dest, source)
// }
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
// fmt.Printf("--- Clone with filter %p\n", filter)
if filter == nil {
return CopyMap(dest, source)
} else {
for k, v := range source {
if filter(k) {
// fmt.Printf("\tClone var %q\n", k)
dest[k] = v
}
}
}
return dest
}
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
dest := make(map[K]V, len(source))
return CopyFilteredMap(dest, source, filter)
}
func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
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 {
result[i] = fn(t)
}
return result
}
+16 -16
View File
@@ -14,36 +14,36 @@ import (
type ListIterator struct { type ListIterator struct {
a *kern.ListType a *kern.ListType
count int count int64
index int index int64
start int start int64
stop int stop int64
step int step int64
} }
func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) { func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
var argc int = 0 var argc int = 0
listLen := len(([]any)(*list)) listLen := int64(len(([]any)(*list)))
if args != nil { if args != nil {
argc = len(args) argc = len(args)
} }
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1} it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 { if argc >= 1 {
if i, err := kern.ToGoInt(args[0], "start index"); err == nil { if i, err := kern.ToGoInt64(args[0], "start index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.start = i it.start = i
} }
if argc >= 2 { if argc >= 2 {
if i, err := kern.ToGoInt(args[1], "stop index"); err == nil { if i, err := kern.ToGoInt64(args[1], "stop index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.stop = i it.stop = i
} }
if argc >= 3 { if argc >= 3 {
if i, err := kern.ToGoInt(args[2], "step"); err == nil { if i, err := kern.ToGoInt64(args[2], "step"); err == nil {
if i < 0 { if i < 0 {
i = -i i = -i
} }
@@ -61,14 +61,14 @@ func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
} }
func NewArrayIterator(array []any) (it *ListIterator) { func NewArrayIterator(array []any) (it *ListIterator) {
it = &ListIterator{a: (*kern.ListType)(&array), count: 0, index: -1, start: 0, stop: len(array) - 1, step: 1} it = &ListIterator{a: (*kern.ListType)(&array), count: 0, index: -1, start: 0, stop: int64(len(array)) - 1, step: 1}
return return
} }
func (it *ListIterator) String() string { func (it *ListIterator) String() string {
var l = 0 var l = int64(0)
if it.a != nil { if it.a != nil {
l = len(*it.a) l = int64(len(*it.a))
} }
return fmt.Sprintf("$([#%d])", l) return fmt.Sprintf("$([#%d])", l)
} }
@@ -106,13 +106,13 @@ func (it *ListIterator) CallOperation(name string, args map[string]any) (v any,
func (it *ListIterator) Current() (item any, err error) { func (it *ListIterator) Current() (item any, err error) {
a := *(it.a) a := *(it.a)
if it.start <= it.stop { if it.start <= it.stop {
if it.stop < len(a) && it.index >= it.start && it.index <= it.stop { if it.stop < int64(len(a)) && it.index >= it.start && it.index <= it.stop {
item = a[it.index] item = a[it.index]
} else { } else {
err = io.EOF err = io.EOF
} }
} else { } else {
if it.start < len(a) && it.index >= it.stop && it.index <= it.start { if it.start < int64(len(a)) && it.index >= it.stop && it.index <= it.start {
item = a[it.index] item = a[it.index]
} else { } else {
err = io.EOF err = io.EOF
@@ -130,11 +130,11 @@ func (it *ListIterator) Next() (item any, err error) {
return return
} }
func (it *ListIterator) Index() int { func (it *ListIterator) Index() int64 {
return it.index return it.index
} }
func (it *ListIterator) Count() int { func (it *ListIterator) Count() int64 {
return it.count return it.count
} }
+5
View File
@@ -136,6 +136,11 @@ func evalIterator(ctx kern.ExprContext, opTerm *term) (v any, err error) {
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil { if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
v = NewListIterator(list, args) v = NewListIterator(list, args)
} }
} else if intVal, ok := firstChildValue.(int64); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, intVal); err == nil {
v, err = NewIntIterator(args)
}
} else { } else {
var list []any var list []any
if list, err = evalSibling(ctx, opTerm.children, firstChildValue); err == nil { if list, err = evalSibling(ctx, opTerm.children, firstChildValue); err == nil {
+3 -2
View File
@@ -6,6 +6,7 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
) )
//-------- assign term //-------- assign term
@@ -48,7 +49,7 @@ func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *ter
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue)) err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
} }
case *kern.DictType: case *kern.DictType:
err = collection.SetItem(keyValue, value) collection.SetItem(keyValue, value)
default: default:
err = collectionTerm.Errorf("collection expected") err = collectionTerm.Errorf("collection expected")
} }
@@ -84,7 +85,7 @@ func evalAssign(ctx kern.ExprContext, opTerm *term) (v any, err error) {
if info := functor.GetFunc(); info != nil { if info := functor.GetFunc(); info != nil {
ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params()) ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok { } else if funcDef, ok := functor.(*exprFunctor); ok {
paramSpecs := kern.ForAll(funcDef.params, func(p kern.ExprFuncParam) kern.ExprFuncParam { return p }) paramSpecs := util.ForAll(funcDef.params, func(p kern.ExprFuncParam) kern.ExprFuncParam { return p })
ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, paramSpecs) ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, paramSpecs)
} else { } else {
+4 -4
View File
@@ -43,8 +43,8 @@ func evalDigest(ctx kern.ExprContext, opTerm *term) (v any, err error) {
lastValue = nil lastValue = nil
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item) ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index()) ctx.SetVar("__", it.Index())
ctx.SetVar("_count", it.Count()) ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.children[1].Compute(ctx); err == nil {
if rightValue == nil { if rightValue == nil {
break break
@@ -52,8 +52,8 @@ func evalDigest(ctx kern.ExprContext, opTerm *term) (v any, err error) {
lastValue = rightValue lastValue = rightValue
} }
} }
ctx.DeleteVar("_count") ctx.DeleteVar("_#")
ctx.DeleteVar("_index") ctx.DeleteVar("__")
ctx.DeleteVar("_") ctx.DeleteVar("_")
if err != nil { if err != nil {
break break
+16
View File
@@ -44,6 +44,22 @@ func evalDot(ctx kern.ExprContext, opTerm *term) (v any, err error) {
} else { } else {
err = indexTerm.tk.ErrorExpectedGot("identifier") err = indexTerm.tk.ErrorExpectedGot("identifier")
} }
case *kern.DictType:
var ok bool
s := opTerm.children[1].symbol()
if s == SymVariable || s == SymString {
src := opTerm.children[1].Source()
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1]
}
if v, ok = unboxedValue.GetItem(src); !ok {
err = opTerm.errKeyNotFound(src)
}
} else if rightValue, err = opTerm.children[1].Compute(ctx); err == nil {
if v, ok = unboxedValue.GetItem(rightValue); !ok {
err = opTerm.errKeyNotFound(rightValue)
}
}
default: default:
if rightValue, err = opTerm.children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.children[1].Compute(ctx); err == nil {
err = opTerm.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
+4 -4
View File
@@ -43,8 +43,8 @@ func evalFilter(ctx kern.ExprContext, opTerm *term) (v any, err error) {
values := kern.NewListA() values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item) ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index()) ctx.SetVar("__", it.Index())
ctx.SetVar("_count", it.Count()) ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.children[1].Compute(ctx); err == nil {
if success, valid := kern.ToBool(rightValue); valid { if success, valid := kern.ToBool(rightValue); valid {
if success { if success {
@@ -54,8 +54,8 @@ func evalFilter(ctx kern.ExprContext, opTerm *term) (v any, err error) {
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue) err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
} }
} }
ctx.DeleteVar("_count") ctx.DeleteVar("_#")
ctx.DeleteVar("_index") ctx.DeleteVar("__")
ctx.DeleteVar("_") ctx.DeleteVar("_")
if err != nil { if err != nil {
break break
+105
View File
@@ -0,0 +1,105 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-groupby.go
package expr
import (
"fmt"
"io"
"strconv"
"git.portale-stac.it/go-pkg/expr/kern"
)
//-------- group by term
func newGroupByTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priIterOp,
evalFunc: evalGroupBy,
}
}
func evalGroupBy(ctx kern.ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var it kern.Iterator
var item any
var sKey string
var keyByIndex bool
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", kern.TypeName(leftValue))
}
rightTk := opTerm.children[1].tk
if rightTk.IsSymbol(SymVariable) && rightTk.source == "__" {
keyByIndex = true
} else if rightValue, err = opTerm.children[1].Compute(ctx); err != nil {
return
} else if kern.IsString(rightValue) {
sKey = rightValue.(string)
} else {
return nil, fmt.Errorf("right operand of GROUPBY must be a string or identifier '__'; got %s", kern.TypeName(rightValue))
}
values := kern.MakeDict()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
var sItemKey string
if d, ok := item.(*kern.DictType); ok {
if keyByIndex || len(sKey) == 0 {
sItemKey = strconv.Itoa(int(it.Index()))
} else if d.HasKey(sKey) {
if keyValue, exists := d.GetItem(sKey); exists {
sItemKey = fmt.Sprintf("%v", keyValue)
} else {
sItemKey = "_"
}
} else {
sItemKey = "_"
}
} else {
sItemKey = strconv.Itoa(int(it.Index()))
}
var ls *kern.ListType
if lsAny, exists := values.GetItem(sItemKey); exists && lsAny != nil {
ls = lsAny.(*kern.ListType)
}
if ls == nil {
ls = kern.NewListA()
}
ls.AppendItem(item)
values.SetItem(sItemKey, ls)
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
registerTermConstructor(SymKwGroupBy, newGroupByTerm)
}
+6 -4
View File
@@ -43,13 +43,13 @@ func evalMap(ctx kern.ExprContext, opTerm *term) (v any, err error) {
values := kern.NewListA() values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item) ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index()) ctx.SetVar("__", it.Index())
ctx.SetVar("_count", it.Count()) ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.children[1].Compute(ctx); err == nil {
values.AppendItem(rightValue) values.AppendItem(rightValue)
} }
ctx.DeleteVar("_count") ctx.DeleteVar("_#")
ctx.DeleteVar("_index") ctx.DeleteVar("__")
ctx.DeleteVar("_") ctx.DeleteVar("_")
if err != nil { if err != nil {
break break
@@ -58,7 +58,9 @@ func evalMap(ctx kern.ExprContext, opTerm *term) (v any, err error) {
if err == io.EOF { if err == io.EOF {
err = nil err = nil
} }
if err == nil {
v = values v = values
}
return return
} }
+13 -12
View File
@@ -382,6 +382,15 @@ func (parser *parser) parseItem(scanner *scanner, ctx parserContext, termSymbols
} }
func (parser *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) { func (parser *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
defer func() {
if r := recover(); r != nil {
if errVal, ok := r.(error); ok {
err = errVal
} else {
err = errors.New("unexpected error while parsing the expression")
}
}
}()
termSymbols = append(termSymbols, SymEos) termSymbols = append(termSymbols, SymEos)
return parser.parseGeneral(scanner, allowMultiExpr, termSymbols...) return parser.parseGeneral(scanner, allowMultiExpr, termSymbols...)
} }
@@ -455,15 +464,6 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
//fmt.Println("Token:", tk) //fmt.Println("Token:", tk)
if firstToken { if firstToken {
changePrefix(tk) 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 firstToken = false
} }
@@ -471,12 +471,13 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
case SymOpenRound: case SymOpenRound:
var subTree *ast var subTree *ast
if subTree, err = parser.parseGeneral(scanner, ctx, SymClosedRound); err == nil { if subTree, err = parser.parseGeneral(scanner, ctx, SymClosedRound); err == nil {
if subTree.root == nil {
err = tk.ErrorExpectedGotString("expression", "()")
} else {
exprTerm := newExprTerm(subTree.root) exprTerm := newExprTerm(subTree.root)
err = tree.addTerm(exprTerm) err = tree.addTerm(exprTerm)
currentTerm = exprTerm currentTerm = exprTerm
// subTree.root.priority = priValue }
// err = tree.addTerm(newExprTerm(subTree.root))
// currentTerm = subTree.root
} }
case SymFuncCall: case SymFuncCall:
var funcCallTerm *term var funcCallTerm *term
+4 -3
View File
@@ -9,6 +9,7 @@ import (
"slices" "slices"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
// "strings" // "strings"
) )
@@ -61,8 +62,8 @@ func (ctx *SimpleStore) GetGlobal() (globalCtx kern.ExprContext) {
func (ctx *SimpleStore) Clone() kern.ExprContext { func (ctx *SimpleStore) Clone() kern.ExprContext {
clone := &SimpleStore{ clone := &SimpleStore{
global: ctx.global, global: ctx.global,
varStore: kern.CloneFilteredMap(ctx.varStore, filterRefName), varStore: util.CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: kern.CloneFilteredMap(ctx.funcStore, filterRefName), funcStore: util.CloneFilteredMap(ctx.funcStore, filterRefName),
} }
return clone return clone
} }
@@ -138,7 +139,7 @@ func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
func (ctx *SimpleStore) SetVar(varName string, value any) { func (ctx *SimpleStore) SetVar(varName string, value any) {
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value) // fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
if allowedValue, ok := kern.FromGenericAny(value); ok { if allowedValue, ok := util.FromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue ctx.varStore[varName] = allowedValue
} else { } else {
panic(fmt.Errorf("unsupported type %T of value %v", value, value)) panic(fmt.Errorf("unsupported type %T of value %v", value, value))
+31 -2
View File
@@ -139,6 +139,7 @@ func init() {
SymKwFilter: {"filter", symClassOperator, posInfix}, SymKwFilter: {"filter", symClassOperator, posInfix},
SymKwDigest: {"digest", symClassOperator, posInfix}, SymKwDigest: {"digest", symClassOperator, posInfix},
SymKwJoin: {"join", symClassOperator, posInfix}, SymKwJoin: {"join", symClassOperator, posInfix},
SymKwGroupBy: {"groupby", symClassOperator, posInfix},
SymKwFunc: {"func(", symClassDeclaration, posPrefix}, SymKwFunc: {"func(", symClassDeclaration, posPrefix},
SymKwBuiltin: {"builtin", symClassOperator, posPrefix}, SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
SymKwPlugin: {"plugin", symClassOperator, posPrefix}, SymKwPlugin: {"plugin", symClassOperator, posPrefix},
@@ -182,21 +183,49 @@ func StringEndsWithOperator(s string) bool {
return endingOperator(s) != SymNone 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
// }
func endingOperator(s string) (sym Symbol) { func endingOperator(s string) (sym Symbol) {
var matchLength = 0 var matchLength = 0
var repr string
sym = SymNone sym = SymNone
lower := strings.TrimRight(strings.ToLower(s), " \t") lower := strings.TrimRight(strings.ToLower(s), " \t")
for symbol, spec := range symbolMap { for symbol, spec := range symbolMap {
if len(spec.repr) > matchLength || repr == spec.repr {
if strings.HasSuffix(lower, spec.repr) { if strings.HasSuffix(lower, spec.repr) {
if len(spec.repr) > matchLength { if isNotEndingSymbol(spec) && repr != spec.repr {
repr = spec.repr
matchLength = len(spec.repr) matchLength = len(spec.repr)
if spec.kind == symClassOperator && (spec.opType == posInfix || spec.opType == posPrefix) {
sym = symbol sym = symbol
} else { } else {
sym = SymNone sym = SymNone
break
// matchLength = 0
} }
} }
} }
} }
return return
} }
func isNotEndingSymbol(spec symbolSpec) bool {
return (spec.kind == symClassOperator && (spec.opType == posInfix || spec.opType == posPrefix)) ||
(spec.kind == symClassParenthesis && spec.opType == posPrefix)
}
+2
View File
@@ -122,6 +122,7 @@ const (
SymKwMap SymKwMap
SymKwFilter SymKwFilter
SymKwDigest SymKwDigest
SymKwGroupBy
SymKwJoin SymKwJoin
SymKwNil SymKwNil
SymKwUnset SymKwUnset
@@ -147,5 +148,6 @@ func init() {
"UNSET": SymKwUnset, "UNSET": SymKwUnset,
"DIGEST": SymKwDigest, "DIGEST": SymKwDigest,
"JOIN": SymKwJoin, "JOIN": SymKwJoin,
"GROUPBY": SymKwGroupBy,
} }
} }
+53
View File
@@ -72,3 +72,56 @@ func TestFuncBase(t *testing.T) {
// runTestSuiteSpec(t, section, inputs, 49) // runTestSuiteSpec(t, section, inputs, 49)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
func TestFuncBaseString(t *testing.T) {
section := "Builtin-Base-String"
inputs := []inputType{
/* 1 */ {`string(3)`, "3", nil},
/* 2 */ {`string(3.5)`, "3.5", nil},
/* 3 */ {`string(true)`, "true", nil},
/* 4 */ {`string(false)`, "false", nil},
/* 5 */ {`string("123")`, "123", nil},
/* 6 */ {`string(1:2)`, "1:2", nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 49)
runTestSuite(t, section, inputs)
}
func TestFuncBaseFraction(t *testing.T) {
section := "Builtin-Base-Fraction"
inputs := []inputType{
/* 1 */ {`fract(-0.5)`, kern.NewFraction(-1, 2), nil},
/* 2 */ {`fract("")`, (*kern.FractionType)(nil), `bad syntax`},
/* 3 */ {`fract("-1")`, kern.NewFraction(-1, 1), nil},
/* 4 */ {`fract("+1")`, kern.NewFraction(1, 1), nil},
/* 5 */ {`fract("1a")`, (*kern.FractionType)(nil), `strconv.ParseInt: parsing "1a": invalid syntax`},
/* 6 */ {`fract(1,0)`, nil, `fract(): division by zero`},
/* 7 */ {`fract(5, "1")`, nil, `fract(): expected integer, got string ("1")`},
/* 8 */ {`fract(true)`, kern.NewFraction(1, 1), nil},
/* 9 */ {`fract(false)`, kern.NewFraction(0, 1), nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 8)
runTestSuite(t, section, inputs)
}
func TestFuncBaseOthers(t *testing.T) {
section := "Builtin-Base-Others"
inputs := []inputType{
/* 1 */ {`set("a", 3); a`, int64(3), nil},
/* 2 */ {`set(true, 3)`, nil, `set(): the "name" parameter must be a string, got a bool (true)`},
// /* 3 */ {`a=3; unset("a"); a`, nil, `undefined variable or function "a"`},
// /* 4 */ {`unset("a")`, nil, `undefined variable or function "a"`},
}
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
}
+3 -3
View File
@@ -14,7 +14,7 @@ func TestFuncRun(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`builtin "iterator"; it=$(1,2,3); run(it)`, nil, nil}, /* 1 */ {`builtin "iterator"; it=$(1,2,3); run(it)`, nil, nil},
/* 2 */ {`builtin "iterator"; run($(1,2,3), func(index,item){item+10})`, nil, nil}, /* 2 */ {`builtin "iterator"; run($(1,2,3), func(index,item){item+10})`, nil, nil},
/* 3 */ {`builtin "iterator"; run($(1,2,3), func(index,item){status=status+item; true}, {"status":0})`, int64(6), nil}, /* 3 */ {`builtin "iterator"; run($(4), func(index,item){status=status+item; true}, {"status":0})`, int64(6), nil},
/* 4 */ {`builtin ["iterator", "fmt"]; run($(1,2,3), func(index,item){println(item+10)})`, nil, nil}, /* 4 */ {`builtin ["iterator", "fmt"]; run($(1,2,3), func(index,item){println(item+10)})`, nil, nil},
/* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed <nil> [nil]`}, /* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed <nil> [nil]`},
/* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, nil}, /* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, nil},
@@ -26,6 +26,6 @@ func TestFuncRun(t *testing.T) {
//t.Setenv("EXPR_PATH", ".") //t.Setenv("EXPR_PATH", ".")
runTestSuiteSpec(t, section, inputs, 10) // runTestSuiteSpec(t, section, inputs, 3)
// runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+3 -16
View File
@@ -6,7 +6,6 @@ package expr
import ( import (
"errors" "errors"
"reflect"
"strings" "strings"
"testing" "testing"
@@ -74,7 +73,6 @@ func doTest(t *testing.T, ctx kern.ExprContext, section string, input *inputType
var ast Expr var ast Expr
var gotResult any var gotResult any
var gotErr error var gotErr error
var eq, eqDone bool
wantErr := getWantedError(input) wantErr := getWantedError(input)
@@ -93,27 +91,16 @@ func doTest(t *testing.T, ctx kern.ExprContext, section string, input *inputType
gotResult, gotErr = ast.Eval(ctx) gotResult, gotErr = ast.Eval(ctx)
} }
if input.wantResult != nil && gotResult != nil { eq := kern.Equal(gotResult, input.wantResult)
if ls1, ok := input.wantResult.(*kern.ListType); ok {
if ls2, ok := gotResult.(*kern.ListType); ok {
eq = ls1.Equals(*ls2)
eqDone = true
}
}
}
if !eqDone {
eq = reflect.DeepEqual(gotResult, input.wantResult)
}
if !eq /*gotResult != input.wantResult*/ { if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, kern.TypeName(gotResult), input.wantResult, kern.TypeName(input.wantResult)) t.Errorf(">>>%s/%d: `%s` -> result = %v [%s], want = %v [%s]", section, count, input.source, gotResult, kern.TypeName(gotResult), input.wantResult, kern.TypeName(input.wantResult))
good = false good = false
} }
if gotErr != wantErr { if gotErr != wantErr {
if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) { if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
t.Errorf("%d: %s -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr) t.Errorf(">>>%s/%d: %s -> got-err = <%v>, expected-err = <%v>", section, count, input.source, gotErr, wantErr)
good = false good = false
} }
} }
+3
View File
@@ -39,6 +39,9 @@ func TestDictParser(t *testing.T) {
//"b":2, //"b":2,
"c":3 "c":3
}`, map[any]any{"a": 1, "c": 3}, nil}, }`, map[any]any{"a": 1, "c": 3}, nil},
/* 13 */ {`D={"a":1, "b":2}; D."a"`, int64(1), nil},
/* 14 */ {`D={"a":1, "b":2}; D.a`, int64(1), nil},
/* 15 */ {`D={1:"a", 2:"b", 3:"c"}; D.(1+2)`, "c", nil},
} }
succeeded := 0 succeeded := 0
+5 -11
View File
@@ -28,18 +28,12 @@ func TestFractionsParser(t *testing.T) {
/* 13 */ {`builtin "math.arith"; mul(1:2, 2:3)`, kern.NewFraction(2, 6), nil}, /* 13 */ {`builtin "math.arith"; mul(1:2, 2:3)`, kern.NewFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1:2, 1.0, 2)`, float64(1.0), nil}, /* 14 */ {`builtin "math.arith"; mul(1:2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1:0`, nil, `[1:3] division by zero`}, /* 15 */ {`1:0`, nil, `[1:3] division by zero`},
/* 16 */ {`fract(-0.5)`, kern.NewFraction(-1, 2), nil}, /* 16 */ {`1+1:2+0.5`, float64(2), nil},
/* 17 */ {`fract("")`, (*kern.FractionType)(nil), `bad syntax`}, /* 17 */ {`1:(2-2)`, nil, `[1:3] division by zero`},
/* 18 */ {`fract("-1")`, kern.NewFraction(-1, 1), nil}, /* 18 */ {`[0,1][1-1]:1`, kern.NewFraction(0, 1), nil},
/* 19 */ {`fract("+1")`, kern.NewFraction(1, 1), nil}, /* 19 */ {`1:2 == 0.5`, true, nil},
/* 20 */ {`fract("1a")`, (*kern.FractionType)(nil), `strconv.ParseInt: parsing "1a": invalid syntax`},
/* 21 */ {`fract(1,0)`, nil, `fract(): division by zero`},
/* 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`, kern.NewFraction(0, 1), nil},
} }
// runTestSuiteSpec(t, section, inputs, 25) // runTestSuiteSpec(t, section, inputs, 26)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+12
View File
@@ -49,6 +49,18 @@ func TestFuncs(t *testing.T) {
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
func TestFuncs2(t *testing.T) {
section := "Funcs2"
inputs := []inputType{
/* 1 */ {`sum=func(a,b){a+b}; sum(1,2)`, int64(3), nil},
/* 2 */ {`sum=func(a,b){a+b}; sum(1)`, nil, `sum(): too few params -- expected 2, got 1`},
/* 3 */ {`["1", "2", "3"] map int()`, nil, `int(): too few params -- expected 1, got 0`},
/* 4 */ {`builtin "iterator"; times=func(a,b){a*b}; run($(["1", "2", "3"]), times)`, nil, `operator(): missing params -- a, b`},
}
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
}
func dummy(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func dummy(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
return return
} }
+2
View File
@@ -1,6 +1,8 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
//go:build graph
// t_graph_test.go // t_graph_test.go
package expr package expr
+8 -2
View File
@@ -35,11 +35,17 @@ func TestIteratorParser(t *testing.T) {
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", 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}, /* 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++`, kern.NewList([]any{int64(1), "one"}), nil}, /* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, kern.NewList([]any{int64(1), "one"}), nil},
/* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${_index}`, kern.NewList([]any{int64(0), int64(1)}), nil}, /* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${__}`, kern.NewList([]any{int64(0), int64(1)}), nil},
/* 24 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil}, /* 24 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil},
/* 25 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil}, /* 25 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil},
/* 26 */ {`#($(10) map ${_})`, int64(10), nil},
/* 27 */ {`#($(10,0) map ${_})`, int64(10), nil},
/* 28 */ {`$(10) digest ${_}`, int64(9), nil},
/* 29 */ {`$(10,0) digest ${_}`, int64(1), nil},
/* 30 */ {`$(10,0,-2) digest ${_}`, int64(2), nil},
/* 31 */ {`[3,4,5] map ${_#}`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
} }
// runTestSuiteSpec(t, section, inputs, 23) // runTestSuiteSpec(t, section, inputs, 10)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+75 -7
View File
@@ -35,16 +35,84 @@ func TestOperator(t *testing.T) {
/* 20 */ {`a=1; a^=2`, int64(3), nil}, /* 20 */ {`a=1; a^=2`, int64(3), nil},
/* 21 */ {`a=1; ++a`, int64(2), nil}, /* 21 */ {`a=1; ++a`, int64(2), nil},
/* 22 */ {`a=1; --a`, int64(0), nil}, /* 22 */ {`a=1; --a`, int64(0), nil},
/* 23 */ {`[1,2,3] map var("_")`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 24 */ {`[1,2,3] map $_`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 25 */ {`[1,2,3,4] filter ($_ % 2 == 0)`, kern.NewList([]any{int64(2), int64(4)}), nil},
/* 26 */ {`max=0; [2,3,1] digest max=(($_ > max) ? {$_} :: {max})`, int64(3), nil},
/* 27 */ {`["a","b"] join ["x"]`, kern.NewList([]any{"a", "b", "x"}), nil},
/* 28 */ {`["a","b"] join ["x"-true]`, nil, `[1:21] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 28) // runTestSuiteSpec(t, section, inputs, 22)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
func TestOperatorMap(t *testing.T) {
section := "Operator-Map"
inputs := []inputType{
/* 1 */ {`a=1; --a`, int64(0), nil},
/* 2 */ {`[1,2,3] map var("_")`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 3 */ {`[1,2,3] map $_`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
}
// runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
func TestOperatorFilter(t *testing.T) {
section := "Operator-Filter"
inputs := []inputType{
/* 1 */ {`[1,2,3,4] filter ($_ % 2 == 0)`, kern.NewList([]any{int64(2), int64(4)}), nil},
}
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
func TestOperatorDigest(t *testing.T) {
section := "Operator-Digest"
inputs := []inputType{
/* 1 */ {`max=0; [2,3,1] digest max=(($_ > max) ? {$_} :: {max})`, int64(3), nil},
}
// runTestSuiteSpec(t, section, inputs, 29)
runTestSuite(t, section, inputs)
}
func TestOperatorJoin(t *testing.T) {
section := "Operator-Join"
inputs := []inputType{
/* 1 */ {`["a","b"] join ["x"]`, kern.NewList([]any{"a", "b", "x"}), nil},
/* 2 */ {`["a","b"] join ["x"-true]`, nil, `[1:21] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`},
}
// runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
}
func TestOperatorGroupBy(t *testing.T) {
section := "Operator-GroupBy"
inputs := []inputType{
/* 1 */ {`L=[{"num": 1, "alpha": "one"}, {"num": 2, "alpha": "two"}, {"num": 3, "alpha": "three"}]; L groupby "num"`,
kern.NewDict(map[any]any{
"1": kern.NewListA(kern.NewDict(map[any]any{"num": int64(1), "alpha": "one"})),
"2": kern.NewListA(kern.NewDict(map[any]any{"num": int64(2), "alpha": "two"})),
"3": kern.NewListA(kern.NewDict(map[any]any{"num": int64(3), "alpha": "three"})),
}),
nil},
/* 2 */ {`cars = [{"model": "compas", "vendor": "jeep"}, {"model": "limited", "vendor": "jeep"}, {"model": "600", "vendor":"fiat"}]; cars groupby "vendor"`,
kern.NewDict(map[any]any{
"jeep": kern.NewListA(
kern.NewDict(map[any]any{"model": "compas", "vendor": "jeep"}),
kern.NewDict(map[any]any{"model": "limited", "vendor": "jeep"})),
"fiat": kern.NewListA(kern.NewDict(map[any]any{"model": "600", "vendor": "fiat"})),
}),
nil},
/* 3 */ {`[3,4,5] groupby $__`,
kern.NewDict(map[any]any{
"0": kern.NewListA(int64(3)),
"1": kern.NewListA(int64(4)),
"2": kern.NewListA(int64(5)),
}),
nil},
}
runTestSuiteSpec(t, section, inputs, 3)
// runTestSuite(t, section, inputs)
}
+12
View File
@@ -147,3 +147,15 @@ func TestGeneralParser(t *testing.T) {
// runTestSuiteSpec(t, section, inputs, 114) // runTestSuiteSpec(t, section, inputs, 114)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
func TestSpecialParser(t *testing.T) {
section := "Parser"
inputs := []inputType{
/* 1 */ {`()`, nil, "[1:2] expected expression, got `()`"},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 114)
runTestSuite(t, section, inputs)
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_symbol_test.go
package expr
import (
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
)
func TestOperatorEnding(t *testing.T) {
section := "Symbol"
inputs := []inputType{
/* 1 */ {`a++`, false, nil},
/* 2 */ {`a;`, true, nil},
/* 3 */ {`a +`, true, nil},
/* 4 */ {`a + 1`, false, nil},
/* 5 */ {`a - `, true, nil},
/* 6 */ {`a( `, true, nil},
/* 7 */ {`a) `, false, nil},
/* 8 */ {`++a `, false, nil},
}
// testStringArrayEndingWithOperatorSpec(t, section, inputs, 6)
testStringArrayEndingWithOperator(t, section, inputs)
}
func testStringEndingWithOperator(source string) bool {
return StringEndsWithOperator(source)
}
func testStringArrayEndingWithOperatorSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
for i := range spec {
if spec[i] < 1 || spec[i] > len(inputs) {
t.Errorf("Invalid test spec index: %d (must be between 1 and %d)", spec[i], len(inputs))
continue
}
doEndingTest(t, section, inputs[spec[i]-1], spec[i]-1)
}
}
func testStringArrayEndingWithOperator(t *testing.T, section string, inputs []inputType) {
for i, input := range inputs {
doEndingTest(t, section, input, i)
}
}
func doEndingTest(t *testing.T, section string, input inputType, i int) {
wantErr := getWantedError(&input)
logTest(t, i+1, section, input.source, input.wantResult, wantErr)
gotResult := testStringEndingWithOperator(input.source)
t.Logf("Is %s op ending? %v", input.source, gotResult)
if gotResult != input.wantResult.(bool) {
t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", i+1, input.source, gotResult, kern.TypeName(gotResult), input.wantResult, kern.TypeName(input.wantResult))
}
}
+7 -7
View File
@@ -12,7 +12,7 @@ import (
"path" "path"
"testing" "testing"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/util"
) )
func TestExpandPathRootOk(t *testing.T) { func TestExpandPathRootOk(t *testing.T) {
@@ -21,7 +21,7 @@ func TestExpandPathRootOk(t *testing.T) {
// wantErr := errors.New(`test expected string, got list ([])`) // wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := kern.ExpandPath(source) gotValue, gotErr := util.ExpandPath(source)
if gotErr != nil && gotErr.Error() != wantErr.Error() { if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`, t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
@@ -38,7 +38,7 @@ func TestExpandPathRootSubDirOk(t *testing.T) {
// wantErr := errors.New(`test expected string, got list ([])`) // wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := kern.ExpandPath(source) gotValue, gotErr := util.ExpandPath(source)
if gotErr != nil && gotErr.Error() != wantErr.Error() { if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`, t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
@@ -56,7 +56,7 @@ func TestExpandPathUser(t *testing.T) {
// wantErr := errors.New(`test expected string, got list ([])`) // wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := kern.ExpandPath(source) gotValue, gotErr := util.ExpandPath(source)
if gotErr != nil { if gotErr != nil {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`, t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
@@ -74,7 +74,7 @@ func TestExpandPathEnv(t *testing.T) {
// wantErr := errors.New(`test expected string, got list ([])`) // wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := kern.ExpandPath(source) gotValue, gotErr := util.ExpandPath(source)
if gotErr != nil { if gotErr != nil {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`, t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
@@ -90,7 +90,7 @@ func TestExpandPathErr(t *testing.T) {
wantValue := "~fake-user/test" wantValue := "~fake-user/test"
wantErr := errors.New(`user: unknown user fake-user`) wantErr := errors.New(`user: unknown user fake-user`)
gotValue, gotErr := kern.ExpandPath(source) gotValue, gotErr := util.ExpandPath(source)
if gotErr != nil && gotErr.Error() != wantErr.Error() { if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`, t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
@@ -108,7 +108,7 @@ func TestExpandPathUserErr(t *testing.T) {
// wantErr := errors.New(`test expected string, got list ([])`) // wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := kern.ExpandPath(source) gotValue, gotErr := util.ExpandPath(source)
if gotErr != nil { if gotErr != nil {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`, t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
+28 -1
View File
@@ -10,6 +10,7 @@ import (
"testing" "testing"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
) )
func TestIsString(t *testing.T) { func TestIsString(t *testing.T) {
@@ -121,6 +122,32 @@ func TestToIntErr(t *testing.T) {
} }
} }
func TestToInt64Ok(t *testing.T) {
source := int64(64)
wantValue := int64(64)
wantErr := error(nil)
gotValue, gotErr := kern.ToGoInt64(source, "test")
if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt64(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestToInt64Err(t *testing.T) {
source := uint64(64)
wantValue := int64(0)
wantErr := errors.New(`test expected integer, got uint64 (64)`)
gotValue, gotErr := kern.ToGoInt64(source, "test")
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
t.Errorf("toInt64(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestAnyInteger(t *testing.T) { func TestAnyInteger(t *testing.T) {
type inputType struct { type inputType struct {
source any source any
@@ -161,7 +188,7 @@ func TestAnyInteger(t *testing.T) {
func TestCopyMap(t *testing.T) { func TestCopyMap(t *testing.T) {
source := map[string]int{"one": 1, "two": 2, "three": 3} source := map[string]int{"one": 1, "two": 2, "three": 3}
dest := make(map[string]int) dest := make(map[string]int)
result := kern.CopyMap(dest, source) result := util.CopyMap(dest, source)
if !reflect.DeepEqual(result, source) { if !reflect.DeepEqual(result, source) {
t.Errorf("utils.CopyMap() failed") t.Errorf("utils.CopyMap() failed")
} }
+4
View File
@@ -235,6 +235,10 @@ func (t *term) errDivisionByZero() error {
return t.tk.Errorf("division by zero") return t.tk.Errorf("division by zero")
} }
func (t *term) errKeyNotFound(key any) error {
return t.tk.Errorf("key '%v' not found", key)
}
func (t *term) Errorf(template string, args ...any) (err error) { func (t *term) Errorf(template string, args ...any) (err error) {
err = t.tk.Errorf(template, args...) err = t.tk.Errorf(template, args...)
return return
+1 -1
View File
@@ -4,7 +4,7 @@
// All rights reserved. // All rights reserved.
// utils-unix.go // utils-unix.go
package kern package util
import ( import (
"os" "os"
@@ -4,7 +4,7 @@
// All rights reserved. // All rights reserved.
// utils-unix.go // utils-unix.go
package kern package util
import ( import (
"os" "os"
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils.go
package util
import (
"reflect"
"git.portale-stac.it/go-pkg/expr/kern"
)
func IsFunc(v any) bool {
return reflect.TypeOf(v).Kind() == reflect.Func
}
func FromGenericAny(v any) (exprAny any, ok bool) {
if v != nil {
if exprAny, ok = v.(bool); ok {
return
}
if exprAny, ok = v.(string); ok {
return
}
if exprAny, ok = kern.AnyInteger(v); ok {
return
}
if exprAny, ok = kern.AnyFloat(v); ok {
return
}
if exprAny, ok = v.(*kern.DictType); ok {
return
}
if exprAny, ok = v.(*kern.ListType); ok {
return
}
}
return
}
func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
for k, v := range source {
dest[k] = v
}
return dest
}
// func CloneMap[K comparable, V any](source map[K]V) map[K]V {
// dest := make(map[K]V, len(source))
// return CopyMap(dest, source)
// }
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
// fmt.Printf("--- Clone with filter %p\n", filter)
if filter == nil {
return CopyMap(dest, source)
} else {
for k, v := range source {
if filter(k) {
// fmt.Printf("\tClone var %q\n", k)
dest[k] = v
}
}
}
return dest
}
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
dest := make(map[K]V, len(source))
return CopyFilteredMap(dest, source, filter)
}
func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {
result[i] = fn(t)
}
return result
}