Compare commits

...

16 Commits

Author SHA1 Message Date
62e16219f7 func-math.go/add: now supports the general iterator interface 2024-04-26 04:47:59 +02:00
750c660331 first working implementation of iterators and some fixes to the parser around lists analysis 2024-04-26 04:45:42 +02:00
7a88449cd1 updated some error messages; add some tests on the dict data-type; Use of reflect.DeepEqual() to compare the test results with the desired results. 2024-04-26 04:43:36 +02:00
b14dc2f1ee provisional implementation of the postfix ++ operator 2024-04-26 04:37:50 +02:00
d354102c6a adapted to the new GetFuncInfo() specification 2024-04-26 04:36:03 +02:00
761ec868e6 operator-assign.go: little refactor 2024-04-26 04:31:31 +02:00
7941c2dfec the FlatArrayIterator has been moved to the new file iter-list.go 2024-04-26 04:30:43 +02:00
ebb2811ed3 context.go: added exists return value to the GetFuncInfo() 2024-04-26 04:28:50 +02:00
75c0c0f681 Fix ast.go: the insert() didn't check the returned error 2024-04-26 04:26:20 +02:00
268a968548 term.go: added two new priorities (priIterValue and priPrePost); new function term.symbol()) 2024-04-26 04:23:39 +02:00
323308d86f expressions now support dict data-type 2024-04-21 14:24:56 +02:00
b28d6a8f02 commented out a test for a future new operator based on the caret symbol 2024-04-21 07:12:59 +02:00
ab82bcf1ef preparation for the definition of the iterators 2024-04-21 07:11:58 +02:00
a628bfac39 New symbol '^' (caret). The scanner now returns an error token if can't recognise a symbol. 2024-04-21 07:10:19 +02:00
d1122da566 coalesce operators '??' and '?=' now accepts function definitions too 2024-04-20 09:40:07 +02:00
6ae5ca34ed expr_test.go, more tests on the int() function 2024-04-20 08:50:05 +02:00
26 changed files with 626 additions and 88 deletions

3
ast.go
View File

@ -83,8 +83,9 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
subRoot, err = self.insert(last, node)
if subRoot, err = self.insert(last, node); err == nil {
subRoot.setParent(tree)
}
} else {
node.setParent(tree)
}

View File

@ -36,7 +36,7 @@ type ExprContext interface {
setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) ExprFunc
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
}

View File

@ -21,6 +21,7 @@ func TestExpr(t *testing.T) {
/* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
}
succeeded := 0

View File

@ -45,6 +45,10 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)

View File

@ -140,3 +140,7 @@ func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
}
func init() {
registerImport("import", ImportImportFuncs)
}

View File

@ -23,15 +23,20 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if subIter, ok := v.(Iterator); ok {
if v, err = doAdd(ctx, name, subIter); err != nil {
break
}
} else {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
}
if !sumAsFloat && isFloat(v) {
sumAsFloat = true

View File

@ -162,3 +162,7 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
}
func init() {
registerImport("os", ImportOsFuncs)
}

42
iter-list.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-list.go
package expr
import "io"
type FlatArrayIterator struct {
a []any
index int
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}
func (it *FlatArrayIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.a) {
item = it.a[it.index]
} else {
err = io.EOF
}
return
}
func (it *FlatArrayIterator) Next() (item any, err error) {
if item, err = it.Current(); err != io.EOF {
it.index++
}
// if it.index < len(it.a) {
// item = it.a[it.index]
// it.index++
// } else {
// err = io.EOF
// }
return
}
func (it *FlatArrayIterator) Index() int {
return it.index - 1
}

View File

@ -4,37 +4,8 @@
// iterator.go
package expr
import "io"
type Iterator interface {
Reset()
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
}
type FlatArrayIterator struct {
a []any
index int
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}
func (it *FlatArrayIterator) Reset() {
it.index = 0
}
func (it *FlatArrayIterator) Next() (item any, err error) {
if it.index < len(it.a) {
item = it.a[it.index]
it.index++
} else {
err = io.EOF
}
return
}
func (it *FlatArrayIterator) Index() int {
return it.index - 1
}

38
operand-dict.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-dict.go
package expr
// -------- dict term
// func newDictTermA(args ...*term) *term {
// return newDictTerm(args)
// }
func newDictTerm(args map[any]*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymDict, "{}", args),
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalDict,
}
}
// -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) {
dict, _ := self.value().(map[any]*term)
items := make(map[any]any, len(dict))
for key, tree := range dict {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
items[key] = param
}
if err == nil {
v = items
}
return
}

View File

@ -12,8 +12,6 @@ import (
func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil,
children: args,
position: posLeaf,
@ -44,7 +42,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
}
// Export functions
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info := ctx.GetFuncInfo(refName); info != nil {
if info, _ := ctx.GetFuncInfo(refName); info != nil {
exportFunc(parentCtx, refName, info)
}
}

152
operand-iterator.go Normal file
View File

@ -0,0 +1,152 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-iterator.go
package expr
import (
"fmt"
"io"
)
// -------- iterator term
const (
initName = "init"
nextName = "next"
currentName = "current"
)
type dataCursor struct {
ds map[any]*term
ctx ExprContext
index int
resource any
nextFunc Functor
currentFunc Functor
}
func (dc *dataCursor) String() string {
var s string
if item, err := dc.Current(); err == nil {
s = fmt.Sprintf("%v", item)
} else {
s = "(nil)"
}
return s
}
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
if item, err = dc.currentFunc.Invoke(dc.ctx, currentName, []any{}); err == nil && item == nil {
err = io.EOF
}
return
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
if item, err = dc.nextFunc.Invoke(dc.ctx, nextName, []any{}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
}
}
return
}
func (dc *dataCursor) Index() int {
return dc.index
}
func newIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
tk.Sym = SymIterator
children := make([]*term, 0, 1+len(args))
children = append(children, dsTerm)
children = append(children, args...)
return &term{
tk: *tk,
parent: nil,
children: children,
position: posLeaf,
priority: priValue,
evalFunc: evalIterator,
}
}
// -------- eval iterator
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
values = make([]any, len(a))
for i, t := range a {
var value any
if value, err = t.compute(ctx); err == nil {
values[i] = value
} else {
break
}
}
return
}
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
var value any
if len(self.children) < 1 || self.children[0] == nil {
err = self.Errorf("missing the data-source parameter")
return
}
if value, err = self.children[0].compute(ctx); err != nil {
return
}
if dictAny, ok := value.(map[any]any); ok {
ds = make(map[string]Functor)
for _, k := range []string{initName, currentName, nextName} {
if item, exists := dictAny[k]; exists && item != nil {
if functor, ok := item.(*funcDefFunctor); ok {
ds[k] = functor
}
} else if k != initName {
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
break
}
}
} else {
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
}
return
}
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
var ds map[string]Functor
if ds, err = getDataSourceDict(ctx, self); err != nil {
return
}
dc := &dataCursor{
index: -1,
ctx: ctx.Clone(),
}
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var args []any
if len(self.children) > 1 {
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
if dc.resource, err = initFunc.Invoke(dc.ctx, initName, args); err != nil {
return
}
}
dc.nextFunc, _ = ds[nextName]
dc.currentFunc, _ = ds[currentName]
v = dc
return
}

View File

@ -23,8 +23,13 @@ func newVarTerm(tk *Token) *term {
// -------- eval func
func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool
if v, exists = ctx.GetVar(self.tk.source); !exists {
err = fmt.Errorf("undefined variable %q", self.tk.source)
name := self.source()
if v, exists = ctx.GetVar(name); !exists {
if info, exists := ctx.GetFuncInfo(name); exists {
v = info.Functor()
} else {
err = fmt.Errorf("undefined variable or function %q", name)
}
}
return
}

View File

@ -31,7 +31,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if functor, ok := v.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else {
ctx.setVar(leftTerm.tk.source, v)
ctx.setVar(leftTerm.source(), v)
}
}
return

View File

@ -34,11 +34,11 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if _, ok := rightValue.(Functor); ok {
err = errCoalesceNoFunc(self.children[1])
} else {
// if _, ok := rightValue.(Functor); ok {
// err = errCoalesceNoFunc(self.children[1])
// } else {
v = rightValue
}
// }
}
return
}
@ -71,8 +71,8 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if _, ok := rightValue.(Functor); ok {
err = errCoalesceNoFunc(self.children[1])
if functor, ok := rightValue.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else {
v = rightValue
ctx.setVar(leftTerm.source(), rightValue)
@ -82,9 +82,9 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
}
// utils
func errCoalesceNoFunc(t *term) error {
return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
}
// func errCoalesceNoFunc(t *term) error {
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
// }
// init
func init() {

View File

@ -4,6 +4,8 @@
// operator-dot.go
package expr
import "fmt"
// -------- dot term
func newDotTerm(tk *Token) (inst *term) {
return &term{
@ -23,15 +25,13 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
}
indexTerm := self.children[1]
if !isInteger(rightValue) {
err = indexTerm.Errorf("index expression must be integer, got %T", rightValue)
if isList(leftValue) {
var index int
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
return
}
index64, _ := rightValue.(int64)
index := int(index64)
if isList(leftValue) {
list, _ := leftValue.([]any)
if index >= 0 && index < len(list) {
v = list[index]
@ -41,6 +41,11 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
err = indexTerm.Errorf("index %v out of bounds", index)
}
} else if isString(leftValue) {
var index int
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
return
}
s, _ := leftValue.(string)
if index >= 0 && index < len(s) {
v = string(s[index])
@ -49,6 +54,12 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
}
} else if isDict(leftValue) {
var ok bool
d, _ := leftValue.(map[any]any)
if v, ok = d[rightValue]; !ok {
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}

38
operator-iter-value.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-iter-value.go
package expr
//-------- iter value term
func newIterValueTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIterValue,
evalFunc: evalIterValue,
}
}
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
return
}
if dc, ok := leftValue.(*dataCursor); ok {
v, err = dc.Current()
} else {
err = self.errIncompatibleType(leftValue)
}
return
}
// init
func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
}

41
operator-post-inc.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
package expr
// -------- post increment term
func newPostIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priPrePost,
evalFunc: evalPostInc,
}
}
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
return
}
if dc, ok := leftValue.(*dataCursor); ok {
v, err = dc.Next()
} else if isInteger(leftValue) && self.children[0].symbol() == SymIdentifier {
v = leftValue
i, _ := leftValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
} else {
self.errIncompatibleType(leftValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm)
}

194
parser.go
View File

@ -58,23 +58,72 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
return
}
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// // Example: "add = func(x,y) {x+y}
// var body *ast
// args := make([]*term, 0)
// tk := scanner.Next()
// for tk.Sym != SymClosedRound && tk.Sym != SymEos {
// if tk.Sym == SymIdentifier {
// t := newTerm(tk, nil)
// args = append(args, t)
// } else {
// err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
// break
// }
// tk = scanner.Next()
// }
// if err == nil && tk.Sym != SymClosedRound {
// err = tk.Errorf("unterminate function params list")
// }
// if err == nil {
// tk = scanner.Next()
// if tk.Sym == SymOpenBrace {
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
// }
// }
// if err == nil {
// // TODO Check arguments
// if scanner.Previous().Sym != SymClosedBrace {
// err = scanner.Previous().Errorf("not properly terminated function body")
// } else {
// tk = scanner.makeValueToken(SymExpression, "", body)
// tree = newFuncDefTerm(tk, args)
// }
// }
// return
// }
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y}
var body *ast
args := make([]*term, 0)
tk := scanner.Next()
for tk.Sym != SymClosedRound && tk.Sym != SymEos {
if tk.Sym == SymIdentifier {
t := newTerm(tk, nil)
args = append(args, t)
lastSym := SymUnknown
itemExpected := false
tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
if subTree.root.symbol() == SymIdentifier {
args = append(args, subTree.root)
} else {
err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
err = tk.Errorf("exptected identifier, got %q", subTree.root)
}
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected function parameter, got %q", prev)
break
}
tk = scanner.Next()
} else {
break
}
if err == nil && tk.Sym != SymClosedRound {
err = tk.Errorf("unterminate function params list")
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil && lastSym != SymClosedRound {
err = tk.Errorf("unterminated function parameters list")
}
if err == nil {
tk = scanner.Next()
@ -97,16 +146,22 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected list item, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
@ -119,6 +174,115 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
return
}
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
var ds *term
tk := scanner.Previous()
args := make([]*term, 0)
lastSym := SymUnknown
dsExpected := true
itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
if dsExpected {
if sym := subTree.root.symbol(); sym == SymDict || sym == SymIdentifier {
ds = subTree.root
} else {
err = subTree.root.Errorf("data-source dictionary expected, got %q", subTree.root.source())
}
dsExpected = false
} else {
args = append(args, subTree.root)
}
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected iterator argument, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound {
err = scanner.Previous().Errorf("unterminate iterator param list")
} else if ds != nil {
subtree = newIteratorTerm(tk, ds, args)
} else {
tk.Errorf("missing data-source param")
}
}
return
}
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
tk := scanner.Next()
if tk.Sym == SymError {
err = tk.Error()
return
}
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
return
}
if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next()
if tkSep.Sym != SymColon {
err = tkSep.Errorf("expected \":\", got %q", tkSep)
} else {
key = tk.Value
}
} else {
err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
}
return
}
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make(map[any]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast
var key any
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
break
} else if key == nil {
tk := scanner.Previous()
lastSym = tk.Sym
if itemExpected {
err = tk.Errorf("expected dictionary key, got %q", tk)
}
break
}
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
if subTree.root != nil {
args[key] = subTree.root
} else if key != nil {
prev := scanner.Previous()
err = prev.Errorf("expected dictionary value, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedBrace {
err = scanner.Previous().Errorf("unterminated dictionary")
} else {
subtree = newDictTerm(args)
}
}
return
}
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term
var caseExpr *ast
@ -241,6 +405,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(listTerm)
currentTerm = listTerm
}
case SymOpenBrace:
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk)
@ -251,6 +421,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(funcDefTerm)
currentTerm = funcDefTerm
}
case SymDollarRound:
var iterDefTerm *term
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
err = tree.addTerm(iterDefTerm)
currentTerm = iterDefTerm
}
case SymIdentifier:
if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)

View File

@ -7,6 +7,7 @@ package expr
import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
)
@ -117,9 +118,9 @@ func TestParser(t *testing.T) {
/* 96 */ {`x=2 but x*10`, int64(20), nil},
/* 97 */ {`false and true`, false, nil},
/* 98 */ {`false and (x==2)`, false, nil},
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable "x"`)},
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
/* 100 */ {`false or true`, true, nil},
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
/* 102 */ {`a=5; a`, int64(5), nil},
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
@ -134,13 +135,13 @@ func TestParser(t *testing.T) {
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 114 */ {`x ?? "default"`, "default", nil},
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
/* 116 */ {`x ?? func(){}`, nil, errors.New(`[1:14] the right operand of a coalescing operation cannot be a function definition`)},
/* 116 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
/* 117 */ {`x ?= "default"; x`, "default", nil},
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)},
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
@ -157,7 +158,11 @@ func TestParser(t *testing.T) {
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 138 */ {`nil`, nil, nil},
/* 139 */ {`null`, nil, errors.New(`undefined variable "null"`)},
/* 139 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
/* 140 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
/* 141 */ {`{"key":}`, nil, errors.New(`[1:9] expected dictionary value, got "}"`)},
/* 142 */ {`{}`, map[any]any{}, nil},
/* 144 */ //{`3^2`, int64(9), nil},
}
check_env_expr_path := 113
@ -165,7 +170,7 @@ func TestParser(t *testing.T) {
failed := 0
// inputs1 := []inputType{
// /* 140 */ {`isNil(nil)`, true, nil},
// /* 140 */ {`ds={}; $(ds)`, nil, nil},
// }
for i, input := range inputs {
@ -190,7 +195,9 @@ func TestParser(t *testing.T) {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
@ -221,10 +228,6 @@ func TestListParser(t *testing.T) {
wantErr error
}
// inputs1 := []inputType{
// {`add(1,2,3)`, int64(6), nil},
// }
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
@ -244,6 +247,10 @@ func TestListParser(t *testing.T) {
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any

View File

@ -86,13 +86,14 @@ func (self *scanner) Next() (tk *Token) {
}
func (self *scanner) fetchNextToken() (tk *Token) {
var ch byte
if err := self.skipBlanks(); err != nil {
return self.makeErrorToken(err)
}
escape := false
for {
ch, _ := self.readChar()
ch, _ = self.readChar()
switch ch {
case '+':
if next, _ := self.peek(); next == '+' {
@ -143,6 +144,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
}
case ',':
tk = self.makeToken(SymComma, ch)
case '^':
tk = self.makeToken(SymCaret, ch)
case ':':
if next, _ := self.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next)
@ -236,9 +239,18 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymGreater, ch)
}
case '$':
if next, _ := self.peek(); next == '(' {
tk = self.moveOn(SymDollarRound, ch, next)
tk.source += ")"
} else {
tk = self.makeToken(SymDollar, ch)
}
case '(':
if next, _ := self.peek(); next == ')' {
tk = self.moveOn(SymOpenClosedRound, ch, next)
} else {
tk = self.makeToken(SymOpenRound, ch)
}
case ')':
tk = self.makeToken(SymClosedRound, ch)
case '[':
@ -271,6 +283,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
break
}
}
if tk == nil {
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
}
return
}

View File

@ -50,8 +50,8 @@ func (ctx *SimpleFuncStore) Clone() ExprContext {
}
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc) {
info, _ = ctx.funcStore[name]
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}

View File

@ -54,7 +54,7 @@ func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (v
return
}
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
return
}

View File

@ -61,6 +61,9 @@ const (
SymDoubleColon // 50: '::'
SymInsert // 51: '>>'
SymAppend // 52: '<<'
SymCaret // 53: '^'
SymDollarRound // 54: '$('
SymOpenClosedRound // 55: '()'
SymChangeSign
SymUnchangeSign
SymIdentifier
@ -68,6 +71,7 @@ const (
SymInteger
SymFloat
SymString
SymIterator
SymOr
SymAnd
SymNot
@ -75,6 +79,7 @@ const (
SymFuncCall
SymFuncDef
SymList
SymDict
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"

15
term.go
View File

@ -23,7 +23,9 @@ const (
priSelector
priSign
priFact
priIterValue
priCoalesce
priPrePost
priDot
priValue
)
@ -127,6 +129,10 @@ func (self *term) setParent(parent *term) {
}
}
func (self *term) symbol() Symbol {
return self.tk.Sym
}
func (self *term) source() string {
return self.tk.source
}
@ -144,6 +150,15 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
return
}
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
if index64, ok := computedValue.(int64); ok {
i = int(index64)
} else {
err = self.Errorf("%s, got %T", valueDescription, computedValue)
}
return
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
return self.tk.Errorf(
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",

View File

@ -26,6 +26,11 @@ func isList(v any) (ok bool) {
return ok
}
func isDict(v any) (ok bool) {
_, ok = v.(map[any]any)
return ok
}
func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v)
}