Compare commits
49 Commits
d643e24a1b
..
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 | |||
| 54bc759f70 | |||
| b6887af77a | |||
| 353d495c50 | |||
| f45b2c0a88 | |||
| 624e3ac0f2 | |||
| 35fcbd2bce | |||
| 2150181303 |
@@ -5,7 +5,6 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,8 +83,9 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
|
|||||||
if tree.isComplete() {
|
if tree.isComplete() {
|
||||||
var subRoot *term
|
var subRoot *term
|
||||||
last := tree.removeLastChild()
|
last := tree.removeLastChild()
|
||||||
subRoot, err = self.insert(last, node)
|
if subRoot, err = self.insert(last, node); err == nil {
|
||||||
subRoot.setParent(tree)
|
subRoot.setParent(tree)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
node.setParent(tree)
|
node.setParent(tree)
|
||||||
}
|
}
|
||||||
@@ -129,8 +129,8 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
result, err = self.root.compute(ctx)
|
result, err = self.root.compute(ctx)
|
||||||
}
|
}
|
||||||
} else {
|
// } else {
|
||||||
err = errors.New("empty expression")
|
// err = errors.New("empty expression")
|
||||||
}
|
}
|
||||||
return
|
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)
|
setVar(varName string, value any)
|
||||||
EnumVars(func(name string) (accept bool)) (varNames []string)
|
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||||
GetFuncInfo(name string) ExprFunc
|
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||||
Call(name string, args []any) (result any, err error)
|
Call(name string, args []any) (result any, err error)
|
||||||
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::[]
|
toc::[]
|
||||||
|
|
||||||
|
#TODO: Work in progress#
|
||||||
|
|
||||||
== Expr
|
== Expr
|
||||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
_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` 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 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]`=`
|
=== Assignment operator [blue]`=`
|
||||||
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
|
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
|
||||||
|
|||||||
+17
-2
@@ -18,14 +18,28 @@ func TestExpr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputs := []inputType{
|
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
|
succeeded := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
inputs1 := []inputType{
|
inputs1 := []inputType{
|
||||||
{`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 {
|
for i, input := range inputs1 {
|
||||||
@@ -36,6 +50,7 @@ func TestExpr(t *testing.T) {
|
|||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleFuncStore()
|
||||||
// ImportMathFuncs(ctx)
|
// ImportMathFuncs(ctx)
|
||||||
// ImportImportFunc(ctx)
|
// ImportImportFunc(ctx)
|
||||||
|
ImportOsFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
+5
-1
@@ -136,7 +136,11 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportImportFunc(ctx ExprContext) {
|
func ImportImportFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
||||||
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerImport("import", ImportImportFuncs)
|
||||||
|
}
|
||||||
|
|||||||
+10
-1
@@ -23,15 +23,20 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
var v any
|
var v any
|
||||||
|
|
||||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if subIter, ok := v.(Iterator); ok {
|
||||||
|
if v, err = doAdd(ctx, name, subIter); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if array, ok := v.([]any); ok {
|
if array, ok := v.([]any); ok {
|
||||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !sumAsFloat && isFloat(v) {
|
if !sumAsFloat && isFloat(v) {
|
||||||
sumAsFloat = true
|
sumAsFloat = true
|
||||||
@@ -108,3 +113,7 @@ func ImportMathFuncs(ctx ExprContext) {
|
|||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerImport("math.arith", ImportMathFuncs)
|
||||||
|
}
|
||||||
|
|||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-os.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type osHandle interface {
|
||||||
|
getFile() *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
type osWriter struct {
|
||||||
|
fh *os.File
|
||||||
|
writer *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *osWriter) getFile() *os.File {
|
||||||
|
return h.fh
|
||||||
|
}
|
||||||
|
|
||||||
|
type osReader struct {
|
||||||
|
fh *os.File
|
||||||
|
reader *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *osReader) getFile() *os.File {
|
||||||
|
return h.fh
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var filePath string
|
||||||
|
if len(args) > 0 {
|
||||||
|
filePath, _ = args[0].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filePath) > 0 {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Create(filePath); err == nil {
|
||||||
|
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): missing the file path", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var filePath string
|
||||||
|
if len(args) > 0 {
|
||||||
|
filePath, _ = args[0].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filePath) > 0 {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err == nil {
|
||||||
|
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): missing the file path", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var filePath string
|
||||||
|
if len(args) > 0 {
|
||||||
|
filePath, _ = args[0].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filePath) > 0 {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
|
||||||
|
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): missing the file path", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
handle, _ = args[0].(osHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.getFile(); fh != nil {
|
||||||
|
if w, ok := handle.(*osWriter); ok {
|
||||||
|
err = w.writer.Flush()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = fh.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): invalid file handle", name)
|
||||||
|
}
|
||||||
|
result = err == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
handle, _ = args[0].(osHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.getFile(); fh != nil {
|
||||||
|
if w, ok := handle.(*osWriter); ok {
|
||||||
|
result, err = fmt.Fprint(w.writer, args[1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
handle, _ = args[0].(osHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.getFile(); fh != nil {
|
||||||
|
if r, ok := handle.(*osReader); ok {
|
||||||
|
var limit byte = '\n'
|
||||||
|
var v string
|
||||||
|
if len(args) > 1 {
|
||||||
|
if s, ok := args[1].(string); ok && len(s) > 0 {
|
||||||
|
limit = s[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
|
||||||
|
if len(v) > 0 && v[len(v)-1] == limit {
|
||||||
|
result = v[0 : len(v)-1]
|
||||||
|
} else {
|
||||||
|
result = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportOsFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
|
||||||
|
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
||||||
|
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
||||||
|
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
||||||
|
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
|
// iterator.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
type Iterator interface {
|
type Iterator interface {
|
||||||
Reset()
|
|
||||||
Next() (item any, err error) // must return io.EOF after the last item
|
Next() (item any, err error) // must return io.EOF after the last item
|
||||||
|
Current() (item any, err error)
|
||||||
Index() int
|
Index() int
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlatArrayIterator struct {
|
|
||||||
a []any
|
|
||||||
index int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
|
|
||||||
return &FlatArrayIterator{a: array, index: 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Reset() {
|
|
||||||
it.index = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Next() (item any, err error) {
|
|
||||||
if it.index < len(it.a) {
|
|
||||||
item = it.a[it.index]
|
|
||||||
it.index++
|
|
||||||
} else {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Index() int {
|
|
||||||
return it.index - 1
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-48
@@ -4,52 +4,10 @@
|
|||||||
// operand-const.go
|
// operand-const.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// -------- bool const term
|
// -------- const term
|
||||||
func newBoolTerm(tk *Token) *term {
|
func newConstTerm(tk *Token) *term {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classConst,
|
|
||||||
// kind: kindBool,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- integer const term
|
|
||||||
func newIntegerTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- 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,
|
parent: nil,
|
||||||
children: nil,
|
children: nil,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
@@ -66,8 +24,9 @@ func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymString, newStringTerm)
|
registerTermConstructor(SymString, newConstTerm)
|
||||||
registerTermConstructor(SymInteger, newIntegerTerm)
|
registerTermConstructor(SymInteger, newConstTerm)
|
||||||
registerTermConstructor(SymFloat, newFloatTerm)
|
registerTermConstructor(SymFloat, newConstTerm)
|
||||||
registerTermConstructor(SymBool, newBoolTerm)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
|
||||||
// func init() {
|
|
||||||
// registerTermConstructor(SymExpression, newExprTerm)
|
|
||||||
// }
|
|
||||||
|
|||||||
+2
-35
@@ -12,8 +12,6 @@ import (
|
|||||||
func newFuncCallTerm(tk *Token, args []*term) *term {
|
func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classVar,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: args,
|
children: args,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
@@ -24,7 +22,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
|||||||
|
|
||||||
// -------- eval func call
|
// -------- eval func call
|
||||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||||
ctx := parentCtx.Clone()
|
ctx := cloneContext(parentCtx)
|
||||||
name, _ := self.tk.Value.(string)
|
name, _ := self.tk.Value.(string)
|
||||||
params := make([]any, len(self.children))
|
params := make([]any, len(self.children))
|
||||||
for i, tree := range 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 err == nil {
|
||||||
if v, err = ctx.Call(name, params); err == nil {
|
if v, err = ctx.Call(name, params); err == nil {
|
||||||
exportAll := isEnabled(ctx, control_export_all)
|
exportObjects(parentCtx, ctx)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
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
|
// -------- function definition term
|
||||||
func newFuncDefTerm(tk *Token, args []*term) *term {
|
func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -109,12 +82,6 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
paramList := make([]string, 0, len(self.children))
|
paramList := make([]string, 0, len(self.children))
|
||||||
for _, param := range self.children {
|
for _, param := range self.children {
|
||||||
paramList = append(paramList, param.source())
|
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{
|
v = &funcDefFunctor{
|
||||||
params: paramList,
|
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
|
// -------- eval func
|
||||||
func evalVar(ctx ExprContext, self *term) (v any, err error) {
|
func evalVar(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var exists bool
|
var exists bool
|
||||||
if v, exists = ctx.GetVar(self.tk.source); !exists {
|
name := self.source()
|
||||||
err = fmt.Errorf("undefined variable %q", self.tk.source)
|
if v, exists = ctx.GetVar(name); !exists {
|
||||||
|
if info, exists := ctx.GetFuncInfo(name); exists {
|
||||||
|
v = info.Functor()
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("undefined variable or function %q", name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if functor, ok := v.(Functor); ok {
|
if functor, ok := v.(Functor); ok {
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.tk.source, v)
|
ctx.setVar(leftTerm.source(), v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -34,11 +34,11 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if _, ok := rightValue.(Functor); ok {
|
// if _, ok := rightValue.(Functor); ok {
|
||||||
err = errCoalesceNoFunc(self.children[1])
|
// err = errCoalesceNoFunc(self.children[1])
|
||||||
} else {
|
// } else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -71,8 +71,8 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if _, ok := rightValue.(Functor); ok {
|
if functor, ok := rightValue.(Functor); ok {
|
||||||
err = errCoalesceNoFunc(self.children[1])
|
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
ctx.setVar(leftTerm.source(), rightValue)
|
ctx.setVar(leftTerm.source(), rightValue)
|
||||||
@@ -82,9 +82,9 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
func errCoalesceNoFunc(t *term) error {
|
// func errCoalesceNoFunc(t *term) error {
|
||||||
return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-insert.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- insert term
|
||||||
|
|
||||||
|
func newInsertTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priAssign,
|
||||||
|
evalFunc: evalInsert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppendTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priAssign,
|
||||||
|
evalFunc: evalAppend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList(rightValue) {
|
||||||
|
list, _ := rightValue.([]any)
|
||||||
|
v = append([]any{leftValue}, list...)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList(leftValue) {
|
||||||
|
list, _ := leftValue.([]any)
|
||||||
|
v = append(list, rightValue)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymInsert, newInsertTerm)
|
||||||
|
registerTermConstructor(SymAppend, newAppendTerm)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-length.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- length term
|
||||||
|
|
||||||
|
func newLengthTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priSign,
|
||||||
|
evalFunc: evalLength,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList(rightValue) {
|
||||||
|
list, _ := rightValue.([]any)
|
||||||
|
v = len(list)
|
||||||
|
} else if isString(rightValue) {
|
||||||
|
s, _ := rightValue.(string)
|
||||||
|
v = len(s)
|
||||||
|
} else if it, ok := rightValue.(Iterator); ok {
|
||||||
|
v = it.Index()
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymHash, newLengthTerm)
|
||||||
|
}
|
||||||
@@ -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
|
// operator-selector.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// //-------- export all term
|
//-------- selector 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
|
|
||||||
|
|
||||||
func newSelectorTerm(tk *Token) (inst *term) {
|
func newSelectorTerm(tk *Token) (inst *term) {
|
||||||
return &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)
|
caseData, _ := caseSel.(*selectorCase)
|
||||||
if caseData.filterList == nil {
|
if caseData.filterList == nil {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||||
|
match = true
|
||||||
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
||||||
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||||
|
match = true
|
||||||
} else {
|
} else {
|
||||||
var caseValue any
|
var caseValue any
|
||||||
for _, caseTerm := range filterList {
|
for _, caseTerm := range filterList {
|
||||||
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||||
|
match = true
|
||||||
break
|
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) {
|
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var exprValue any
|
var exprValue any
|
||||||
// var caseList []*term
|
var match bool
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
if err = self.checkOperands(); err != nil {
|
||||||
return
|
return
|
||||||
@@ -110,11 +55,11 @@ func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
caseList, _ := caseListTerm.value().([]*term)
|
caseList, _ := caseListTerm.value().([]*term)
|
||||||
for i, caseTerm := range caseList {
|
for i, caseTerm := range caseList {
|
||||||
caseSel := caseTerm.value()
|
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
|
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)
|
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newMinusTerm(tk *Token) (inst *term) {
|
func newMinusTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priSum,
|
priority: priSum,
|
||||||
|
|||||||
@@ -58,23 +58,72 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||||
|
// // Example: "add = func(x,y) {x+y}
|
||||||
|
// var body *ast
|
||||||
|
// args := make([]*term, 0)
|
||||||
|
// tk := scanner.Next()
|
||||||
|
// for tk.Sym != SymClosedRound && tk.Sym != SymEos {
|
||||||
|
// if tk.Sym == SymIdentifier {
|
||||||
|
// t := newTerm(tk, nil)
|
||||||
|
// args = append(args, t)
|
||||||
|
// } else {
|
||||||
|
// err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// tk = scanner.Next()
|
||||||
|
// }
|
||||||
|
// if err == nil && tk.Sym != SymClosedRound {
|
||||||
|
// err = tk.Errorf("unterminate function params list")
|
||||||
|
// }
|
||||||
|
// if err == nil {
|
||||||
|
// tk = scanner.Next()
|
||||||
|
// if tk.Sym == SymOpenBrace {
|
||||||
|
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if err == nil {
|
||||||
|
// // TODO Check arguments
|
||||||
|
// if scanner.Previous().Sym != SymClosedBrace {
|
||||||
|
// err = scanner.Previous().Errorf("not properly terminated function body")
|
||||||
|
// } else {
|
||||||
|
// tk = scanner.makeValueToken(SymExpression, "", body)
|
||||||
|
// tree = newFuncDefTerm(tk, args)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||||
// Example: "add = func(x,y) {x+y}
|
// Example: "add = func(x,y) {x+y}
|
||||||
var body *ast
|
var body *ast
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
tk := scanner.Next()
|
lastSym := SymUnknown
|
||||||
for tk.Sym != SymClosedRound && tk.Sym != SymEos {
|
itemExpected := false
|
||||||
if tk.Sym == SymIdentifier {
|
tk := scanner.Previous()
|
||||||
t := newTerm(tk, nil)
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
args = append(args, t)
|
var subTree *ast
|
||||||
|
if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
|
||||||
|
if subTree.root != nil {
|
||||||
|
if subTree.root.symbol() == SymIdentifier {
|
||||||
|
args = append(args, subTree.root)
|
||||||
} else {
|
} else {
|
||||||
err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
err = tk.Errorf("exptected identifier, got %q", subTree.root)
|
||||||
|
}
|
||||||
|
} else if itemExpected {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.Errorf("expected function parameter, got %q", prev)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
tk = scanner.Next()
|
} else {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if err == nil && tk.Sym != SymClosedRound {
|
lastSym = scanner.Previous().Sym
|
||||||
err = tk.Errorf("unterminate function params list")
|
itemExpected = lastSym == SymComma
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && lastSym != SymClosedRound {
|
||||||
|
err = tk.Errorf("unterminated function parameters list")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
@@ -97,16 +146,22 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
|
itemExpected := false
|
||||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
||||||
if subTree.root != nil {
|
if subTree.root != nil {
|
||||||
args = append(args, subTree.root)
|
args = append(args, subTree.root)
|
||||||
|
} else if itemExpected {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.Errorf("expected list item, got %q", prev)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lastSym = scanner.Previous().Sym
|
lastSym = scanner.Previous().Sym
|
||||||
|
itemExpected = lastSym == SymComma
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
// TODO Check arguments
|
||||||
@@ -119,6 +174,115 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
|
var ds *term
|
||||||
|
tk := scanner.Previous()
|
||||||
|
args := make([]*term, 0)
|
||||||
|
lastSym := SymUnknown
|
||||||
|
dsExpected := true
|
||||||
|
itemExpected := false
|
||||||
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
|
var subTree *ast
|
||||||
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
||||||
|
if subTree.root != nil {
|
||||||
|
if dsExpected {
|
||||||
|
if sym := subTree.root.symbol(); sym == SymDict || sym == SymIdentifier {
|
||||||
|
ds = subTree.root
|
||||||
|
} else {
|
||||||
|
err = subTree.root.Errorf("data-source dictionary expected, got %q", subTree.root.source())
|
||||||
|
}
|
||||||
|
dsExpected = false
|
||||||
|
} else {
|
||||||
|
args = append(args, subTree.root)
|
||||||
|
}
|
||||||
|
} else if itemExpected {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.Errorf("expected iterator argument, got %q", prev)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastSym = scanner.Previous().Sym
|
||||||
|
itemExpected = lastSym == SymComma
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// TODO Check arguments
|
||||||
|
if lastSym != SymClosedRound {
|
||||||
|
err = scanner.Previous().Errorf("unterminate iterator param list")
|
||||||
|
} else if ds != nil {
|
||||||
|
subtree = newIteratorTerm(tk, ds, args)
|
||||||
|
} else {
|
||||||
|
tk.Errorf("missing data-source param")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
|
||||||
|
tk := scanner.Next()
|
||||||
|
if tk.Sym == SymError {
|
||||||
|
err = tk.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tk.Sym == SymInteger || tk.Sym == SymString {
|
||||||
|
tkSep := scanner.Next()
|
||||||
|
if tkSep.Sym != SymColon {
|
||||||
|
err = tkSep.Errorf("expected \":\", got %q", tkSep)
|
||||||
|
} else {
|
||||||
|
key = tk.Value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
|
args := make(map[any]*term, 0)
|
||||||
|
lastSym := SymUnknown
|
||||||
|
itemExpected := false
|
||||||
|
for lastSym != SymClosedBrace && lastSym != SymEos {
|
||||||
|
var subTree *ast
|
||||||
|
var key any
|
||||||
|
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
|
||||||
|
break
|
||||||
|
} else if key == nil {
|
||||||
|
tk := scanner.Previous()
|
||||||
|
lastSym = tk.Sym
|
||||||
|
if itemExpected {
|
||||||
|
err = tk.Errorf("expected dictionary key, got %q", tk)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
|
||||||
|
if subTree.root != nil {
|
||||||
|
args[key] = subTree.root
|
||||||
|
} else if key != nil {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.Errorf("expected dictionary value, got %q", prev)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastSym = scanner.Previous().Sym
|
||||||
|
itemExpected = lastSym == SymComma
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// TODO Check arguments
|
||||||
|
if lastSym != SymClosedBrace {
|
||||||
|
err = scanner.Previous().Errorf("unterminated dictionary")
|
||||||
|
} else {
|
||||||
|
subtree = newDictTerm(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
|
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
|
||||||
var filterList *term
|
var filterList *term
|
||||||
var caseExpr *ast
|
var caseExpr *ast
|
||||||
@@ -191,10 +355,11 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
|
|||||||
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
var selectorTerm *term = nil
|
var selectorTerm *term = nil
|
||||||
var currentTerm *term = nil
|
var currentTerm *term = nil
|
||||||
|
var tk *Token
|
||||||
tree = NewAst()
|
tree = NewAst()
|
||||||
firstToken := true
|
firstToken := true
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
for tk := scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
||||||
if tk.Sym == SymComment {
|
if tk.Sym == SymComment {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -240,6 +405,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
err = tree.addTerm(listTerm)
|
err = tree.addTerm(listTerm)
|
||||||
currentTerm = listTerm
|
currentTerm = listTerm
|
||||||
}
|
}
|
||||||
|
case SymOpenBrace:
|
||||||
|
var mapTerm *term
|
||||||
|
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||||
|
err = tree.addTerm(mapTerm)
|
||||||
|
currentTerm = mapTerm
|
||||||
|
}
|
||||||
case SymEqual:
|
case SymEqual:
|
||||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
@@ -250,6 +421,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
err = tree.addTerm(funcDefTerm)
|
err = tree.addTerm(funcDefTerm)
|
||||||
currentTerm = funcDefTerm
|
currentTerm = funcDefTerm
|
||||||
}
|
}
|
||||||
|
case SymDollarRound:
|
||||||
|
var iterDefTerm *term
|
||||||
|
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
|
||||||
|
err = tree.addTerm(iterDefTerm)
|
||||||
|
currentTerm = iterDefTerm
|
||||||
|
}
|
||||||
case SymIdentifier:
|
case SymIdentifier:
|
||||||
if tk.source[0] == '@' && !allowVarRef {
|
if tk.source[0] == '@' && !allowVarRef {
|
||||||
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
||||||
@@ -281,6 +458,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
}
|
}
|
||||||
lastSym = tk.Sym
|
lastSym = tk.Sym
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = tk.Error()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-23
@@ -7,6 +7,7 @@ package expr
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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 "+"`)},
|
/* 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]`)},
|
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||||
/* 44 */ {`false // very simple expression`, false, nil},
|
/* 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`)},
|
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||||
/* 46 */ {"", nil, errors.New(`empty expression`)},
|
/* 46 */ {"", nil, nil},
|
||||||
/* 47 */ {"4!", int64(24), nil},
|
/* 47 */ {"4!", int64(24), nil},
|
||||||
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||||
/* 49 */ {"-4!", int64(-24), nil},
|
/* 49 */ {"-4!", int64(-24), nil},
|
||||||
@@ -78,16 +79,16 @@ func TestParser(t *testing.T) {
|
|||||||
/* 57 */ {`"1.5" > "7"`, false, nil},
|
/* 57 */ {`"1.5" > "7"`, false, nil},
|
||||||
/* 58 */ {`"1.5" == "7"`, false, nil},
|
/* 58 */ {`"1.5" == "7"`, false, nil},
|
||||||
/* 59 */ {`"1.5" != "7"`, true, nil},
|
/* 59 */ {`"1.5" != "7"`, true, nil},
|
||||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] 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 not 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 not 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 not 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 not 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 not 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 not 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 not 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 not 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 not 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},
|
/* 70 */ {"+1.5", float64(1.5), nil},
|
||||||
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||||
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
/* 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},
|
/* 96 */ {`x=2 but x*10`, int64(20), nil},
|
||||||
/* 97 */ {`false and true`, false, nil},
|
/* 97 */ {`false and true`, false, nil},
|
||||||
/* 98 */ {`false and (x==2)`, false, nil},
|
/* 98 */ {`false and (x==2)`, false, nil},
|
||||||
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable "x"`)},
|
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
/* 100 */ {`false or true`, true, nil},
|
/* 100 */ {`false or true`, true, nil},
|
||||||
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
|
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
/* 102 */ {`a=5; a`, int64(5), nil},
|
/* 102 */ {`a=5; a`, int64(5), nil},
|
||||||
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
|
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||||
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||||
@@ -134,13 +135,13 @@ func TestParser(t *testing.T) {
|
|||||||
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
/* 114 */ {`x ?? "default"`, "default", nil},
|
/* 114 */ {`x ?? "default"`, "default", nil},
|
||||||
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||||
/* 116 */ {`x ?? func(){}"`, nil, errors.New(`[1: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},
|
/* 117 */ {`x ?= "default"; x`, "default", nil},
|
||||||
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
||||||
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
||||||
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
||||||
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
||||||
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)},
|
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||||
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||||
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||||
@@ -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`)},
|
/* 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},
|
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||||
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
||||||
|
/* 138 */ {`nil`, nil, nil},
|
||||||
|
/* 139 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||||
|
/* 140 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||||
|
/* 141 */ {`{"key":}`, nil, errors.New(`[1:9] expected dictionary value, got "}"`)},
|
||||||
|
/* 142 */ {`{}`, map[any]any{}, nil},
|
||||||
|
/* 144 */ //{`3^2`, int64(9), nil},
|
||||||
}
|
}
|
||||||
check_env_expr_path := 113
|
check_env_expr_path := 113
|
||||||
|
|
||||||
@@ -163,7 +170,7 @@ func TestParser(t *testing.T) {
|
|||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
// inputs1 := []inputType{
|
||||||
// {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
// /* 140 */ {`ds={}; $(ds)`, nil, nil},
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
@@ -175,7 +182,7 @@ func TestParser(t *testing.T) {
|
|||||||
ctx.SetVar("var1", int64(123))
|
ctx.SetVar("var1", int64(123))
|
||||||
ctx.SetVar("var2", "abc")
|
ctx.SetVar("var2", "abc")
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
ImportImportFunc(ctx)
|
ImportImportFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||||
@@ -188,7 +195,9 @@ func TestParser(t *testing.T) {
|
|||||||
gotResult, gotErr = expr.Eval(ctx)
|
gotResult, gotErr = expr.Eval(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gotResult != input.wantResult {
|
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||||
|
|
||||||
|
if !eq /*gotResult != input.wantResult*/ {
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
good = false
|
good = false
|
||||||
}
|
}
|
||||||
@@ -219,10 +228,6 @@ func TestListParser(t *testing.T) {
|
|||||||
wantErr error
|
wantErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// {`add(1,2,3)`, int64(6), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`[]`, []any{}, nil},
|
/* 1 */ {`[]`, []any{}, nil},
|
||||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||||
@@ -242,6 +247,10 @@ func TestListParser(t *testing.T) {
|
|||||||
succeeded := 0
|
succeeded := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
|
// inputs1 := []inputType{
|
||||||
|
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||||
|
// }
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
var expr *ast
|
var expr *ast
|
||||||
var gotResult any
|
var gotResult any
|
||||||
|
|||||||
+94
-18
@@ -86,13 +86,14 @@ func (self *scanner) Next() (tk *Token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchNextToken() (tk *Token) {
|
func (self *scanner) fetchNextToken() (tk *Token) {
|
||||||
|
var ch byte
|
||||||
if err := self.skipBlanks(); err != nil {
|
if err := self.skipBlanks(); err != nil {
|
||||||
return self.makeErrorToken(err)
|
return self.makeErrorToken(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
escape := false
|
escape := false
|
||||||
for {
|
for {
|
||||||
ch, _ := self.readChar()
|
ch, _ = self.readChar()
|
||||||
switch ch {
|
switch ch {
|
||||||
case '+':
|
case '+':
|
||||||
if next, _ := self.peek(); next == '+' {
|
if next, _ := self.peek(); next == '+' {
|
||||||
@@ -143,6 +144,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
}
|
}
|
||||||
case ',':
|
case ',':
|
||||||
tk = self.makeToken(SymComma, ch)
|
tk = self.makeToken(SymComma, ch)
|
||||||
|
case '^':
|
||||||
|
tk = self.makeToken(SymCaret, ch)
|
||||||
case ':':
|
case ':':
|
||||||
if next, _ := self.peek(); next == ':' {
|
if next, _ := self.peek(); next == ':' {
|
||||||
tk = self.moveOn(SymDoubleColon, ch, next)
|
tk = self.moveOn(SymDoubleColon, ch, next)
|
||||||
@@ -152,9 +155,10 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
case ';':
|
case ';':
|
||||||
tk = self.makeToken(SymSemiColon, ch)
|
tk = self.makeToken(SymSemiColon, ch)
|
||||||
case '.':
|
case '.':
|
||||||
if next, _ := self.peek(); next >= '0' && next <= '9' {
|
//if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||||
tk = self.parseNumber(ch)
|
// tk = self.parseNumber(ch)
|
||||||
} else if next == '/' {
|
//} else if next == '/' {
|
||||||
|
if next, _ := self.peek(); next == '/' {
|
||||||
tk = self.moveOn(SymDotSlash, ch, next)
|
tk = self.moveOn(SymDotSlash, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymDot, ch)
|
tk = self.makeToken(SymDot, ch)
|
||||||
@@ -219,6 +223,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
case '<':
|
case '<':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := self.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymLessOrEqual, ch, next)
|
tk = self.moveOn(SymLessOrEqual, ch, next)
|
||||||
|
} else if next == '<' {
|
||||||
|
tk = self.moveOn(SymAppend, ch, next)
|
||||||
} else if next == '>' {
|
} else if next == '>' {
|
||||||
tk = self.moveOn(SymLessGreater, ch, next)
|
tk = self.moveOn(SymLessGreater, ch, next)
|
||||||
} else {
|
} else {
|
||||||
@@ -227,13 +233,26 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
case '>':
|
case '>':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := self.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymGreaterOrEqual, ch, next)
|
tk = self.moveOn(SymGreaterOrEqual, ch, next)
|
||||||
|
} else if next == '>' {
|
||||||
|
tk = self.moveOn(SymInsert, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymGreater, ch)
|
tk = self.makeToken(SymGreater, ch)
|
||||||
}
|
}
|
||||||
case '$':
|
case '$':
|
||||||
|
if next, _ := self.peek(); next == '(' {
|
||||||
|
tk = self.moveOn(SymDollarRound, ch, next)
|
||||||
|
tk.source += ")"
|
||||||
|
} else if next == '$' {
|
||||||
|
tk = self.moveOn(SymDoubleDollar, ch, next)
|
||||||
|
} else {
|
||||||
tk = self.makeToken(SymDollar, ch)
|
tk = self.makeToken(SymDollar, ch)
|
||||||
|
}
|
||||||
case '(':
|
case '(':
|
||||||
|
if next, _ := self.peek(); next == ')' {
|
||||||
|
tk = self.moveOn(SymOpenClosedRound, ch, next)
|
||||||
|
} else {
|
||||||
tk = self.makeToken(SymOpenRound, ch)
|
tk = self.makeToken(SymOpenRound, ch)
|
||||||
|
}
|
||||||
case ')':
|
case ')':
|
||||||
tk = self.makeToken(SymClosedRound, ch)
|
tk = self.makeToken(SymClosedRound, ch)
|
||||||
case '[':
|
case '[':
|
||||||
@@ -266,6 +285,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if tk == nil {
|
||||||
|
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,17 +298,78 @@ func (self *scanner) sync(err error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isBinaryDigit(ch byte) bool {
|
||||||
|
return ch == '0' || ch == '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOctalDigit(ch byte) bool {
|
||||||
|
return ch >= '0' && ch <= '7'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDecimalDigit(ch byte) bool {
|
||||||
|
return ch >= '0' && ch <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexDigit(ch byte) bool {
|
||||||
|
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
|
||||||
|
var ch byte
|
||||||
|
var digitType string
|
||||||
|
firstCh = currentFirstCh
|
||||||
|
digitFunc = isDecimalDigit
|
||||||
|
numBase = 10
|
||||||
|
|
||||||
|
if ch, err = self.peek(); err == nil {
|
||||||
|
if ch == 'b' || ch == 'B' {
|
||||||
|
numBase = 2
|
||||||
|
digitType = "binary"
|
||||||
|
self.readChar()
|
||||||
|
digitFunc = isBinaryDigit
|
||||||
|
firstCh, err = self.readChar()
|
||||||
|
} else if ch == 'o' || ch == 'O' {
|
||||||
|
numBase = 8
|
||||||
|
digitType = "octal"
|
||||||
|
self.readChar()
|
||||||
|
digitFunc = isOctalDigit
|
||||||
|
firstCh, err = self.readChar()
|
||||||
|
} else if ch == 'x' || ch == 'X' {
|
||||||
|
numBase = 16
|
||||||
|
digitType = "hex"
|
||||||
|
self.readChar()
|
||||||
|
digitFunc = isHexDigit
|
||||||
|
firstCh, err = self.readChar()
|
||||||
|
}
|
||||||
|
if err == nil && !digitFunc(firstCh) {
|
||||||
|
if len(digitType) == 0 {
|
||||||
|
digitType = "decimal"
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("expected %s digit, got '%c'", digitType, firstCh)
|
||||||
|
}
|
||||||
|
} else if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch byte
|
var ch byte
|
||||||
var sym Symbol = SymInteger
|
var sym Symbol = SymInteger
|
||||||
var value any
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
var isDigit func(byte) bool = isDecimalDigit
|
||||||
|
var numBase = 10
|
||||||
|
|
||||||
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
if firstCh == '0' {
|
||||||
|
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
|
||||||
|
}
|
||||||
|
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
if ch == '.' {
|
|
||||||
|
if numBase == 10 {
|
||||||
|
if err == nil && ch == '.' {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
ch, err = self.readChar()
|
ch, err = self.readChar()
|
||||||
@@ -296,7 +379,7 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ch == 'e' || ch == 'E' {
|
if err == nil && (ch == 'e' || ch == 'E') {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
if ch, err = self.readChar(); err == nil {
|
if ch, err = self.readChar(); err == nil {
|
||||||
@@ -308,30 +391,23 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
//err = self.sync(err)
|
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("expected integer exponent")
|
err = errors.New("expected integer exponent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// } else {
|
}
|
||||||
// err = self.sync(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
tk = self.makeErrorToken(err)
|
tk = self.makeErrorToken(err)
|
||||||
} else {
|
} else {
|
||||||
|
var value any
|
||||||
err = self.sync(err)
|
err = self.sync(err)
|
||||||
txt := sb.String()
|
txt := sb.String()
|
||||||
if sym == SymFloat {
|
if sym == SymFloat {
|
||||||
value, err = strconv.ParseFloat(txt, 64)
|
value, err = strconv.ParseFloat(txt, 64)
|
||||||
} else if strings.HasPrefix(txt, "0x") {
|
|
||||||
value, err = strconv.ParseInt(txt, 16, 64)
|
|
||||||
} else if strings.HasPrefix(txt, "0o") {
|
|
||||||
value, err = strconv.ParseInt(txt, 8, 64)
|
|
||||||
} else if strings.HasPrefix(txt, "0b") {
|
|
||||||
value, err = strconv.ParseInt(txt, 2, 64)
|
|
||||||
} else {
|
} else {
|
||||||
value, err = strconv.ParseInt(txt, 10, 64)
|
value, err = strconv.ParseInt(txt, numBase, 64)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tk = self.makeValueToken(sym, txt, value)
|
tk = self.makeValueToken(sym, txt, value)
|
||||||
|
|||||||
+2
-2
@@ -36,7 +36,7 @@ func TestScanner(t *testing.T) {
|
|||||||
/* 14 */ {`:`, SymColon, nil, nil},
|
/* 14 */ {`:`, SymColon, nil, nil},
|
||||||
/* 15 */ {`;`, SymSemiColon, nil, nil},
|
/* 15 */ {`;`, SymSemiColon, nil, nil},
|
||||||
/* 16 */ {`.`, SymDot, 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},
|
/* 18 */ {`\\`, SymBackSlash, nil, nil},
|
||||||
/* 19 */ {"`", SymBackTick, nil, nil},
|
/* 19 */ {"`", SymBackTick, nil, nil},
|
||||||
/* 20 */ {"?", SymQuestion, nil, nil},
|
/* 20 */ {"?", SymQuestion, nil, nil},
|
||||||
@@ -74,7 +74,7 @@ func TestScanner(t *testing.T) {
|
|||||||
scanner := NewScanner(r, nil)
|
scanner := NewScanner(r, nil)
|
||||||
|
|
||||||
if tk := scanner.Next(); tk == 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 {
|
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
|
||||||
if tk.Sym == SymError && input.wantSym == tk.Sym {
|
if tk.Sym == SymError && input.wantSym == tk.Sym {
|
||||||
if tkErr, tkOk := tk.Value.(error); tkOk {
|
if tkErr, tkOk := tk.Value.(error); tkOk {
|
||||||
|
|||||||
@@ -35,10 +35,12 @@ func (info *funcInfo) Functor() Functor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||||
return &SimpleFuncStore{
|
ctx := &SimpleFuncStore{
|
||||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||||
funcStore: make(map[string]*funcInfo),
|
funcStore: make(map[string]*funcInfo),
|
||||||
}
|
}
|
||||||
|
ImportBuiltinsFuncs(ctx)
|
||||||
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||||
@@ -48,8 +50,8 @@ func (ctx *SimpleFuncStore) Clone() ExprContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc) {
|
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||||
info, _ = ctx.funcStore[name]
|
info, exists = ctx.funcStore[name]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (v
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
|
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ const (
|
|||||||
SymQuestionEqual // 48: '?='
|
SymQuestionEqual // 48: '?='
|
||||||
SymDoubleAt // 49: '@@'
|
SymDoubleAt // 49: '@@'
|
||||||
SymDoubleColon // 50: '::'
|
SymDoubleColon // 50: '::'
|
||||||
|
SymInsert // 51: '>>'
|
||||||
|
SymAppend // 52: '<<'
|
||||||
|
SymCaret // 53: '^'
|
||||||
|
SymDollarRound // 54: '$('
|
||||||
|
SymOpenClosedRound // 55: '()'
|
||||||
|
SymDoubleDollar // 56: '$$
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
@@ -66,6 +72,7 @@ const (
|
|||||||
SymInteger
|
SymInteger
|
||||||
SymFloat
|
SymFloat
|
||||||
SymString
|
SymString
|
||||||
|
SymIterator
|
||||||
SymOr
|
SymOr
|
||||||
SymAnd
|
SymAnd
|
||||||
SymNot
|
SymNot
|
||||||
@@ -73,6 +80,7 @@ const (
|
|||||||
SymFuncCall
|
SymFuncCall
|
||||||
SymFuncDef
|
SymFuncDef
|
||||||
SymList
|
SymList
|
||||||
|
SymDict
|
||||||
SymExpression
|
SymExpression
|
||||||
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||||
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||||
@@ -87,6 +95,8 @@ const (
|
|||||||
SymKwOr
|
SymKwOr
|
||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
|
SymKwBuiltin
|
||||||
|
SymKwNil
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords map[string]Symbol
|
var keywords map[string]Symbol
|
||||||
@@ -95,9 +105,11 @@ func init() {
|
|||||||
//keywords = make(map[string]Symbol)
|
//keywords = make(map[string]Symbol)
|
||||||
keywords = map[string]Symbol{
|
keywords = map[string]Symbol{
|
||||||
"AND": SymKwAnd,
|
"AND": SymKwAnd,
|
||||||
|
"BUILTIN": SymKwBuiltin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
"OR": SymKwOr,
|
"OR": SymKwOr,
|
||||||
|
"NIL": SymKwNil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ const (
|
|||||||
priSelector
|
priSelector
|
||||||
priSign
|
priSign
|
||||||
priFact
|
priFact
|
||||||
|
priIterValue
|
||||||
priCoalesce
|
priCoalesce
|
||||||
|
priPrePost
|
||||||
|
priDot
|
||||||
priValue
|
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 {
|
func (self *term) source() string {
|
||||||
return self.tk.source
|
return self.tk.source
|
||||||
}
|
}
|
||||||
@@ -143,6 +150,15 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
|
||||||
|
if index64, ok := computedValue.(int64); ok {
|
||||||
|
i = int(index64)
|
||||||
|
} else {
|
||||||
|
err = self.Errorf("%s, got %T", valueDescription, computedValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||||
return self.tk.Errorf(
|
return self.tk.Errorf(
|
||||||
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
||||||
@@ -166,7 +182,7 @@ func (self *term) checkOperands() (err error) {
|
|||||||
switch self.position {
|
switch self.position {
|
||||||
case posInfix:
|
case posInfix:
|
||||||
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
|
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:
|
case posPrefix:
|
||||||
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
|
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
uno
|
||||||
|
due
|
||||||
@@ -67,3 +67,17 @@ func (self *Token) Errorf(template string, args ...any) (err error) {
|
|||||||
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
|
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Token) Error() (err error) {
|
||||||
|
if self.Sym == SymError {
|
||||||
|
if msg, ok := self.Value.(error); ok {
|
||||||
|
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Token) Errors(msg string) (err error) {
|
||||||
|
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
+3
-2
@@ -5,6 +5,7 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ func TestDevString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {"100", SymInteger, 100, `[55]"100"{100}`},
|
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)},
|
||||||
/* 2 */ {"+", SymPlus, nil, `[6]"+"{}`},
|
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ func isList(v any) (ok bool) {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDict(v any) (ok bool) {
|
||||||
|
_, ok = v.(map[any]any)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func isNumber(v any) (ok bool) {
|
func isNumber(v any) (ok bool) {
|
||||||
return isFloat(v) || isInteger(v)
|
return isFloat(v) || isInteger(v)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user