Compare commits

..

37 Commits

Author SHA1 Message Date
camoroso ab06702e5e operator-post-inc.go: new post int decrement operator '--' 2024-07-24 06:39:35 +02:00
camoroso ffe1fa3aac op-assign expansion now end at ']' and '}' too 2024-07-24 06:37:57 +02:00
camoroso 1a1a475dd8 Added support to op-assign operators like '+='.
This feature is implemented by expansion, not by dedicated operators, e.g. a*=2+x is exapanded as a=a*(2+x)
2024-07-23 15:35:57 +02:00
camoroso 463e3634ba scanner.go: New function UnreadToken().
Currently it only supports one staging level.
2024-07-23 15:32:25 +02:00
camoroso 5f8ca47ef0 term.go: New function Clone() 2024-07-23 15:27:50 +02:00
camoroso 837b887490 token.go: New functions Clone() and IsOneOfA() 2024-07-23 15:27:36 +02:00
camoroso c76e1d3c8e symbol.go: New symbol '*=' 2024-07-23 15:24:54 +02:00
camoroso 315f5b22d3 data-cursor.go: the inizialization of the current item is done in the Next() method.
This allows the application of the filter and map operator to the first item too.
2024-07-23 05:46:37 +02:00
camoroso dfae593e86 operand-iterator.go: removed commented code 2024-07-21 16:35:13 +02:00
camoroso d572f3a129 Iterator: new function Count() 2024-07-21 16:34:52 +02:00
camoroso c461fd138e Iterator defined by data-source now only requires one method: next() 2024-07-21 05:45:22 +02:00
camoroso 6ecbe2feb1 Fixed type (expcted -> expected) 2024-07-21 05:43:55 +02:00
camoroso 80d3c6ec7d parser.go: Fixed an old bug that did not allow the parser to skip comment tokens 2024-07-21 05:37:34 +02:00
camoroso e09806c716 %q replaced by %s in some error messages 2024-07-21 05:33:06 +02:00
camoroso 1a772597cb removed/commented unused code 2024-07-19 17:03:03 +02:00
camoroso 33b3e1fc29 New function for searching and importing plugin 2024-07-19 15:37:00 +02:00
camoroso 4e3f5cfbc6 import-utils.go: Paths are now expanded with respect to env-vars and shell ~ 2024-07-19 15:33:15 +02:00
camoroso e35d4e3f70 utils.go: added function ExpandPath 2024-07-19 15:30:26 +02:00
camoroso b4529499d6 iterator.go: exported some const identifier 2024-07-18 07:27:02 +02:00
camoroso 7745dc24e2 dict-type.go: exported NewDataCursor 2024-07-18 07:25:51 +02:00
camoroso be25385d02 builtin-iterator: changed status variable from 'it_status' to 'status' 2024-07-15 06:59:13 +02:00
camoroso 79889cd8e1 New builtin module 'iterator' 2024-07-14 16:53:32 +02:00
camoroso 234759158c scanner: disabled prefix operator '()' 2024-07-13 18:10:04 +02:00
camoroso 00fda29606 operator-iter-value.go: prefix operator () used to get the current value of an iterator replaced by ^ 2024-07-13 17:17:16 +02:00
camoroso 2a2840bdf2 ListIterator now implements next and current command (e.g it.next) 2024-07-13 17:15:53 +02:00
camoroso 06373f5126 Impemented Typer interface 2024-07-13 17:14:25 +02:00
camoroso e69dad5fb5 Rename priority label priCoalesce as priDefault 2024-07-13 17:13:16 +02:00
camoroso 7459057df9 Expr embeds Typer and ast implements it 2024-07-13 17:11:39 +02:00
camoroso 176969c956 New iterator tests 2024-07-13 16:19:04 +02:00
camoroso cde2efacf1 Test on iterator filter and map 2024-07-13 15:30:04 +02:00
camoroso d7a7b3218c Merge branch 'main' into enhance_iterators 2024-07-13 09:08:16 +02:00
camoroso be3bb12f28 operator-unset.go: fixed function removal 2024-07-13 09:07:33 +02:00
camoroso 032916d4fa New operator unset to delete variables and functions from current context 2024-07-13 09:01:59 +02:00
camoroso f3cc0cc7ad operator-include.go: Fixed inclusion of a list of files.
Now returns the value of the last evaluated expression.
2024-07-13 09:00:53 +02:00
camoroso 905337f963 ExprContext: new functions VarCount(), DeleteVar(), FuncCount(), DeleteFunc() 2024-07-13 08:59:15 +02:00
camoroso 9a95a837f6 data-cursor.go: add item mapping support 2024-07-13 06:44:00 +02:00
camoroso 8547248ea2 data-cursor.go: fixed filter 2024-07-13 00:12:08 +02:00
42 changed files with 746 additions and 191 deletions
+10 -6
View File
@@ -19,6 +19,10 @@ func NewAst() *ast {
return &ast{}
}
func (expr *ast) TypeName() string {
return "Expression"
}
func (expr *ast) ToForest() {
if expr.root != nil {
if expr.forest == nil {
@@ -41,19 +45,19 @@ func (expr *ast) String() string {
func (expr *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens {
if err = expr.addToken(tk); err != nil {
if _, err = expr.addToken(tk); err != nil {
break
}
}
return
}
func (expr *ast) addToken(tk *Token) (err error) {
_, err = expr.addToken2(tk)
return
}
// func (expr *ast) addToken(tk *Token) (err error) {
// _, err = expr.addToken2(tk)
// return
// }
func (expr *ast) addToken2(tk *Token) (t *term, err error) {
func (expr *ast) addToken(tk *Token) (t *term, err error) {
if t = newTerm(tk); t != nil {
err = expr.addTerm(t)
} else {
+4
View File
@@ -29,6 +29,10 @@ func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFuncto
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
func (functor *exprFunctor) TypeName() string {
return "ExprFunctor"
}
func (functor *exprFunctor) GetDefinitionContext() ExprContext {
return functor.defCtx
}
+4
View File
@@ -14,6 +14,10 @@ func NewGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) TypeName() string {
return "GoFunctor"
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-iterator.go
package expr
import (
"fmt"
"io"
)
const (
iterParamOperator = "operator"
iterParamVars = "vars"
iterVarStatus = "status"
)
func parseRunArgs(localCtx ExprContext, args []any) (it Iterator, op Functor, err error) {
var ok bool
if it, ok = args[0].(Iterator); !ok {
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[0], TypeName(args[0]))
return
}
if len(args) > 1 {
if op, ok = args[1].(Functor); !ok || op == nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[1], TypeName(args[1]))
return
}
if len(args) > 2 {
var vars *DictType
if vars, ok = args[2].(*DictType); !ok || vars == nil {
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[2], TypeName(args[2]))
return
}
for key, value := range *vars {
var varName string
if varName, ok = key.(string); ok {
localCtx.UnsafeSetVar(varName, value)
}
}
}
}
return
}
func runFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var it Iterator
var ok bool
var op Functor
var v any
var usingDefaultOp = false
var params []any
var item any
localCtx := ctx.Clone()
localCtx.UnsafeSetVar(iterVarStatus, nil)
if it, op, err = parseRunArgs(localCtx, args); err != nil {
return
} else if op == nil {
op = NewGolangFunctor(printLnFunc)
usingDefaultOp = true
}
for item, err = it.Next(); err == nil; item, err = it.Next() {
if usingDefaultOp {
params = []any{item}
} else {
params = []any{it.Index(), item}
}
if v, err = op.Invoke(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = ToBool(v); !success || !ok {
break
}
}
}
if err == io.EOF {
err = nil
}
if err == nil {
result, _ = localCtx.GetVar(iterVarStatus)
}
return
}
func ImportIterFuncs(ctx ExprContext) {
ctx.RegisterFunc("run", NewGolangFunctor(runFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamIterator),
NewFuncParamFlag(iterParamOperator, PfOptional),
NewFuncParamFlag(iterParamVars, PfOptional),
})
}
func init() {
RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
}
+4 -4
View File
@@ -34,8 +34,8 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return
}
}
@@ -107,8 +107,8 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return
}
}
+1
View File
@@ -19,6 +19,7 @@ const (
ParamEllipsis = "..."
ParamFilepath = "filepath"
ParamDirpath = "dirpath"
ParamIterator = "iterator"
)
// to be moved in its own source file
+97 -56
View File
@@ -5,30 +5,45 @@
package expr
import (
"errors"
"io"
)
type dataCursor struct {
ds map[string]Functor
ctx ExprContext
initState bool // true if no item has prodiced yet (this replace di initial Next() call in the contructor)
index int
count int
current any
lastErr error
resource any
nextFunc Functor
cleanFunc Functor
resetFunc Functor
currentFunc Functor
}
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
dc = &dataCursor{
ds: ds,
initState: true,
index: -1,
count: 0,
current: nil,
lastErr: nil,
resource: resource,
ctx: ctx.Clone(),
nextFunc: ds[NextName],
cleanFunc: ds[CleanName],
resetFunc: ds[ResetName],
}
//dc.Next()
return
}
func (dc *dataCursor) Context() ExprContext {
return dc.ctx
}
func (dc *dataCursor) TypeName() string {
return "DataCursor"
}
@@ -62,7 +77,7 @@ func (dc *dataCursor) String() string {
}
func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == indexName
exists = name == IndexName
if !exists {
f, ok := dc.ds[name]
exists = ok && isFunctor(f)
@@ -71,7 +86,7 @@ func (dc *dataCursor) HasOperation(name string) (exists bool) {
}
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if name == indexName {
if name == IndexName {
value = int64(dc.Index())
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc {
@@ -93,15 +108,19 @@ func (dc *dataCursor) Reset() (success bool, err error) {
if dc.resetFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
dc.index = -1
}
_, err = dc.resetFunc.Invoke(ctx, ResetName, []any{dc.resource})
exportObjects(dc.ctx, ctx)
dc.index = -1
dc.count = 0
dc.initState = true
dc.current = nil
dc.lastErr = nil
//dc.Next()
} else {
err = errInvalidDataSource()
}
} else {
err = errNoOperation(resetName)
err = errNoOperation(ResetName)
}
success = err == nil
return
@@ -111,82 +130,104 @@ func (dc *dataCursor) Clean() (success bool, err error) {
if dc.cleanFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
dc.resource = nil
_, err = dc.cleanFunc.Invoke(ctx, CleanName, []any{dc.resource})
// dc.resource = nil
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errors.New("no 'clean' function defined in the data-source")
err = errNoOperation(CleanName)
}
success = err == nil
return
}
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx)
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
dc.init()
if dc.current != nil {
item = dc.current
} else {
err = io.EOF
}
exportObjects(dc.ctx, ctx)
return
}
func (dc *dataCursor) _Next() (item any, err error) { // must return io.EOF after the last item
func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
var v any
var ok bool
ctx := cloneContext(dc.ctx)
if v, err = filter.Invoke(ctx, FilterName, []any{item, dc.index}); err == nil && v != nil {
if accepted, ok = v.(bool); !ok {
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
}
return
}
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
ctx := cloneContext(dc.ctx)
mappedItem, err = mapper.Invoke(ctx, MapName, []any{item, dc.index})
return
}
func (dc *dataCursor) init() {
if dc.initState {
dc.initState = false
dc.Next()
}
}
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
if dc.initState {
dc.init()
} else if err = dc.lastErr; err != nil {
return
}
current = dc.current
if dc.resource != nil {
filter := dc.ds[FilterName]
mapper := dc.ds[MapName]
var item any
for item == nil && dc.lastErr == nil {
ctx := cloneContext(dc.ctx)
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
}
}
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else {
err = errInvalidDataSource()
}
return
}
const filterName = "filter"
func (dc *dataCursor) filter(item any) (filterdItem any, err error) {
if filter, ok := dc.ds[filterName]; ok {
ctx := cloneContext(dc.ctx)
filterdItem, err = filter.Invoke(ctx, filterName, []any{item, dc.index});
} else {
filterdItem = item
}
return
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
for item == nil && err == nil {
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item, dc.lastErr = dc.nextFunc.Invoke(ctx, NextName, []any{dc.resource, dc.index}); dc.lastErr == nil {
if item == nil {
err = io.EOF
dc.lastErr = io.EOF
} else {
dc.index++
item, err = dc.filter(item)
accepted := true
if filter != nil {
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
item = nil
}
}
if accepted {
dc.count++
}
if item != nil && mapper != nil {
item, dc.lastErr = dc.mapItem(mapper, item)
}
}
}
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
}
dc.current = item
if dc.lastErr != nil {
dc.index--
dc.Clean()
}
} else {
err = errInvalidDataSource()
dc.lastErr = errInvalidDataSource()
}
return
}
func (dc *dataCursor) Index() int {
return dc.index
return dc.index - 1
}
func (dc *dataCursor) Count() int {
return dc.count
}
+15
View File
@@ -18,7 +18,22 @@ func MakeDict() (dict *DictType) {
return
}
func NewDict(dictAny map[any]any) (dict *DictType) {
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func newDict(dictAny map[any]*term) (dict *DictType) {
// TODO Change wi a call to NewDict()
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
+7
View File
@@ -14,8 +14,15 @@ type ExprContext interface {
GetLast() any
SetVar(varName string, value any)
UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
VarCount() int
DeleteVar(varName string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
FuncCount() int
DeleteFunc(funcName string)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
RegisterFuncInfo(info ExprFunc)
+1
View File
@@ -6,6 +6,7 @@ package expr
// ---- Functor interface
type Functor interface {
Typer
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
+1
View File
@@ -6,6 +6,7 @@ package expr
// ----Expression interface
type Expr interface {
Typer
Eval(ctx ExprContext) (result any, err error)
String() string
}
+8
View File
@@ -75,7 +75,11 @@ func isFile(filePath string) bool {
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
for _, dir := range dirList {
if dir, err = ExpandPath(dir); err != nil {
continue
}
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
@@ -90,6 +94,10 @@ func isPathRelative(filePath string) bool {
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if filename, err = ExpandPath(filename); err != nil {
return
}
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
+10 -7
View File
@@ -12,13 +12,15 @@ import (
// Operator names
const (
initName = "init"
cleanName = "clean"
resetName = "reset"
nextName = "next"
currentName = "current"
indexName = "index"
countName = "count"
InitName = "init"
CleanName = "clean"
ResetName = "reset"
NextName = "next"
CurrentName = "current"
IndexName = "index"
CountName = "count"
FilterName = "filter"
MapName = "map"
)
type Iterator interface {
@@ -26,6 +28,7 @@ type Iterator interface {
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
Count() int
}
type ExtIterator interface {
+13 -4
View File
@@ -90,17 +90,21 @@ func (it *ListIterator) TypeName() string {
}
func (it *ListIterator) HasOperation(name string) bool {
yes := name == resetName || name == indexName || name == countName
yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
return yes
}
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
switch name {
case resetName:
case NextName:
v, err = it.Next()
case ResetName:
v, err = it.Reset()
case indexName:
case IndexName:
v = int64(it.Index())
case countName:
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
default:
err = errNoOperation(name)
@@ -139,7 +143,12 @@ func (it *ListIterator) Index() int {
return it.index
}
func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (bool, error) {
it.index = it.start - it.step
it.count = 0
return true, nil
}
+10 -13
View File
@@ -66,8 +66,8 @@ func evalFirstChild(ctx ExprContext, iteratorTerm *term) (value any, err error)
func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) {
if dictAny, ok := firstChildValue.(*DictType); ok {
requiredFields := []string{currentName, nextName}
fieldsMask := 0b11
requiredFields := []string{NextName}
fieldsMask := 0b1
foundFields := 0
ds = make(map[string]Functor)
for keyAny, item := range *dictAny {
@@ -88,7 +88,6 @@ func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]F
missingFields = append(missingFields, field)
}
}
// err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
err = iteratorTerm.children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
}
}
@@ -108,9 +107,10 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
}
if ds != nil {
dc := newDataCursor(ctx, ds)
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var dc *dataCursor
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
return
@@ -119,18 +119,15 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
args = []any{}
}
initCtx := dc.ctx.Clone()
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
initCtx := ctx.Clone()
if resource, err = initFunc.Invoke(initCtx, InitName, args); err != nil {
return
}
exportObjects(dc.ctx, initCtx)
dcCtx := ctx.Clone()
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
}
dc.nextFunc = ds[nextName]
dc.currentFunc = ds[currentName]
dc.cleanFunc = ds[cleanName]
dc.resetFunc = ds[resetName]
v = dc
} else if list, ok := firstChildValue.(*ListType); ok {
var args []any
+3 -3
View File
@@ -11,7 +11,7 @@ func newDefaultTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
priority: priDefault,
evalFunc: evalDefault,
}
}
@@ -45,7 +45,7 @@ func newAlternateTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
priority: priDefault,
evalFunc: evalAlternate,
}
}
@@ -81,7 +81,7 @@ func newDefaultAssignTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
priority: priDefault,
evalFunc: evalAssignDefault,
}
}
+5 -4
View File
@@ -25,8 +25,8 @@ func evalInclude(ctx ExprContext, opTerm *term) (v any, err error) {
count := 0
if IsList(childValue) {
list, _ := childValue.([]any)
for i, filePathSpec := range list {
list, _ := childValue.(*ListType)
for i, filePathSpec := range *list {
if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil {
count++
@@ -47,8 +47,9 @@ func evalInclude(ctx ExprContext, opTerm *term) (v any, err error) {
} else {
err = opTerm.errIncompatibleType(childValue)
}
if err == nil {
v = count
if err != nil {
//v = count
v = nil
}
return
}
+2 -1
View File
@@ -33,5 +33,6 @@ func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
// init
func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
}
+7 -7
View File
@@ -30,16 +30,16 @@ func evalLength(ctx ExprContext, opTerm *term) (v any, err error) {
s, _ := childValue.(string)
v = int64(len(s))
} else if IsDict(childValue) {
// m, _ := childValue.(map[any]any)
m, _ := childValue.(*DictType)
v = int64(len(*m))
} else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)
v, _ = ToGoInt(count, "")
} else {
v = int64(it.Index() + 1)
}
v = int64(it.Count())
// if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
// count, _ := extIt.CallOperation(CountName, nil)
// v, _ = ToGoInt(count, "")
// } else {
// v = int64(it.Index() + 1)
// }
} else {
err = opTerm.errIncompatibleType(childValue)
}
+2 -24
View File
@@ -4,10 +4,6 @@
// operator-plugin.go
package expr
import (
"io"
)
//-------- plugin term
func newPluginTerm(tk *Token) (inst *term) {
@@ -22,31 +18,13 @@ func newPluginTerm(tk *Token) (inst *term) {
func evalPlugin(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
var moduleSpec any
var count int
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count := 0
it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
break
}
count++
} else {
err = opTerm.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
if err == nil {
if count, err = importPluginFromSearchPath(childValue); err == nil {
v = int64(count)
}
return
+32
View File
@@ -35,7 +35,39 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
// -------- post decrement term
func newPostDecTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priIncDec,
evalFunc: evalPostDec,
}
}
func evalPostDec(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
/* if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else */if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(opTerm.children[0].source(), i-1)
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm)
registerTermConstructor(SymDoubleMinus, newPostDecTerm)
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-unset.go
package expr
import "strings"
//-------- unset term
func newUnsetTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalUnset,
}
}
func deleteContextItem(ctx ExprContext, opTerm *term, item any) (deleted bool, err error) {
if name, ok := item.(string); ok {
var size int
if strings.HasSuffix(name, "()") {
size = ctx.FuncCount()
ctx.DeleteFunc(strings.TrimRight(name, "()"))
deleted = ctx.FuncCount() < size
} else {
size = ctx.VarCount()
ctx.DeleteVar(name)
deleted = ctx.VarCount() < size
}
} else {
err = opTerm.errIncompatibleType(item)
}
return
}
func evalUnset(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
var deleted bool
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
count := 0
if IsList(childValue) {
list, _ := childValue.(*ListType)
for _, item := range *list {
if deleted, err = deleteContextItem(ctx, opTerm, item); err != nil {
break
} else if deleted {
count++
}
}
} else if deleted, err = deleteContextItem(ctx, opTerm, childValue); err == nil && deleted {
count++
}
if err == nil {
v = int64(count)
}
return
}
// init
func init() {
registerTermConstructor(SymKwUnset, newUnsetTerm)
}
+74 -18
View File
@@ -18,6 +18,12 @@ func NewParser() (p *parser) {
return p
}
func (parser *parser) Next(scanner *scanner) (tk *Token) {
for tk = scanner.Next(); tk.IsSymbol(SymComment); tk = scanner.Next() {
}
return
}
func (parser *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
args := make([]*term, 0, 10)
itemExpected := false
@@ -57,11 +63,11 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
itemExpected := false
tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next()
tk = parser.Next(scanner)
if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk)
args = append(args, param)
tk = scanner.Next()
tk = parser.Next(scanner)
if tk.Sym == SymEqual {
var paramExpr *ast
defaultParamsStarted = true
@@ -86,7 +92,7 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
err = tk.ErrorExpectedGot(")")
}
if err == nil {
tk = scanner.Next()
tk = parser.Next(scanner)
if tk.IsSymbol(SymOpenBrace) {
body, err = parser.parseGeneral(scanner, true, true, SymClosedBrace)
} else {
@@ -184,8 +190,8 @@ func (parser *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree
return
}
func (parser *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
tk := scanner.Next()
func (parser *parser) parseDictKey(scanner *scanner) (key any, err error) {
tk := parser.Next(scanner)
if tk.Sym == SymError {
err = tk.Error()
return
@@ -194,7 +200,7 @@ func (parser *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any,
return
}
if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next()
tkSep := parser.Next(scanner)
if tkSep.Sym != SymColon {
err = tkSep.ErrorExpectedGot(":")
} else {
@@ -213,7 +219,7 @@ func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtr
for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast
var key any
if key, err = parser.parseDictKey(scanner, allowVarRef); err != nil {
if key, err = parser.parseDictKey(scanner); err != nil {
break
} else if key == nil {
tk := scanner.Previous()
@@ -251,7 +257,7 @@ func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtr
func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term
var caseExpr *ast
tk := scanner.Next()
tk := parser.Next(scanner)
startRow := tk.row
startCol := tk.col
if tk.Sym == SymOpenSquare {
@@ -262,7 +268,7 @@ func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defa
if filterList, err = parser.parseList(scanner, false, allowVarRef); err != nil {
return
}
tk = scanner.Next()
tk = parser.Next(scanner)
startRow = tk.row
startCol = tk.col
} else if !defaultCase {
@@ -299,7 +305,7 @@ func addSelectorCase(selectorTerm, caseTerm *term) {
func (parser *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
var caseTerm *term
tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken2(tk); err != nil {
if selectorTerm, err = tree.addToken(tk); err != nil {
return
}
@@ -337,13 +343,14 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
var selectorTerm *term = nil
var currentTerm *term = nil
var tk *Token
tree = NewAst()
firstToken := true
// lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
for tk = parser.Next(scanner); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = parser.Next(scanner) {
// if tk.Sym == SymComment {
// continue
// }
if tk.Sym == SymSemiColon {
if allowForest {
@@ -409,7 +416,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
}
case SymEqual:
// if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk)
currentTerm, err = tree.addToken(tk)
// }
case SymFuncDef:
var funcDefTerm *term
@@ -427,7 +434,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
} else {
currentTerm, err = tree.addToken2(tk)
currentTerm, err = tree.addToken(tk)
}
case SymQuestion:
if selectorTerm, err = parser.parseSelector(scanner, tree, allowVarRef); err == nil {
@@ -444,14 +451,16 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
}
}
} else {
currentTerm, err = tree.addToken2(tk)
currentTerm, err = tree.addToken(tk)
}
if tk.IsSymbol(SymColon) {
// Colon outside a selector term acts like a separator
firstToken = true
}
case SymPlusEqual, SymMinusEqual, SymStarEqual:
currentTerm, err = parser.expandOpAssign(scanner, tree, tk, allowVarRef)
default:
currentTerm, err = tree.addToken2(tk)
currentTerm, err = tree.addToken(tk)
}
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
@@ -460,6 +469,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
}
// lastSym = tk.Sym
}
if err == nil {
err = tk.Error()
}
@@ -472,3 +482,49 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
// }
// 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
}
+24
View File
@@ -6,6 +6,7 @@ package expr
import (
"fmt"
"io"
"os"
"plugin"
"strings"
@@ -90,6 +91,29 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
return
}
func importPluginFromSearchPath(name any) (count int, err error) {
var moduleSpec any
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count = 0
it := NewAnyIterator(name)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
break
}
count++
} else {
err = fmt.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
return
}
func loadModules(dirList []string, moduleNames []string) (err error) {
for _, name := range moduleNames {
if err1 := importPlugin(dirList, name); err1 != nil {
+22 -4
View File
@@ -16,6 +16,7 @@ import (
type scanner struct {
current *Token
prev *Token
stage *Token
stream *bufio.Reader
row int
column int
@@ -74,6 +75,16 @@ func (scanner *scanner) unreadChar() (err error) {
return
}
func (scanner *scanner) UnreadToken() (err error) {
if scanner.stage == nil {
scanner.stage = scanner.current
scanner.current = scanner.prev
} else {
err = fmt.Errorf("staging already present, currently one level only of staging is allowed")
}
return
}
func (scanner *scanner) lastPos() (r, c int) {
if scanner.prev != nil {
r = scanner.prev.row
@@ -89,7 +100,12 @@ func (scanner *scanner) Previous() *Token {
func (scanner *scanner) Next() (tk *Token) {
scanner.prev = scanner.current
tk = scanner.current
if scanner.stage != nil {
scanner.current = scanner.stage
scanner.stage = nil
} else {
scanner.current = scanner.fetchNextToken()
}
return tk
}
@@ -124,6 +140,8 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.moveOn(SymDoubleStar, ch, next)
// } else if next == '/' {
// tk = self.moveOn(SymClosedComment, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymStarEqual, ch, next)
} else {
tk = scanner.makeToken(SymStar, ch)
}
@@ -269,11 +287,11 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.makeToken(SymDollar, ch)
}
case '(':
if next, _ := scanner.peek(); next == ')' {
tk = scanner.moveOn(SymOpenClosedRound, ch, next)
} else {
// if next, _ := scanner.peek(); next == ')' {
// tk = scanner.moveOn(SymOpenClosedRound, ch, next)
// } else {
tk = scanner.makeToken(SymOpenRound, ch)
}
// }
case ')':
tk = scanner.makeToken(SymClosedRound, ch)
case '[':
+16
View File
@@ -132,6 +132,14 @@ func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varN
return
}
func (ctx *SimpleStore) VarCount() int {
return len(ctx.varStore)
}
func (ctx *SimpleStore) DeleteVar(varName string) {
delete(ctx.varStore, varName)
}
func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
@@ -163,6 +171,14 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
return
}
func (ctx *SimpleStore) FuncCount() int {
return len(ctx.funcStore)
}
func (ctx *SimpleStore) DeleteFunc(funcName string) {
delete(ctx.funcStore, funcName)
}
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
if info, exists := GetLocalFuncInfo(ctx, name); exists {
functor := info.Functor()
+3
View File
@@ -68,6 +68,7 @@ const (
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymChangeSign
SymUnchangeSign
SymIdentifier
@@ -106,6 +107,7 @@ const (
SymKwIn
SymKwInclude
SymKwNil
SymKwUnset
)
var keywords map[string]Symbol
@@ -123,5 +125,6 @@ func init() {
"NOT": SymKwNot,
"OR": SymKwOr,
"NIL": SymKwNil,
"UNSET": SymKwUnset,
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ func TestAddUnknownTokens(t *testing.T) {
wantErr := errors.New(`unexpected token "%"`)
tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
if _, gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
}
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-iterator.go
package expr
import (
"testing"
)
func TestFuncRun(t *testing.T) {
section := "Builtin-Iterator"
inputs := []inputType{
/* 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},
/* 3 */ {`builtin "iterator"; run($(1,2,3), 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},
/* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed <nil> [nil]`},
/* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, `paramter "operator" must be a function, passed <nil> [nil]`},
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
}
//t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+1 -1
View File
@@ -97,7 +97,7 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
if gotErr != wantErr {
if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr)
t.Errorf("%d: %s -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr)
good = false
}
}
+6 -1
View File
@@ -22,7 +22,7 @@ func TestDictParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 2 */ {`{123}`, nil, errors.New("[1:6] expected `:`, got `}`")},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
@@ -32,6 +32,11 @@ func TestDictParser(t *testing.T) {
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
/* 12 */ {`m={
"a":1,
//"b":2,
"c":3
}`, map[any]any{"a": 1, "c": 3}, nil},
}
succeeded := 0
+15 -4
View File
@@ -17,10 +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 */ {`
/* 6 */ {`a=3; a+=1`, int64(4), nil},
/* 7 */ {`a=3; a-=1`, int64(2), nil},
/* 8 */ {`a=3; a*=2`, 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`},
/* 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 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"init":func(@end){@current=0 but true},
//"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
@@ -32,6 +43,6 @@ func TestExpr(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3)
//runTestSuiteSpec(t, section, inputs, 9)
runTestSuite(t, section, inputs)
}
+2 -1
View File
@@ -27,9 +27,10 @@ func TestFuncs(t *testing.T) {
/* 14 */ {`two=func(){2}; two(123)`, nil, `two(): too much params -- expected 0, got 1`},
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, `[1:16] can't mix default and non-default parameters`},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, `[1:24] expected "function-param-value", got ")"`},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, "[1:24] expected `function-param-value`, got `)`"},
/* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil},
/* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil},
// /* 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 ")"`)},
}
+7 -7
View File
@@ -15,7 +15,7 @@ func TestNewListIterator(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item)
t.Errorf("expected %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
@@ -27,7 +27,7 @@ func TestNewListIterator2(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "d" {
t.Errorf("expcted %q, got %q", "d", item)
t.Errorf("expected %q, got %q", "d", item)
} else {
t.Logf("Next: %v", item)
}
@@ -39,7 +39,7 @@ func TestNewListIterator3(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item)
t.Errorf("expected %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
@@ -51,7 +51,7 @@ func TestNewIterList2(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
@@ -63,7 +63,7 @@ func TestNewIterList3(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
@@ -83,7 +83,7 @@ func TestNewIterList5(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "123" {
t.Errorf("expcted %q, got %q", "123", item)
t.Errorf("expected %q, got %q", "123", item)
} else {
t.Logf("Next: %v", item)
}
@@ -96,7 +96,7 @@ func TestNewIterList6(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
+9 -5
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(2), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), 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},
/* 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},
@@ -20,9 +20,13 @@ func TestIteratorParser(t *testing.T) {
/* 9 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil},
/* 10 */ {`it=$(1,2,3); it++`, int64(1), nil},
/* 11 */ {`it=$(1,2,3); it++; it.reset; it++`, int64(1), nil},
// /* 12 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(1), nil},
/* 12 */ {`it=$([1,2,3,4],1); it++`, int64(2), nil},
/* 13 */ {`it=$([1,2,3,4],1,3); it++; it++;`, int64(3), nil},
/* 14 */ {`it=$([1,2,3,4],1,3,2); it++; it++;`, int64(4), nil},
/* 15 */ {`it=$([1,2,3,4],1,2,2); it++; it++;`, nil, `EOF`},
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
}
//runTestSuiteSpec(t, section, inputs, 12)
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_operator_test.go
package expr
import (
"testing"
)
func TestOperator(t *testing.T) {
section := "Operator"
inputs := []inputType{
/* 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},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+3 -3
View File
@@ -128,8 +128,8 @@ func TestGeneralParser(t *testing.T) {
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 115 */ {`nil`, nil, nil},
/* 116 */ {`null`, nil, `undefined variable or function "null"`},
/* 117 */ {`{"key"}`, nil, `[1:8] expected ":", got "}"`},
/* 118 */ {`{"key":}`, nil, `[1:9] expected "dictionary-value", got "}"`},
/* 117 */ {`{"key"}`, nil, "[1:8] expected `:`, got `}`"},
/* 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},
@@ -144,6 +144,6 @@ func TestGeneralParser(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 102)
//runTestSuiteSpec(t, section, inputs, 130)
runTestSuite(t, section, inputs)
}
+9 -2
View File
@@ -9,8 +9,15 @@ import (
)
func _TestImportPlugin(t *testing.T) {
if err := importPlugin([]string{"test-resources"}, "json"); err != nil {
t.Errorf("importPlugin() failed: %v", err)
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)
}
}
+20 -1
View File
@@ -26,7 +26,7 @@ const (
priSign
priFact
priIterValue
priCoalesce
priDefault
priIncDec
priDot
priValue
@@ -53,6 +53,25 @@ type term struct {
evalFunc evalFuncType
}
func (s *term) Clone() (d *term) {
var children []*term
if s.children != nil {
children = make([]*term, len(s.children))
for i, c := range s.children {
children[i] = c.Clone()
}
}
d = &term{
tk: *s.tk.Clone(),
parent: s.parent,
children: children,
position: s.position,
priority: s.priority,
evalFunc: s.evalFunc,
}
return
}
func (term *term) String() string {
var sb strings.Builder
term.toString(&sb)
+15
View File
@@ -0,0 +1,15 @@
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
, "filter":func(item,index) { item > 0 }
, "map": func(item,index) { item * 2 }
}
/* Example
;
it=$(ds,3);
add(it)
*/
+11 -3
View File
@@ -28,7 +28,7 @@ func (tk *Token) DevString() string {
func (tk *Token) String() string {
if tk.Value != nil {
if s, ok := tk.Value.(string); ok {
return fmt.Sprintf("%q", s)
return s //fmt.Sprintf("%q", s)
} else {
return fmt.Sprintf("%v", tk.Value)
}
@@ -51,6 +51,10 @@ func NewErrorToken(row, col int, err error) *Token {
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
}
func (tk *Token) Clone() (c *Token) {
return NewValueToken(tk.row, tk.col, tk.Sym, tk.source, tk.Value)
}
func (tk *Token) IsEos() bool {
return tk.Sym == SymEos
}
@@ -67,6 +71,10 @@ func (tk *Token) IsOneOf(termSymbols []Symbol) bool {
return termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0
}
func (tk *Token) IsOneOfA(termSymbols ...Symbol) bool {
return slices.Index(termSymbols, tk.Sym) >= 0
}
func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym
}
@@ -91,11 +99,11 @@ func (tk *Token) Errors(msg string) (err error) {
}
func (tk *Token) ErrorExpectedGot(symbol string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", tk.row, tk.col, symbol, tk)
err = fmt.Errorf("[%d:%d] expected `%s`, got `%s`", tk.row, tk.col, symbol, tk)
return
}
func (tk *Token) ErrorExpectedGotString(symbol, got string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", tk.row, tk.col, symbol, got)
err = fmt.Errorf("[%d:%d] expected `%s`, got `%s`", tk.row, tk.col, symbol, got)
return
}
+38
View File
@@ -6,7 +6,11 @@ package expr
import (
"fmt"
"os"
"os/user"
"path"
"reflect"
"strings"
)
func IsString(v any) (ok bool) {
@@ -221,3 +225,37 @@ func ForAll[T, V any](ts []T, fn func(T) V) []V {
}
return result
}
func ExpandPath(sourcePath string) (expandedPath string, err error) {
for expandedPath = os.ExpandEnv(sourcePath); expandedPath != sourcePath; expandedPath = os.ExpandEnv(sourcePath) {
sourcePath = expandedPath
}
if strings.HasPrefix(sourcePath, "~") {
var home, userName, remainder string
slashPos := strings.IndexRune(sourcePath, '/')
if slashPos > 0 {
userName = sourcePath[1:slashPos]
remainder = sourcePath[slashPos:]
} else {
userName = sourcePath[1:]
}
if len(userName) == 0 {
home, err = os.UserHomeDir()
if err != nil {
return
}
} else {
var userInfo *user.User
userInfo, err = user.Lookup(userName)
if err != nil {
return
}
home = userInfo.HomeDir
}
expandedPath = path.Join(home, remainder)
}
return
}