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() { if tree.isComplete() {
var subRoot *term var subRoot *term
last := tree.removeLastChild() last := tree.removeLastChild()
subRoot, err = self.insert(last, node) if subRoot, err = self.insert(last, node); err == nil {
subRoot.setParent(tree) subRoot.setParent(tree)
}
} else { } else {
node.setParent(tree) node.setParent(tree)
} }

View File

@ -36,7 +36,7 @@ type ExprContext interface {
setVar(varName string, value any) setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string) EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []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) Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int) RegisterFunc(name string, f Functor, minArgs, maxArgs int)
} }

View File

@ -21,6 +21,7 @@ func TestExpr(t *testing.T) {
/* 1 */ {`0?{}`, nil, nil}, /* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), 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}, /* 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 succeeded := 0

View File

@ -45,6 +45,10 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return return
} }
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func ImportBuiltinsFuncs(ctx ExprContext) { func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1) ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 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("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 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 var v any
for v, err = it.Next(); err == nil; v, err = it.Next() { 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 { if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break break
} }
if array, ok := v.([]any); ok { if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil { if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break break
} }
} }
}
if !sumAsFloat && isFloat(v) { if !sumAsFloat && isFloat(v) {
sumAsFloat = true sumAsFloat = true

View File

@ -162,3 +162,7 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2) ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1) 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 // iterator.go
package expr package expr
import "io"
type Iterator interface { type Iterator interface {
Reset()
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)
Index() int 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 { func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil, parent: nil,
children: args, children: args,
position: posLeaf, position: posLeaf,
@ -44,7 +42,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
} }
// Export functions // Export functions
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) { 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) 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 // -------- eval func
func evalVar(ctx ExprContext, self *term) (v any, err error) { func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool var exists bool
if v, exists = ctx.GetVar(self.tk.source); !exists { name := self.source()
err = fmt.Errorf("undefined variable %q", self.tk.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 return
} }

View File

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

View File

@ -4,6 +4,8 @@
// operator-dot.go // operator-dot.go
package expr package expr
import "fmt"
// -------- dot term // -------- dot term
func newDotTerm(tk *Token) (inst *term) { func newDotTerm(tk *Token) (inst *term) {
return &term{ return &term{
@ -23,15 +25,13 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
} }
indexTerm := self.children[1] 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 return
} }
index64, _ := rightValue.(int64)
index := int(index64)
if isList(leftValue) {
list, _ := leftValue.([]any) list, _ := leftValue.([]any)
if index >= 0 && index < len(list) { if index >= 0 && index < len(list) {
v = list[index] 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) err = indexTerm.Errorf("index %v out of bounds", index)
} }
} else if isString(leftValue) { } 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) s, _ := leftValue.(string)
if index >= 0 && index < len(s) { if index >= 0 && index < len(s) {
v = string(s[index]) v = string(s[index])
@ -49,6 +54,12 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
} else { } else {
err = indexTerm.Errorf("index %v out of bounds", index) 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 { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) 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 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) { func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y} // Example: "add = func(x,y) {x+y}
var body *ast var body *ast
args := make([]*term, 0) args := make([]*term, 0)
tk := scanner.Next() lastSym := SymUnknown
for tk.Sym != SymClosedRound && tk.Sym != SymEos { itemExpected := false
if tk.Sym == SymIdentifier { tk := scanner.Previous()
t := newTerm(tk, nil) for lastSym != SymClosedRound && lastSym != SymEos {
args = append(args, t) 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 { } 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 break
} }
tk = scanner.Next() } else {
break
} }
if err == nil && tk.Sym != SymClosedRound { lastSym = scanner.Previous().Sym
err = tk.Errorf("unterminate function params list") itemExpected = lastSym == SymComma
}
if err == nil && lastSym != SymClosedRound {
err = tk.Errorf("unterminated function parameters list")
} }
if err == nil { if err == nil {
tk = scanner.Next() 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) { func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos { for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil { if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil { if subTree.root != nil {
args = append(args, subTree.root) args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected list item, got %q", prev)
break
} }
} else { } else {
break break
} }
lastSym = scanner.Previous().Sym lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments // TODO Check arguments
@ -119,6 +174,115 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
return 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) { func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term var filterList *term
var caseExpr *ast var caseExpr *ast
@ -241,6 +405,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(listTerm) err = tree.addTerm(listTerm)
currentTerm = 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: case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil { if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
@ -251,6 +421,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(funcDefTerm) err = tree.addTerm(funcDefTerm)
currentTerm = 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: case SymIdentifier:
if tk.source[0] == '@' && !allowVarRef { if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source) err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)

View File

@ -7,6 +7,7 @@ package expr
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"strings" "strings"
"testing" "testing"
) )
@ -117,9 +118,9 @@ func TestParser(t *testing.T) {
/* 96 */ {`x=2 but x*10`, int64(20), nil}, /* 96 */ {`x=2 but x*10`, int64(20), nil},
/* 97 */ {`false and true`, false, nil}, /* 97 */ {`false and true`, false, nil},
/* 98 */ {`false and (x==2)`, 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}, /* 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}, /* 102 */ {`a=5; a`, int64(5), nil},
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), 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`)}, /* 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}, /* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 114 */ {`x ?? "default"`, "default", nil}, /* 114 */ {`x ?? "default"`, "default", nil},
/* 115 */ {`x="hello"; x ?? "default"`, "hello", 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}, /* 117 */ {`x ?= "default"; x`, "default", nil},
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", 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"`)}, /* 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}, /* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), 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}, /* 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}, /* 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}, /* 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}, /* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil}, /* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 138 */ {`nil`, nil, 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 check_env_expr_path := 113
@ -165,7 +170,7 @@ func TestParser(t *testing.T) {
failed := 0 failed := 0
// inputs1 := []inputType{ // inputs1 := []inputType{
// /* 140 */ {`isNil(nil)`, true, nil}, // /* 140 */ {`ds={}; $(ds)`, nil, nil},
// } // }
for i, input := range inputs { for i, input := range inputs {
@ -190,7 +195,9 @@ func TestParser(t *testing.T) {
gotResult, gotErr = expr.Eval(ctx) 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) t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false good = false
} }
@ -221,10 +228,6 @@ func TestListParser(t *testing.T) {
wantErr error wantErr error
} }
// inputs1 := []inputType{
// {`add(1,2,3)`, int64(6), nil},
// }
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil}, /* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
@ -244,6 +247,10 @@ func TestListParser(t *testing.T) {
succeeded := 0 succeeded := 0
failed := 0 failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs { for i, input := range inputs {
var expr *ast var expr *ast
var gotResult any var gotResult any

View File

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

View File

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

View File

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

View File

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

15
term.go
View File

@ -23,7 +23,9 @@ const (
priSelector priSelector
priSign priSign
priFact priFact
priIterValue
priCoalesce priCoalesce
priPrePost
priDot priDot
priValue 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 { func (self *term) source() string {
return self.tk.source return self.tk.source
} }
@ -144,6 +150,15 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
return 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 { func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
return self.tk.Errorf( return self.tk.Errorf(
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q", "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 return ok
} }
func isDict(v any) (ok bool) {
_, ok = v.(map[any]any)
return ok
}
func isNumber(v any) (ok bool) { func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v) return isFloat(v) || isInteger(v)
} }