Compare commits
16 Commits
730b59e6d3
...
62e16219f7
Author | SHA1 | Date | |
---|---|---|---|
62e16219f7 | |||
750c660331 | |||
7a88449cd1 | |||
b14dc2f1ee | |||
d354102c6a | |||
761ec868e6 | |||
7941c2dfec | |||
ebb2811ed3 | |||
75c0c0f681 | |||
268a968548 | |||
323308d86f | |||
b28d6a8f02 | |||
ab82bcf1ef | |||
a628bfac39 | |||
d1122da566 | |||
6ae5ca34ed |
3
ast.go
3
ast.go
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
42
iter-list.go
Normal 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
|
||||
}
|
31
iterator.go
31
iterator.go
@ -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
38
operand-dict.go
Normal 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
|
||||
}
|
@ -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
152
operand-iterator.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
38
operator-iter-value.go
Normal 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
41
operator-post-inc.go
Normal 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
194
parser.go
@ -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)
|
||||
|
@ -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
|
||||
|
17
scanner.go
17
scanner.go
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
15
term.go
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user