Compare commits
42 Commits
54bc759f70
..
v0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 723976b37e | |||
| 361b84f31f | |||
| 70892aa980 | |||
| 10eec286fa | |||
| 894b1884eb | |||
| d2bab5fd9e | |||
| f94f369547 | |||
| 107ec4958f | |||
| a22047e84e | |||
| 80b7d5b988 | |||
| 2ab896bbac | |||
| 62e16219f7 | |||
| 750c660331 | |||
| 7a88449cd1 | |||
| b14dc2f1ee | |||
| d354102c6a | |||
| 761ec868e6 | |||
| 7941c2dfec | |||
| ebb2811ed3 | |||
| 75c0c0f681 | |||
| 268a968548 | |||
| 323308d86f | |||
| b28d6a8f02 | |||
| ab82bcf1ef | |||
| a628bfac39 | |||
| d1122da566 | |||
| 6ae5ca34ed | |||
| 730b59e6d3 | |||
| f198ba47e1 | |||
| 943ef3327e | |||
| 475ef3c80a | |||
| 3c0307524b | |||
| c27e487fc3 | |||
| ed973c9b7b | |||
| 15bbfacd47 | |||
| 04f934ab04 | |||
| 591b4ffc19 | |||
| fe9ab9ebd2 | |||
| 7198749063 | |||
| b76481bbf2 | |||
| 4f05e5c90a | |||
| 8ad25afdc4 |
@@ -5,7 +5,6 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -84,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)
|
||||
subRoot.setParent(tree)
|
||||
if subRoot, err = self.insert(last, node); err == nil {
|
||||
subRoot.setParent(tree)
|
||||
}
|
||||
} else {
|
||||
node.setParent(tree)
|
||||
}
|
||||
@@ -129,8 +129,8 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
||||
if err == nil {
|
||||
result, err = self.root.compute(ctx)
|
||||
}
|
||||
} else {
|
||||
err = errors.New("empty expression")
|
||||
// } else {
|
||||
// err = errors.New("empty expression")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// context-helpers.go
|
||||
package expr
|
||||
|
||||
func cloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
|
||||
if sourceCtx != nil {
|
||||
clonedCtx = sourceCtx.Clone()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func exportVar(ctx ExprContext, name string, value any) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.setVar(name, value)
|
||||
}
|
||||
|
||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||
}
|
||||
|
||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||
exportAll := isEnabled(sourceCtx, control_export_all)
|
||||
// Export variables
|
||||
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
refValue, _ := sourceCtx.GetVar(refName)
|
||||
exportVar(destCtx, refName, refValue)
|
||||
}
|
||||
// Export functions
|
||||
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
|
||||
exportFunc(destCtx, refName, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// data-cursors.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
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 newDataCursor(ctx ExprContext) (dc *dataCursor) {
|
||||
dc = &dataCursor{
|
||||
index: -1,
|
||||
ctx: ctx.Clone(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) String() string {
|
||||
return "$(...)"
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||
ctx := cloneContext(dc.ctx)
|
||||
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
exportObjects(dc.ctx, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||
ctx := cloneContext(dc.ctx)
|
||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
|
||||
if item == nil {
|
||||
err = io.EOF
|
||||
} else {
|
||||
dc.index++
|
||||
}
|
||||
}
|
||||
exportObjects(dc.ctx, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Index() int {
|
||||
return dc.index
|
||||
}
|
||||
+3
-1
@@ -22,6 +22,8 @@ Expressions calculator
|
||||
|
||||
toc::[]
|
||||
|
||||
#TODO: Work in progress#
|
||||
|
||||
== Expr
|
||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||
|
||||
@@ -182,7 +184,7 @@ a=1; b=2; c=3; a+b+c // returns 6
|
||||
=== [blue]`but` operator
|
||||
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
|
||||
|
||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`;(` and [blue]`)`.
|
||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||
|
||||
=== Assignment operator [blue]`=`
|
||||
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
|
||||
|
||||
+16
-3
@@ -18,15 +18,28 @@ func TestExpr(t *testing.T) {
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
||||
/* 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
|
||||
failed := 0
|
||||
|
||||
inputs1 := []inputType{
|
||||
{`f=openFile("/tmp/test2.txt"); line=readFile(f); closeFile(f); line`, "ciao", nil},
|
||||
//{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
|
||||
/* 1 */ {`
|
||||
ds={
|
||||
"init":func(end){@end=end; @current=0 but true},
|
||||
"current":func(){current},
|
||||
"next":func(){
|
||||
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
|
||||
}
|
||||
};
|
||||
it=$(ds,3);
|
||||
it++;
|
||||
it++
|
||||
`, int64(1), nil},
|
||||
}
|
||||
|
||||
for i, input := range inputs1 {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-builtins.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) == 1 {
|
||||
result = args[0] == nil
|
||||
} else {
|
||||
err = errOneParam(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) == 1 {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
result = v
|
||||
case float64:
|
||||
result = int64(math.Trunc(v))
|
||||
case bool:
|
||||
if v {
|
||||
result = int64(1)
|
||||
} else {
|
||||
result = int64(0)
|
||||
}
|
||||
case string:
|
||||
var i int
|
||||
if i, err = strconv.Atoi(v); err == nil {
|
||||
result = int64(i)
|
||||
}
|
||||
default:
|
||||
err = errCantConvert(name, v, "int")
|
||||
}
|
||||
} else {
|
||||
err = errOneParam(name)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("builtins", ImportBuiltinsFuncs)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-common.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func errOneParam(funcName string) error {
|
||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
}
|
||||
|
||||
func errCantConvert(funcName string, value any, kind string) error {
|
||||
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+15
-6
@@ -23,14 +23,19 @@ 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 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 {
|
||||
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) {
|
||||
@@ -108,3 +113,7 @@ func ImportMathFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("math.arith", ImportMathFuncs)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// funcs_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncs(t *testing.T) {
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`isNil(nil)`, true, nil},
|
||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||
/* 3 */ {`v=5; isNil(v)`, false, nil},
|
||||
/* 4 */ {`int(true)`, int64(1), nil},
|
||||
/* 5 */ {`int(false)`, int64(0), nil},
|
||||
/* 6 */ {`int(3.1)`, int64(3), nil},
|
||||
/* 7 */ {`int(3.9)`, int64(3), nil},
|
||||
/* 8 */ {`int("432")`, int64(432), nil},
|
||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// /* 1 */ {`0?{}`, nil, nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
// ImportMathFuncs(ctx)
|
||||
// ImportImportFunc(ctx)
|
||||
// ImportOsFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good := true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
if 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
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// function-register.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var functionRegister map[string]func(ExprContext)
|
||||
|
||||
func registerImport(name string, importFunc func(ExprContext)) {
|
||||
if functionRegister == nil {
|
||||
functionRegister = make(map[string]func(ExprContext))
|
||||
}
|
||||
functionRegister[name] = importFunc
|
||||
}
|
||||
|
||||
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||
var importFunc func(ExprContext)
|
||||
if importFunc, exists = functionRegister[name]; exists {
|
||||
importFunc(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||
var matched bool
|
||||
for name, importFunc := range functionRegister {
|
||||
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||
if matched {
|
||||
count++
|
||||
importFunc(ctx)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
if functionRegister == nil {
|
||||
functionRegister = make(map[string]func(ExprContext))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+1
-30
@@ -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
|
||||
}
|
||||
|
||||
+7
-48
@@ -4,22 +4,8 @@
|
||||
// operand-const.go
|
||||
package expr
|
||||
|
||||
// -------- bool const term
|
||||
func newBoolTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classConst,
|
||||
// kind: kindBool,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalConst,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- integer const term
|
||||
func newIntegerTerm(tk *Token) *term {
|
||||
// -------- const term
|
||||
func newConstTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
@@ -30,34 +16,6 @@ func newIntegerTerm(tk *Token) *term {
|
||||
}
|
||||
}
|
||||
|
||||
// -------- float const term
|
||||
func newFloatTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classConst,
|
||||
// kind: kindFloat,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalConst,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- string const term
|
||||
func newStringTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classConst,
|
||||
// kind: kindString,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalConst,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- eval func
|
||||
func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = self.tk.Value
|
||||
@@ -66,8 +24,9 @@ func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymString, newStringTerm)
|
||||
registerTermConstructor(SymInteger, newIntegerTerm)
|
||||
registerTermConstructor(SymFloat, newFloatTerm)
|
||||
registerTermConstructor(SymBool, newBoolTerm)
|
||||
registerTermConstructor(SymString, newConstTerm)
|
||||
registerTermConstructor(SymInteger, newConstTerm)
|
||||
registerTermConstructor(SymFloat, newConstTerm)
|
||||
registerTermConstructor(SymBool, newConstTerm)
|
||||
registerTermConstructor(SymKwNil, newConstTerm)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operand-dict.go
|
||||
package expr
|
||||
|
||||
// -------- dict term
|
||||
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
|
||||
}
|
||||
@@ -27,8 +27,3 @@ func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
// func init() {
|
||||
// registerTermConstructor(SymExpression, newExprTerm)
|
||||
// }
|
||||
|
||||
+3
-36
@@ -11,9 +11,7 @@ import (
|
||||
// -------- function call term
|
||||
func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classVar,
|
||||
// kind: kindUnknown,
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
children: args,
|
||||
position: posLeaf,
|
||||
@@ -24,7 +22,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
|
||||
// -------- eval func call
|
||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
ctx := parentCtx.Clone()
|
||||
ctx := cloneContext(parentCtx)
|
||||
name, _ := self.tk.Value.(string)
|
||||
params := make([]any, len(self.children))
|
||||
for i, tree := range self.children {
|
||||
@@ -36,37 +34,12 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
if err == nil {
|
||||
if v, err = ctx.Call(name, params); err == nil {
|
||||
exportAll := isEnabled(ctx, control_export_all)
|
||||
// Export variables
|
||||
for _, refName := range ctx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
refValue, _ := ctx.GetVar(refName)
|
||||
exportVar(parentCtx, refName, refValue)
|
||||
}
|
||||
// Export functions
|
||||
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
if info := ctx.GetFuncInfo(refName); info != nil {
|
||||
exportFunc(parentCtx, refName, info)
|
||||
}
|
||||
}
|
||||
exportObjects(parentCtx, ctx)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func exportVar(ctx ExprContext, name string, value any) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.setVar(name, value)
|
||||
}
|
||||
|
||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||
}
|
||||
|
||||
// -------- function definition term
|
||||
func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||
return &term{
|
||||
@@ -109,12 +82,6 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||
paramList := make([]string, 0, len(self.children))
|
||||
for _, param := range self.children {
|
||||
paramList = append(paramList, param.source())
|
||||
// if paramName, ok := param.value().(string); ok {
|
||||
// paramList = append(paramList, paramName)
|
||||
// } else {
|
||||
// err = fmt.Errorf("invalid function definition: formal param nr %d must be an identifier", i+1)
|
||||
// break
|
||||
// }
|
||||
}
|
||||
v = &funcDefFunctor{
|
||||
params: paramList,
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operand-iterator.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// -------- iterator term
|
||||
|
||||
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 := newDataCursor(ctx)
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
initCtx := dc.ctx.Clone()
|
||||
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
|
||||
return
|
||||
}
|
||||
exportObjects(dc.ctx, initCtx)
|
||||
}
|
||||
|
||||
dc.nextFunc, _ = ds[nextName]
|
||||
dc.currentFunc, _ = ds[currentName]
|
||||
v = dc
|
||||
|
||||
return
|
||||
}
|
||||
+7
-2
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-length.go
|
||||
package expr
|
||||
|
||||
//-------- builtin term
|
||||
|
||||
func newBuiltinTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPrefix,
|
||||
priority: priSign,
|
||||
evalFunc: evalBuiltin,
|
||||
}
|
||||
}
|
||||
|
||||
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
||||
var rightValue any
|
||||
|
||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
count := 0
|
||||
if isList(rightValue) {
|
||||
list, _ := rightValue.([]any)
|
||||
for i, moduleSpec := range list {
|
||||
if module, ok := moduleSpec.(string); ok {
|
||||
if ImportInContext(ctx, module) {
|
||||
count++
|
||||
} else {
|
||||
err = self.Errorf("unknown module %q", module)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if isString(rightValue) {
|
||||
module, _ := rightValue.(string)
|
||||
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
}
|
||||
if err == nil {
|
||||
v = count
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymKwBuiltin, newBuiltinTerm)
|
||||
}
|
||||
+10
-10
@@ -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 {
|
||||
v = rightValue
|
||||
}
|
||||
// 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() {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-context-value.go
|
||||
package expr
|
||||
|
||||
//-------- context term
|
||||
|
||||
func newContextTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPrefix,
|
||||
priority: priPrePost,
|
||||
evalFunc: evalContextValue,
|
||||
}
|
||||
}
|
||||
|
||||
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
||||
var childValue any
|
||||
|
||||
var sourceCtx ExprContext
|
||||
if len(self.children) == 0 {
|
||||
sourceCtx = ctx
|
||||
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
||||
if dc, ok := childValue.(*dataCursor); ok {
|
||||
sourceCtx = dc.ctx
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if sourceCtx != nil {
|
||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||
d := make(map[string]any)
|
||||
for _, key := range keys {
|
||||
d[key], _ = sourceCtx.GetVar(key)
|
||||
}
|
||||
v = d
|
||||
} else {
|
||||
err = self.errIncompatibleType(childValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymDoubleDollar, newContextTerm)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-dot.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// -------- dot term
|
||||
func newDotTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priDot,
|
||||
evalFunc: evalDot,
|
||||
}
|
||||
}
|
||||
|
||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexTerm := self.children[1]
|
||||
|
||||
if isList(leftValue) {
|
||||
var index int
|
||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, _ := leftValue.([]any)
|
||||
if index >= 0 && index < len(list) {
|
||||
v = list[index]
|
||||
} else if index >= -len(list) {
|
||||
v = list[len(list)+index]
|
||||
} else {
|
||||
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])
|
||||
} else if index >= -len(s) {
|
||||
v = string(s[len(s)+index])
|
||||
} 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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymDot, newDotTerm)
|
||||
}
|
||||
+4
-5
@@ -4,7 +4,6 @@
|
||||
// operator-insert.go
|
||||
package expr
|
||||
|
||||
|
||||
//-------- insert term
|
||||
|
||||
func newInsertTerm(tk *Token) (inst *term) {
|
||||
@@ -12,7 +11,7 @@ func newInsertTerm(tk *Token) (inst *term) {
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priSign,
|
||||
priority: priAssign,
|
||||
evalFunc: evalInsert,
|
||||
}
|
||||
}
|
||||
@@ -22,7 +21,7 @@ func newAppendTerm(tk *Token) (inst *term) {
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priSign,
|
||||
priority: priAssign,
|
||||
evalFunc: evalAppend,
|
||||
}
|
||||
}
|
||||
@@ -38,7 +37,7 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
||||
list, _ := rightValue.([]any)
|
||||
v = append([]any{leftValue}, list...)
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -54,7 +53,7 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||
list, _ := leftValue.([]any)
|
||||
v = append(list, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleType(leftValue)
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+2
-3
@@ -29,9 +29,8 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||
} else if isString(rightValue) {
|
||||
s, _ := rightValue.(string)
|
||||
v = len(s)
|
||||
// } else {
|
||||
// v = 1
|
||||
// }
|
||||
} else if it, ok := rightValue.(Iterator); ok {
|
||||
v = it.Index()
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+8
-63
@@ -4,65 +4,7 @@
|
||||
// operator-selector.go
|
||||
package expr
|
||||
|
||||
// //-------- export all term
|
||||
|
||||
// func newSelectorTerm(tk *Token) (inst *term) {
|
||||
// return &term{
|
||||
// tk: *tk,
|
||||
// children: make([]*term, 0, 3),
|
||||
// position: posMultifix,
|
||||
// priority: priSelector,
|
||||
// evalFunc: evalSelector,
|
||||
// }
|
||||
// }
|
||||
|
||||
// func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
|
||||
// caseData, _ := caseSel.(*selectorCase)
|
||||
// if caseData.filterList == nil {
|
||||
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
// } else {
|
||||
// filterList := caseData.filterList.children
|
||||
// if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
||||
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
// } else {
|
||||
// var caseValue any
|
||||
// for _, caseTerm := range filterList {
|
||||
// if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
||||
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||
// var exprValue any
|
||||
// // var caseList []*term
|
||||
|
||||
// if err = self.checkOperands(); err != nil {
|
||||
// return
|
||||
// }
|
||||
// exprTerm := self.children[0]
|
||||
// if exprValue, err = exprTerm.compute(ctx); err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// caseList := self.children[1:]
|
||||
// for i, caseTerm := range caseList {
|
||||
// caseSel := caseTerm.value()
|
||||
// if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if err == nil && v == nil {
|
||||
// err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
//-------- export all term
|
||||
//-------- selector term
|
||||
|
||||
func newSelectorTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
@@ -74,18 +16,21 @@ func newSelectorTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
|
||||
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
|
||||
caseData, _ := caseSel.(*selectorCase)
|
||||
if caseData.filterList == nil {
|
||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
match = true
|
||||
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
||||
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
match = true
|
||||
} else {
|
||||
var caseValue any
|
||||
for _, caseTerm := range filterList {
|
||||
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -96,7 +41,7 @@ func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (sel
|
||||
|
||||
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||
var exprValue any
|
||||
// var caseList []*term
|
||||
var match bool
|
||||
|
||||
if err = self.checkOperands(); err != nil {
|
||||
return
|
||||
@@ -110,11 +55,11 @@ func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||
caseList, _ := caseListTerm.value().([]*term)
|
||||
for i, caseTerm := range caseList {
|
||||
caseSel := caseTerm.value()
|
||||
if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
|
||||
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil && v == nil {
|
||||
if err == nil && !match {
|
||||
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -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("exptected identifier, got %q", subTree.root)
|
||||
}
|
||||
} else if itemExpected {
|
||||
prev := scanner.Previous()
|
||||
err = prev.Errorf("expected function parameter, got %q", prev)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
||||
break
|
||||
}
|
||||
tk = scanner.Next()
|
||||
lastSym = scanner.Previous().Sym
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil && tk.Sym != SymClosedRound {
|
||||
err = tk.Errorf("unterminate function params list")
|
||||
|
||||
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)
|
||||
|
||||
+31
-22
@@ -7,6 +7,7 @@ package expr
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -63,8 +64,8 @@ func TestParser(t *testing.T) {
|
||||
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
||||
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||
/* 44 */ {`false // very simple expression`, false, nil},
|
||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil operands, got 1`)},
|
||||
/* 46 */ {"", nil, errors.New(`empty expression`)},
|
||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||
/* 46 */ {"", nil, nil},
|
||||
/* 47 */ {"4!", int64(24), nil},
|
||||
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||
/* 49 */ {"-4!", int64(-24), nil},
|
||||
@@ -78,16 +79,16 @@ func TestParser(t *testing.T) {
|
||||
/* 57 */ {`"1.5" > "7"`, false, nil},
|
||||
/* 58 */ {`"1.5" == "7"`, false, nil},
|
||||
/* 59 */ {`"1.5" != "7"`, true, nil},
|
||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)},
|
||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)},
|
||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)},
|
||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)},
|
||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)},
|
||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)},
|
||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)},
|
||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)},
|
||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)},
|
||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)},
|
||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 70 */ {"+1.5", float64(1.5), nil},
|
||||
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
||||
@@ -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:15] 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},
|
||||
@@ -156,6 +157,12 @@ func TestParser(t *testing.T) {
|
||||
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
||||
/* 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 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
|
||||
|
||||
@@ -163,7 +170,7 @@ func TestParser(t *testing.T) {
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
||||
// /* 140 */ {`ds={}; $(ds)`, nil, nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
@@ -188,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
|
||||
}
|
||||
@@ -219,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},
|
||||
@@ -242,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
|
||||
|
||||
+24
-6
@@ -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)
|
||||
@@ -152,9 +155,10 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
case ';':
|
||||
tk = self.makeToken(SymSemiColon, ch)
|
||||
case '.':
|
||||
if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||
tk = self.parseNumber(ch)
|
||||
} else if next == '/' {
|
||||
//if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||
// tk = self.parseNumber(ch)
|
||||
//} else if next == '/' {
|
||||
if next, _ := self.peek(); next == '/' {
|
||||
tk = self.moveOn(SymDotSlash, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymDot, ch)
|
||||
@@ -235,9 +239,20 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
tk = self.makeToken(SymGreater, ch)
|
||||
}
|
||||
case '$':
|
||||
tk = self.makeToken(SymDollar, ch)
|
||||
if next, _ := self.peek(); next == '(' {
|
||||
tk = self.moveOn(SymDollarRound, ch, next)
|
||||
tk.source += ")"
|
||||
} else if next == '$' {
|
||||
tk = self.moveOn(SymDoubleDollar, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymDollar, ch)
|
||||
}
|
||||
case '(':
|
||||
tk = self.makeToken(SymOpenRound, ch)
|
||||
if next, _ := self.peek(); next == ')' {
|
||||
tk = self.moveOn(SymOpenClosedRound, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymOpenRound, ch)
|
||||
}
|
||||
case ')':
|
||||
tk = self.makeToken(SymClosedRound, ch)
|
||||
case '[':
|
||||
@@ -270,6 +285,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tk == nil {
|
||||
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ func TestScanner(t *testing.T) {
|
||||
/* 14 */ {`:`, SymColon, nil, nil},
|
||||
/* 15 */ {`;`, SymSemiColon, nil, nil},
|
||||
/* 16 */ {`.`, SymDot, nil, nil},
|
||||
/* 17 */ {`.5`, SymFloat, float64(0.5), nil},
|
||||
/* 17 */ {`0.5`, SymFloat, float64(0.5), nil},
|
||||
/* 18 */ {`\\`, SymBackSlash, nil, nil},
|
||||
/* 19 */ {"`", SymBackTick, nil, nil},
|
||||
/* 20 */ {"?", SymQuestion, nil, nil},
|
||||
@@ -74,7 +74,7 @@ func TestScanner(t *testing.T) {
|
||||
scanner := NewScanner(r, nil)
|
||||
|
||||
if tk := scanner.Next(); tk == nil {
|
||||
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
|
||||
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
|
||||
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
|
||||
if tk.Sym == SymError && input.wantSym == tk.Sym {
|
||||
if tkErr, tkOk := tk.Value.(error); tkOk {
|
||||
|
||||
@@ -35,10 +35,12 @@ func (info *funcInfo) Functor() Functor {
|
||||
}
|
||||
|
||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||
return &SimpleFuncStore{
|
||||
ctx := &SimpleFuncStore{
|
||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||
funcStore: make(map[string]*funcInfo),
|
||||
}
|
||||
ImportBuiltinsFuncs(ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
@@ -48,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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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,10 @@ const (
|
||||
SymDoubleColon // 50: '::'
|
||||
SymInsert // 51: '>>'
|
||||
SymAppend // 52: '<<'
|
||||
SymCaret // 53: '^'
|
||||
SymDollarRound // 54: '$('
|
||||
SymOpenClosedRound // 55: '()'
|
||||
SymDoubleDollar // 56: '$$
|
||||
SymChangeSign
|
||||
SymUnchangeSign
|
||||
SymIdentifier
|
||||
@@ -68,6 +72,7 @@ const (
|
||||
SymInteger
|
||||
SymFloat
|
||||
SymString
|
||||
SymIterator
|
||||
SymOr
|
||||
SymAnd
|
||||
SymNot
|
||||
@@ -75,6 +80,7 @@ const (
|
||||
SymFuncCall
|
||||
SymFuncDef
|
||||
SymList
|
||||
SymDict
|
||||
SymExpression
|
||||
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||
@@ -89,6 +95,8 @@ const (
|
||||
SymKwOr
|
||||
SymKwBut
|
||||
SymKwFunc
|
||||
SymKwBuiltin
|
||||
SymKwNil
|
||||
)
|
||||
|
||||
var keywords map[string]Symbol
|
||||
@@ -96,10 +104,12 @@ var keywords map[string]Symbol
|
||||
func init() {
|
||||
//keywords = make(map[string]Symbol)
|
||||
keywords = map[string]Symbol{
|
||||
"AND": SymKwAnd,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"NOT": SymKwNot,
|
||||
"OR": SymKwOr,
|
||||
"AND": SymKwAnd,
|
||||
"BUILTIN": SymKwBuiltin,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"NOT": SymKwNot,
|
||||
"OR": SymKwOr,
|
||||
"NIL": SymKwNil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ const (
|
||||
priSelector
|
||||
priSign
|
||||
priFact
|
||||
priIterValue
|
||||
priCoalesce
|
||||
priPrePost
|
||||
priDot
|
||||
priValue
|
||||
)
|
||||
|
||||
@@ -126,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
|
||||
}
|
||||
@@ -143,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",
|
||||
@@ -166,7 +182,7 @@ func (self *term) checkOperands() (err error) {
|
||||
switch self.position {
|
||||
case posInfix:
|
||||
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
|
||||
err = self.tk.Errorf("infix operator %q requires two not nil operands, got %d", self.source(), self.getChildrenCount())
|
||||
err = self.tk.Errorf("infix operator %q requires two non-nil operands, got %d", self.source(), self.getChildrenCount())
|
||||
}
|
||||
case posPrefix:
|
||||
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
uno
|
||||
due
|
||||
+3
-2
@@ -5,6 +5,7 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -17,8 +18,8 @@ func TestDevString(t *testing.T) {
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {"100", SymInteger, 100, `[55]"100"{100}`},
|
||||
/* 2 */ {"+", SymPlus, nil, `[6]"+"{}`},
|
||||
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)},
|
||||
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)},
|
||||
}
|
||||
|
||||
for i, input := range inputs {
|
||||
|
||||
Reference in New Issue
Block a user