Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ee0bb5701 | |||
| b2b0bb04c5 | |||
| f3abf5e77c | |||
| 34b7799177 | |||
| 8c3c54913a | |||
| 5910345c08 | |||
| 8b4dad1381 | |||
| 6ef468408c | |||
| 3a30d890c6 | |||
| 71ab417a56 | |||
| 7cfb89c25c | |||
| 569dbfda9d | |||
| f342dfe9f3 | |||
| 5378952394 | |||
| a9d6a82011 | |||
| 43b74131fb | |||
| cb0eac54b2 | |||
| b219b55878 | |||
| 56c86f917e | |||
| 539a4b44e9 | |||
| 74df927179 | |||
| 510966c497 | |||
| c977e82d9e | |||
| 903f1ae1ce | |||
| 9c66056c18 | |||
| f55a48aa26 | |||
| 434ddee733 | |||
| fcced6149f | |||
| 1f0f9cae22 | |||
| 1d8569d3a9 | |||
| 0fdd51049d | |||
| f9ed5776cd | |||
| a2c0a24494 | |||
| 2c5f02cc69 | |||
| d9fbe6f36d | |||
| e6174aca82 | |||
| a736bba2c7 | |||
| 7724cabdcc | |||
| 16557d70de | |||
| 04e71a1b3f | |||
| e463bd61d8 | |||
| 419af7bfea | |||
| 6c604812ee | |||
| 5cf0bfbad4 | |||
| a838361ea8 | |||
| 0dbb0ba515 | |||
| 7a0ba26aa3 | |||
| 0bca3333aa | |||
| 02b7a6df6c | |||
| 8d9963207e | |||
| f9486fa1bd | |||
| 360ebce015 | |||
| dc9eca83e8 | |||
| 2c55167dd0 | |||
| c124e880c4 | |||
| 4db015e4b1 | |||
| 5809de419f | |||
| 7c748f0e31 | |||
| cd6b7982ee | |||
| e00886b1ed | |||
| 49904f9097 | |||
| 2d0d03b975 | |||
| 8cb048edb0 | |||
| cb3d8827fa | |||
| 4c83764332 | |||
| c0c2ab8b4e | |||
| 288e93d708 | |||
| 92e862da19 | |||
| dc6975e56c | |||
| 6a2d3c53fd | |||
| 5643a57bcc | |||
| 52fb398cd8 | |||
| cdbe3dfc22 | |||
| 7aabd068ed | |||
| 924f5da725 | |||
| d657cbb51e | |||
| be874503ec | |||
| 056d42d328 | |||
| aa66d07caa | |||
| fc0e1ffaee | |||
| bf8f1a175f | |||
| 6dd8283308 | |||
| 06ab303b9e | |||
| 2ccbdb2254 | |||
| c5fca70cfc | |||
| 895778f236 | |||
| 81c85afbea | |||
| 354cb79580 | |||
| 327bffa01f | |||
| c99be491df | |||
| 60effe8f1b | |||
| 824b9382be | |||
| 9ce6b7255b | |||
| 9dbf472630 |
@@ -63,7 +63,7 @@ func (self *ast) addToken2(tk *Token) (t *term, err error) {
|
|||||||
if t = newTerm(tk, nil); t != nil {
|
if t = newTerm(tk, nil); t != nil {
|
||||||
err = self.addTerm(t)
|
err = self.addTerm(t)
|
||||||
} else {
|
} else {
|
||||||
err = tk.Errorf("No term constructor for token %q", tk.String())
|
err = tk.Errorf("unexpected token %q", tk.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ func TestAddTokensBad(t *testing.T) {
|
|||||||
func TestAddUnknownTokens(t *testing.T) {
|
func TestAddUnknownTokens(t *testing.T) {
|
||||||
tk0 := NewToken(0, 0, SymPercent, "%")
|
tk0 := NewToken(0, 0, SymPercent, "%")
|
||||||
|
|
||||||
wantErr := errors.New(`No term constructor for token "%"`)
|
wantErr := errors.New(`unexpected token "%"`)
|
||||||
|
|
||||||
tree := NewAst()
|
tree := NewAst()
|
||||||
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
|
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-errors.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- General errors
|
||||||
|
|
||||||
|
func errCantConvert(funcName string, value any, kind string) error {
|
||||||
|
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errExpectedGot(funcName string, kind string, value any) error {
|
||||||
|
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Parameter errors
|
||||||
|
|
||||||
|
func errOneParam(funcName string) error {
|
||||||
|
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errMissingRequiredParameter(funcName, paramName string) error {
|
||||||
|
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||||
|
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||||
|
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-params.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const (
|
||||||
|
paramParts = "parts"
|
||||||
|
paramSeparator = "separator"
|
||||||
|
paramSource = "source"
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-type-names.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeBoolean = "boolean"
|
||||||
|
typeFloat = "decimal"
|
||||||
|
typeFraction = "fraction"
|
||||||
|
typeInt = "integer"
|
||||||
|
typeNumber = "number"
|
||||||
|
typeString = "string"
|
||||||
|
)
|
||||||
@@ -27,8 +27,10 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
|||||||
|
|
||||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
exportAll := isEnabled(sourceCtx, control_export_all)
|
exportAll := isEnabled(sourceCtx, control_export_all)
|
||||||
|
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
||||||
// Export variables
|
// Export variables
|
||||||
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||||
|
// fmt.Printf("\tExporting %q\n", refName)
|
||||||
refValue, _ := sourceCtx.GetVar(refName)
|
refValue, _ := sourceCtx.GetVar(refName)
|
||||||
exportVar(destCtx, refName, refValue)
|
exportVar(destCtx, refName, refValue)
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-16
@@ -5,34 +5,119 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
initName = "init"
|
|
||||||
nextName = "next"
|
|
||||||
currentName = "current"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dataCursor struct {
|
type dataCursor struct {
|
||||||
ds map[any]*term
|
ds map[string]Functor
|
||||||
ctx ExprContext
|
ctx ExprContext
|
||||||
index int
|
index int
|
||||||
resource any
|
resource any
|
||||||
nextFunc Functor
|
nextFunc Functor
|
||||||
|
cleanFunc Functor
|
||||||
|
resetFunc Functor
|
||||||
currentFunc Functor
|
currentFunc Functor
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDataCursor(ctx ExprContext) (dc *dataCursor) {
|
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
|
||||||
dc = &dataCursor{
|
dc = &dataCursor{
|
||||||
|
ds: ds,
|
||||||
index: -1,
|
index: -1,
|
||||||
ctx: ctx.Clone(),
|
ctx: ctx.Clone(),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func mapToString(m map[string]Functor) string {
|
||||||
|
// var sb strings.Builder
|
||||||
|
// sb.WriteByte('{')
|
||||||
|
// for key, _ := range m {
|
||||||
|
// if sb.Len() > 1 {
|
||||||
|
// sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
|
||||||
|
// } else {
|
||||||
|
// sb.WriteString(fmt.Sprintf("%q: func(){}", key))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// sb.WriteByte('}')
|
||||||
|
// return sb.String()
|
||||||
|
// }
|
||||||
|
|
||||||
func (dc *dataCursor) String() string {
|
func (dc *dataCursor) String() string {
|
||||||
return "$(...)"
|
return "$()"
|
||||||
|
/*
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(fmt.Sprintf(`$(
|
||||||
|
index: %d,
|
||||||
|
ds: %s,
|
||||||
|
ctx: `, dc.index, mapToString(dc.ds)))
|
||||||
|
CtxToBuilder(&sb, dc.ctx, 1)
|
||||||
|
sb.WriteByte(')')
|
||||||
|
return sb.String()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
||||||
|
exists = name == indexName
|
||||||
|
if !exists {
|
||||||
|
f, ok := dc.ds[name]
|
||||||
|
exists = ok && isFunctor(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
|
||||||
|
if name == indexName {
|
||||||
|
value = int64(dc.Index())
|
||||||
|
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
|
||||||
|
if functor == dc.cleanFunc {
|
||||||
|
value, err = dc.Clean()
|
||||||
|
} else if functor == dc.resetFunc {
|
||||||
|
value, err = dc.Reset()
|
||||||
|
} else {
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
value, err = functor.Invoke(ctx, name, []any{})
|
||||||
|
exportObjects(dc.ctx, ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Reset() (success bool, err error) {
|
||||||
|
if dc.resetFunc != nil {
|
||||||
|
if dc.resource != nil {
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
|
||||||
|
dc.index = -1
|
||||||
|
}
|
||||||
|
exportObjects(dc.ctx, ctx)
|
||||||
|
} else {
|
||||||
|
err = errInvalidDataSource()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errNoOperation(resetName)
|
||||||
|
}
|
||||||
|
success = err == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Clean() (success bool, err error) {
|
||||||
|
if dc.cleanFunc != nil {
|
||||||
|
if dc.resource != nil {
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
|
||||||
|
dc.resource = nil
|
||||||
|
exportObjects(dc.ctx, ctx)
|
||||||
|
} else {
|
||||||
|
err = errInvalidDataSource()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errors.New("no 'clean' function defined in the data-source")
|
||||||
|
}
|
||||||
|
success = err == nil
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||||
@@ -45,15 +130,22 @@ func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
ctx := cloneContext(dc.ctx)
|
if dc.resource != nil {
|
||||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
|
ctx := cloneContext(dc.ctx)
|
||||||
if item == nil {
|
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||||
err = io.EOF
|
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
||||||
} else {
|
if item == nil {
|
||||||
dc.index++
|
err = io.EOF
|
||||||
|
} else {
|
||||||
|
dc.index++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||||
|
exportObjects(dc.ctx, ctx)
|
||||||
|
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
|
||||||
|
} else {
|
||||||
|
err = errInvalidDataSource()
|
||||||
}
|
}
|
||||||
exportObjects(dc.ctx, ctx)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// dict_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDictParser(t *testing.T) {
|
||||||
|
section := "Dict"
|
||||||
|
|
||||||
|
type inputType struct {
|
||||||
|
source string
|
||||||
|
wantResult any
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`{}`, map[any]any{}, nil},
|
||||||
|
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
|
||||||
|
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1):"one", int64(2):"two", int64(3):"three"}, nil},
|
||||||
|
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
|
||||||
|
// /* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
||||||
|
// /* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
||||||
|
// /* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||||
|
// /* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
||||||
|
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||||
|
// /* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
||||||
|
// /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
||||||
|
// /* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
||||||
|
// /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
||||||
|
// /* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||||
|
// /* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||||
|
// /* 14 */ {`[1,2,3].1`, int64(2), nil},
|
||||||
|
// /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
|
||||||
|
// /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
// inputs1 := []inputType{
|
||||||
|
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||||
|
// }
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
var expr *ast
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
ctx := NewSimpleFuncStore()
|
||||||
|
ctx.SetVar("var1", int64(123))
|
||||||
|
ctx.SetVar("var2", "abc")
|
||||||
|
ImportMathFuncs(ctx)
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
logTest(t, i+1, "Dict", 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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotList, okGot := gotResult.([]any); okGot {
|
||||||
|
if wantList, okWant := input.wantResult.([]any); okWant {
|
||||||
|
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
} else {
|
||||||
|
equal := len(gotList) == len(wantList)
|
||||||
|
if equal {
|
||||||
|
for i, gotItem := range gotList {
|
||||||
|
wantItem := wantList[i]
|
||||||
|
equal = gotItem == wantItem
|
||||||
|
if !equal {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !equal {
|
||||||
|
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.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
+124
-33
@@ -22,15 +22,92 @@ Expressions calculator
|
|||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
#TODO: Work in progress#
|
#TODO: Work in progress (last update on 2024/05/07, 07:15 am)#
|
||||||
|
|
||||||
== 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.
|
||||||
|
|
||||||
|
=== `dev-expr` test tool
|
||||||
|
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, beyond in additon to the automatic test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
|
||||||
|
|
||||||
|
It cat work as a REPL, *R*ead-*E*xecute-*P*rint-*L*oop, or it can process expression acquired from files or standard input.
|
||||||
|
|
||||||
|
The program in located in the _tools_ directory.
|
||||||
|
Here are some examples of execution.
|
||||||
|
|
||||||
|
.Run `dev-expr` in REPL mode and ask for help
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
|
||||||
|
|
||||||
|
[user]$ tools/expr -- Expressions calculator v1.7.0,2024/05/08 (celestino.amoroso@portale-stac.it)
|
||||||
|
Type help to get the list of command.
|
||||||
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
|
|
||||||
|
>>> help
|
||||||
|
--- REPL commands:
|
||||||
|
base -- Set the integer output base: 2, 8, 10, or 16
|
||||||
|
exit -- Exit the program
|
||||||
|
help -- Show command list
|
||||||
|
ml -- Enable/Disable multi-line output
|
||||||
|
mods -- List builtin modules
|
||||||
|
source -- Load a file as input
|
||||||
|
tty -- Enable/Disable ansi output <1>
|
||||||
|
|
||||||
|
--- Command line options:
|
||||||
|
-b <builtin> Import builtin modules.
|
||||||
|
<builtin> can be a list of module names or a glob-pattern.
|
||||||
|
Use the special value 'all' or the pattern '*' to import all modules.
|
||||||
|
-e <expression> Evaluate <expression> instead of standard-input
|
||||||
|
-i Force REPL operation when all -e occurences have been processed
|
||||||
|
-h, --help, help Show this help menu
|
||||||
|
-m, --modules List all builtin modules
|
||||||
|
-p Print prefix form
|
||||||
|
-t Print tree form <2>
|
||||||
|
|
||||||
|
>>>
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> Only available for single fraction values
|
||||||
|
<2> Work in progress
|
||||||
|
|
||||||
|
.REPL examples
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
[user]$ tools/expr -- Expressions calculator v1.6.1,2024/05/06 (celestino.amoroso@portale-stac.it)
|
||||||
|
Type help to get the list of command.
|
||||||
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
|
>>> 2+3
|
||||||
|
5
|
||||||
|
>>> 2+3*(4-1.5)
|
||||||
|
9.5
|
||||||
|
>>> 0xFD + 0b1 + 0o1 <1>
|
||||||
|
255
|
||||||
|
>>> 1|2 + 2|3 <2>
|
||||||
|
7|6
|
||||||
|
>>> ml <3>
|
||||||
|
>>> 1|2 + 2|3
|
||||||
|
7
|
||||||
|
-
|
||||||
|
6
|
||||||
|
>>> 1+2 but 5|2+0.5 <4>
|
||||||
|
3
|
||||||
|
>>> 1+2; 5|2+0.5 <5>
|
||||||
|
3
|
||||||
|
>>>
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
|
||||||
|
<2> Fractions: numerator | denominator.
|
||||||
|
<3> Activate multi-line output of fractions.
|
||||||
|
<4> But operator, see <<_but_operator>>.
|
||||||
|
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
|
||||||
|
|
||||||
=== Concepts and terminology
|
=== Concepts and terminology
|
||||||
#TODO#
|
#TODO#
|
||||||
|
|
||||||
|
image::expression-diagram.png[]
|
||||||
|
|
||||||
== Data types
|
== Data types
|
||||||
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
|
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
|
||||||
|
|
||||||
@@ -169,7 +246,7 @@ x = 1; y = 2*x
|
|||||||
== Other operations
|
== Other operations
|
||||||
|
|
||||||
=== [blue]`;` operator
|
=== [blue]`;` operator
|
||||||
The semicolon operator [blue]`;` is an infixed operator. It evaluates the left expression first and then the right expression. The latter is the final result.
|
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.
|
||||||
|
|
||||||
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
||||||
|
|
||||||
@@ -182,12 +259,12 @@ 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 expressions of any type. 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 variables in the evaluation context or to change their value (see _ExprContext_).
|
||||||
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
@@ -202,13 +279,22 @@ The _selector operator_ is very similar to the _switch/case/default_ statement a
|
|||||||
.Syntax
|
.Syntax
|
||||||
[source,bnf]
|
[source,bnf]
|
||||||
----
|
----
|
||||||
<selector-operator> ::= <expression> "?" <selector-case> { ":" <selector-case> } ["::" <case-value>]
|
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>]
|
||||||
<selector-case> ::= [<list>] <case-value>
|
<selector-case> ::= [<match-list>] <case-value>
|
||||||
<case-value> ::= "{" <multi-expr> "}"
|
<match-list> ::= "["<item>{","<items>}"]"
|
||||||
<multi-expr> ::= <expr> {";" <expr>}
|
<item> ::= <expression
|
||||||
|
<case-multi-expression> ::= "{" <multi-expression> "}"
|
||||||
|
<multi-expression> ::= <expression> {";" <expression>}
|
||||||
----
|
----
|
||||||
|
|
||||||
.Example
|
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision find a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation.
|
||||||
|
|
||||||
|
The match lists are optional. In that case, the position, from left to right, of the `<selector-case>` is used as match-list. Of course, that only works if the select-expression results in an integer.
|
||||||
|
|
||||||
|
The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.
|
||||||
|
|
||||||
|
|
||||||
|
.Examples
|
||||||
[source,go]
|
[source,go]
|
||||||
----
|
----
|
||||||
1 ? {"a"} : {"b"} // returns "b"
|
1 ? {"a"} : {"b"} // returns "b"
|
||||||
@@ -224,33 +310,38 @@ The _selector operator_ is very similar to the _switch/case/default_ statement a
|
|||||||
The table below shows all supported operators by decreasing priorities.
|
The table below shows all supported operators by decreasing priorities.
|
||||||
|
|
||||||
.Operators priorities
|
.Operators priorities
|
||||||
[cols="^2,^2,^2,^5,<5"]
|
[cols="^2,^2,^2,^5,^5"]
|
||||||
|===
|
|===
|
||||||
| Priority | Operators | Position | Operation | Operands and results
|
| Priority | Operators | Position | Operation | Operands and results
|
||||||
|
|
||||||
1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ "!" -> _integer_
|
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
|
||||||
1+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| ("+"\|"-") _number_ -> _number_
|
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||||
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ "*" _number_ -> _number_
|
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||||
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ "*" _integer_ -> _string_
|
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||||
| [blue]`/` | _Infix_ | _Division_ | _number_ "/" _number_ -> _number_
|
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_
|
||||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ "./" _number_ -> _float_
|
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_
|
||||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ "%" _integer_ -> _integer_
|
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_
|
||||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ "+" _number_ -> _number_
|
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_
|
||||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) "+" (_string_\|_number_) -> _string_
|
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_
|
||||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ "+" _list_ -> _list_
|
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
||||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ "-" _number_ -> _number_
|
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
||||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ "-" _list_ -> _list_
|
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
||||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ "<" _comparable_ -> _boolean_
|
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ "\<=" _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
||||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ ">" _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
||||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ ">=" _comparable_ -> _boolean_
|
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
||||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ "==" _comparable_ -> _boolean_
|
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
||||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ "!=" _comparable_ -> _boolean_
|
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | "not" _boolean_ -> _boolean_
|
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
||||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
|
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
||||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
|
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
||||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
|
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
||||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ "\|\|" _boolean_ -> _boolean_
|
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
||||||
|
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
||||||
|
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
||||||
|
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
||||||
|
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
||||||
|
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
||||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||||
|===
|
|===
|
||||||
|
|||||||
+1400
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
+8
-11
@@ -5,7 +5,6 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -22,13 +21,8 @@ func TestExpr(t *testing.T) {
|
|||||||
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
||||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
||||||
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||||
}
|
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
||||||
|
/* 10 */ {`
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
inputs1 := []inputType{
|
|
||||||
/* 1 */ {`
|
|
||||||
ds={
|
ds={
|
||||||
"init":func(end){@end=end; @current=0 but true},
|
"init":func(end){@end=end; @current=0 but true},
|
||||||
"current":func(){current},
|
"current":func(){current},
|
||||||
@@ -42,7 +36,10 @@ func TestExpr(t *testing.T) {
|
|||||||
`, int64(1), nil},
|
`, int64(1), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, input := range inputs1 {
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
var expr Expr
|
var expr Expr
|
||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
@@ -53,7 +50,7 @@ func TestExpr(t *testing.T) {
|
|||||||
ImportOsFuncs(ctx)
|
ImportOsFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, "Expr", input.source, input.wantResult, input.wantErr)
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
r := strings.NewReader(input.source)
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
@@ -81,5 +78,5 @@ func TestExpr(t *testing.T) {
|
|||||||
failed++
|
failed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
|
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
builtin ["os.file", "base"];
|
||||||
|
|
||||||
|
readInt=func(fh){
|
||||||
|
line=readFile(fh);
|
||||||
|
line ? [nil] {nil} :: {int(line)}
|
||||||
|
};
|
||||||
|
|
||||||
|
ds={
|
||||||
|
"init":func(filename){
|
||||||
|
fh=openFile(filename);
|
||||||
|
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
|
||||||
|
fh
|
||||||
|
},
|
||||||
|
"current":func(){
|
||||||
|
prev
|
||||||
|
},
|
||||||
|
"next":func(fh){
|
||||||
|
current ?
|
||||||
|
[nil] {current}
|
||||||
|
:: {@prev=current; @current=readInt(fh) but current}
|
||||||
|
},
|
||||||
|
"clean":func(fh){
|
||||||
|
closeFile(fh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//;f=$(ds, "int.list")
|
||||||
|
/*
|
||||||
|
;f++
|
||||||
|
;f++
|
||||||
|
;f++
|
||||||
|
*/
|
||||||
|
//;add(f)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// formatter.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
type FmtOpt uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
TTY FmtOpt = 1 << iota
|
||||||
|
MultiLine
|
||||||
|
Base2
|
||||||
|
Base8
|
||||||
|
Base10
|
||||||
|
Base16
|
||||||
|
)
|
||||||
|
|
||||||
|
type Formatter interface {
|
||||||
|
ToString(options FmtOpt) string
|
||||||
|
}
|
||||||
@@ -55,5 +55,5 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("builtins", ImportBuiltinsFuncs)
|
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// 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
-5
@@ -20,7 +20,7 @@ func importFunc(ctx ExprContext, name string, args []any) (result any, err error
|
|||||||
return importGeneral(ctx, name, args)
|
return importGeneral(ctx, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
enable(ctx, control_export_all)
|
enable(ctx, control_export_all)
|
||||||
return importGeneral(ctx, name, args)
|
return importGeneral(ctx, name, args)
|
||||||
}
|
}
|
||||||
@@ -30,12 +30,12 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
|
|||||||
|
|
||||||
dirList = addEnvImportDirs(dirList)
|
dirList = addEnvImportDirs(dirList)
|
||||||
dirList = addPresetImportDirs(ctx, dirList)
|
dirList = addPresetImportDirs(ctx, dirList)
|
||||||
result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args))
|
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
||||||
if !(isString(paramValue) /*|| isList(paramValue)*/) {
|
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
|
||||||
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
|
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -138,9 +138,9 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
|||||||
|
|
||||||
func ImportImportFuncs(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("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("import", ImportImportFuncs)
|
registerImport("import", ImportImportFuncs, "Functions import() and include()")
|
||||||
}
|
}
|
||||||
|
|||||||
+87
-30
@@ -9,41 +9,65 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
|
||||||
if !(isNumber(paramValue) || isList(paramValue)) {
|
if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
|
||||||
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
|
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
|
||||||
|
funcName, paramPos+1, subPos+1, level, paramValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result any, err error) {
|
||||||
var sumAsFloat = false
|
var sumAsFloat, sumAsFract bool
|
||||||
var floatSum float64 = 0.0
|
var floatSum float64 = 0.0
|
||||||
var intSum int64 = 0
|
var intSum int64 = 0
|
||||||
|
var fractSum *fraction
|
||||||
var v any
|
var v any
|
||||||
|
|
||||||
|
level++
|
||||||
|
|
||||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if list, ok := v.(*ListType); ok {
|
||||||
|
v = NewListIterator(list, nil)
|
||||||
|
}
|
||||||
if subIter, ok := v.(Iterator); ok {
|
if subIter, ok := v.(Iterator); ok {
|
||||||
if v, err = doAdd(ctx, name, subIter); err != nil {
|
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
|
||||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
|
||||||
break
|
return
|
||||||
}
|
|
||||||
if array, ok := v.([]any); ok {
|
|
||||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
if !sumAsFloat {
|
||||||
|
if IsFloat(v) {
|
||||||
|
sumAsFloat = true
|
||||||
|
if sumAsFract {
|
||||||
|
floatSum = fractSum.toFloat()
|
||||||
|
} else {
|
||||||
|
floatSum = float64(intSum)
|
||||||
|
}
|
||||||
|
} else if !sumAsFract && isFraction(v) {
|
||||||
|
fractSum = newFraction(intSum, 1)
|
||||||
|
sumAsFract = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sumAsFloat && isFloat(v) {
|
|
||||||
sumAsFloat = true
|
|
||||||
floatSum = float64(intSum)
|
|
||||||
}
|
|
||||||
if sumAsFloat {
|
if sumAsFloat {
|
||||||
floatSum += numAsFloat(v)
|
floatSum += numAsFloat(v)
|
||||||
|
} else if sumAsFract {
|
||||||
|
var item *fraction
|
||||||
|
var ok bool
|
||||||
|
if item, ok = v.(*fraction); !ok {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
item = newFraction(iv, 1)
|
||||||
|
}
|
||||||
|
fractSum = sumFract(fractSum, item)
|
||||||
} else {
|
} else {
|
||||||
iv, _ := v.(int64)
|
iv, _ := v.(int64)
|
||||||
intSum += iv
|
intSum += iv
|
||||||
@@ -53,6 +77,8 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
err = nil
|
err = nil
|
||||||
if sumAsFloat {
|
if sumAsFloat {
|
||||||
result = floatSum
|
result = floatSum
|
||||||
|
} else if sumAsFract {
|
||||||
|
result = fractSum
|
||||||
} else {
|
} else {
|
||||||
result = intSum
|
result = intSum
|
||||||
}
|
}
|
||||||
@@ -61,33 +87,62 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
|
result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result any, err error) {
|
||||||
var mulAsFloat = false
|
var mulAsFloat, mulAsFract bool
|
||||||
var floatProd float64 = 1.0
|
var floatProd float64 = 1.0
|
||||||
var intProd int64 = 1
|
var intProd int64 = 1
|
||||||
|
var fractProd *fraction
|
||||||
var v any
|
var v any
|
||||||
|
|
||||||
|
level++
|
||||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
if list, ok := v.(*ListType); ok {
|
||||||
break
|
v = NewListIterator(list, nil)
|
||||||
}
|
}
|
||||||
|
if subIter, ok := v.(Iterator); ok {
|
||||||
if array, ok := v.([]any); ok {
|
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
|
||||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
break
|
||||||
|
}
|
||||||
|
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
|
||||||
|
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
if !mulAsFloat && isFloat(v) {
|
if !mulAsFloat {
|
||||||
mulAsFloat = true
|
if IsFloat(v) {
|
||||||
floatProd = float64(intProd)
|
mulAsFloat = true
|
||||||
|
if mulAsFract {
|
||||||
|
floatProd = fractProd.toFloat()
|
||||||
|
} else {
|
||||||
|
floatProd = float64(intProd)
|
||||||
|
}
|
||||||
|
} else if !mulAsFract && isFraction(v) {
|
||||||
|
fractProd = newFraction(intProd, 1)
|
||||||
|
mulAsFract = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mulAsFloat {
|
if mulAsFloat {
|
||||||
floatProd *= numAsFloat(v)
|
floatProd *= numAsFloat(v)
|
||||||
|
} else if mulAsFract {
|
||||||
|
var item *fraction
|
||||||
|
var ok bool
|
||||||
|
if item, ok = v.(*fraction); !ok {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
item = newFraction(iv, 1)
|
||||||
|
}
|
||||||
|
fractProd = mulFract(fractProd, item)
|
||||||
} else {
|
} else {
|
||||||
iv, _ := v.(int64)
|
iv, _ := v.(int64)
|
||||||
intProd *= iv
|
intProd *= iv
|
||||||
@@ -97,6 +152,8 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
err = nil
|
err = nil
|
||||||
if mulAsFloat {
|
if mulAsFloat {
|
||||||
result = floatProd
|
result = floatProd
|
||||||
|
} else if mulAsFract {
|
||||||
|
result = fractProd
|
||||||
} else {
|
} else {
|
||||||
result = intProd
|
result = intProd
|
||||||
}
|
}
|
||||||
@@ -105,7 +162,7 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
result, err = doMul(ctx, name, NewFlatArrayIterator(args))
|
result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,5 +172,5 @@ func ImportMathFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("math.arith", ImportMathFuncs)
|
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()")
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -126,7 +126,7 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
|
|||||||
|
|
||||||
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
var handle osHandle
|
var handle osHandle
|
||||||
|
result = nil
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
handle, _ = args[0].(osHandle)
|
handle, _ = args[0].(osHandle)
|
||||||
}
|
}
|
||||||
@@ -141,13 +141,16 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
limit = s[0]
|
limit = s[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
|
if v, err = r.reader.ReadString(limit); err == nil {
|
||||||
if len(v) > 0 && v[len(v)-1] == limit {
|
if len(v) > 0 && v[len(v)-1] == limit {
|
||||||
result = v[0 : len(v)-1]
|
result = v[0 : len(v)-1]
|
||||||
} else {
|
} else {
|
||||||
result = v
|
result = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,5 +167,5 @@ func ImportOsFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("os", ImportOsFuncs)
|
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
|
||||||
}
|
}
|
||||||
|
|||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-string.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Start of function definitions
|
||||||
|
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
var v any
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if it.Index() > 0 {
|
||||||
|
sb.WriteString(sep)
|
||||||
|
}
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
sb.WriteString(s)
|
||||||
|
} else {
|
||||||
|
err = errExpectedGot(funcName, typeString, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil || err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
result = sb.String()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||||
|
}
|
||||||
|
if sep, ok := args[0].(string); ok {
|
||||||
|
if len(args) == 1 {
|
||||||
|
result = ""
|
||||||
|
} else if len(args) == 2 {
|
||||||
|
if ls, ok := args[1].(*ListType); ok {
|
||||||
|
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
|
||||||
|
} else if it, ok := args[1].(Iterator); ok {
|
||||||
|
result, err = doJoinStr(name, sep, it)
|
||||||
|
} else {
|
||||||
|
err = errInvalidParameterValue(name, paramParts, args[1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errWrongParamType(name, paramSeparator, typeString, args[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var start = 0
|
||||||
|
var count = -1
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
if start, err = toInt(args[1], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 2 {
|
||||||
|
if count, err = toInt(args[2], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < 0 {
|
||||||
|
start = len(source) + start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count < 0 {
|
||||||
|
count = len(source) - start
|
||||||
|
}
|
||||||
|
end := min(start+count, len(source))
|
||||||
|
result = source[start:end]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
result = strings.TrimSpace(source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
if len(args) < 1 {
|
||||||
|
return result, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
for i, targetSpec := range args[1:] {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasPrefix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
if len(args) < 1 {
|
||||||
|
return result, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
for i, targetSpec := range args[1:] {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasSuffix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source, sep string
|
||||||
|
var count int = -1
|
||||||
|
var parts []string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
return result, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
if len(args) >= 2 {
|
||||||
|
if sep, ok = args[1].(string); !ok {
|
||||||
|
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
|
||||||
|
}
|
||||||
|
if len(args) >= 3 {
|
||||||
|
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
|
||||||
|
count = int(count64)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
parts = strings.SplitN(source, sep, count)
|
||||||
|
} else if count < 0 {
|
||||||
|
parts = strings.Split(source, sep)
|
||||||
|
} else {
|
||||||
|
parts = []string{}
|
||||||
|
}
|
||||||
|
list := make(ListType, len(parts))
|
||||||
|
for i, part := range parts {
|
||||||
|
list[i] = part
|
||||||
|
}
|
||||||
|
result = &list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- End of function definitions
|
||||||
|
|
||||||
|
// Import above functions in the context
|
||||||
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
|
||||||
|
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
|
||||||
|
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the import function in the import-register.
|
||||||
|
// That will allow to import all function of this module by the "builtin" operator."
|
||||||
|
func init() {
|
||||||
|
registerImport("string", ImportStringFuncs, "string utilities")
|
||||||
|
}
|
||||||
+38
-54
@@ -6,18 +6,10 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFuncs(t *testing.T) {
|
func TestFuncs(t *testing.T) {
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`isNil(nil)`, true, nil},
|
/* 1 */ {`isNil(nil)`, true, nil},
|
||||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||||
@@ -30,53 +22,45 @@ func TestFuncs(t *testing.T) {
|
|||||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
/* 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`)},
|
/* 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`)},
|
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
|
||||||
|
/* 12 */ {`two=func(){2}; two()`, int64(2), nil},
|
||||||
|
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
||||||
|
/* 14 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
||||||
|
/* 15 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
||||||
|
/* 16 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 17 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 18 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 19 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
||||||
|
/* 20 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
||||||
|
/* 21 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
||||||
|
/* 22 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
|
/* 23 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||||
|
/* 24 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||||
|
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||||
|
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
|
||||||
|
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
|
||||||
|
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
|
||||||
|
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
|
||||||
|
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
|
||||||
|
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
|
||||||
|
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
|
||||||
|
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
|
||||||
|
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
|
||||||
|
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
|
||||||
|
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
|
||||||
|
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
|
||||||
|
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
|
||||||
|
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||||
|
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
||||||
|
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
|
||||||
|
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||||
|
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
||||||
|
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one","two","three"), nil},
|
||||||
|
/* 45 */ {`["a", "b", "c"]`, newListA("a","b","c"), nil},
|
||||||
|
/* 46 */ {`["a", "b", "c"]`, newList([]any{"a","b","c"}), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
succeeded := 0
|
t.Setenv("EXPR_PATH", ".")
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
// parserTest(t, "Func", inputs[25:26])
|
||||||
// /* 1 */ {`0?{}`, nil, nil},
|
parserTest(t, "Func", inputs)
|
||||||
// }
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+23
@@ -6,6 +6,8 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,3 +61,24 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
|
||||||
|
var tree *ast
|
||||||
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
if tree, err = parser.Parse(scanner); err == nil {
|
||||||
|
result, err = tree.Eval(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalFile(ctx ExprContext, filePath string) (result any, err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
result, err = EvalStream(ctx, fh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
+108
-18
@@ -4,39 +4,129 @@
|
|||||||
// iter-list.go
|
// iter-list.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "io"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
type FlatArrayIterator struct {
|
type ListIterator struct {
|
||||||
a []any
|
a *ListType
|
||||||
|
count int
|
||||||
index int
|
index int
|
||||||
|
start int
|
||||||
|
stop int
|
||||||
|
step int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
|
func NewListIterator(list *ListType, args []any) (it *ListIterator) {
|
||||||
return &FlatArrayIterator{a: array, index: 0}
|
var argc int = 0
|
||||||
|
listLen := len(([]any)(*list))
|
||||||
|
if args != nil {
|
||||||
|
argc = len(args)
|
||||||
|
}
|
||||||
|
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
|
||||||
|
if argc >= 1 {
|
||||||
|
if i, err := toInt(args[0], "start index"); err == nil {
|
||||||
|
if i < 0 {
|
||||||
|
i = listLen + i
|
||||||
|
}
|
||||||
|
it.start = i
|
||||||
|
}
|
||||||
|
if argc >= 2 {
|
||||||
|
if i, err := toInt(args[1], "stop index"); err == nil {
|
||||||
|
if i < 0 {
|
||||||
|
i = listLen + i
|
||||||
|
}
|
||||||
|
it.stop = i
|
||||||
|
}
|
||||||
|
if argc >= 3 {
|
||||||
|
if i, err := toInt(args[2], "step"); err == nil {
|
||||||
|
if i < 0 {
|
||||||
|
i = -i
|
||||||
|
}
|
||||||
|
if it.start > it.stop {
|
||||||
|
it.step = -i
|
||||||
|
} else {
|
||||||
|
it.step = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.index = it.start - it.step
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Current() (item any, err error) {
|
func NewArrayIterator(array []any) (it *ListIterator) {
|
||||||
if it.index >= 0 && it.index < len(it.a) {
|
it = &ListIterator{a: (*ListType)(&array), count: 0, index: -1, start: 0, stop: len(array) - 1, step: 1}
|
||||||
item = it.a[it.index]
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnyIterator(value any) (it *ListIterator) {
|
||||||
|
if value == nil {
|
||||||
|
it = NewArrayIterator([]any{})
|
||||||
|
} else if list, ok := value.(*ListType); ok {
|
||||||
|
it = NewListIterator(list, nil)
|
||||||
|
} else if array, ok := value.([]any); ok {
|
||||||
|
it = NewArrayIterator(array)
|
||||||
|
} else if it1, ok := value.(*ListIterator); ok {
|
||||||
|
it = it1
|
||||||
|
} else {
|
||||||
|
it = NewArrayIterator([]any{value})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) String() string {
|
||||||
|
var l = 0
|
||||||
|
if it.a != nil {
|
||||||
|
l = len(*it.a)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("$(#%d)", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) HasOperation(name string) bool {
|
||||||
|
yes := name == resetName || name == indexName || name == countName
|
||||||
|
return yes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case resetName:
|
||||||
|
v, err = it.Reset()
|
||||||
|
case indexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case countName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = errNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Current() (item any, err error) {
|
||||||
|
a := *(it.a)
|
||||||
|
if it.index >= 0 && it.index <= it.stop {
|
||||||
|
item = a[it.index]
|
||||||
} else {
|
} else {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Next() (item any, err error) {
|
func (it *ListIterator) Next() (item any, err error) {
|
||||||
|
it.index += it.step
|
||||||
if item, err = it.Current(); err != io.EOF {
|
if item, err = it.Current(); err != io.EOF {
|
||||||
it.index++
|
it.count++
|
||||||
}
|
}
|
||||||
// if it.index < len(it.a) {
|
|
||||||
// item = it.a[it.index]
|
|
||||||
// it.index++
|
|
||||||
// } else {
|
|
||||||
// err = io.EOF
|
|
||||||
// }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Index() int {
|
func (it *ListIterator) Index() int {
|
||||||
return it.index - 1
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Reset() (bool, error) {
|
||||||
|
it.index = it.start
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
ds={
|
||||||
|
"init":func(end){@end=end; @current=0; @prev=@current},
|
||||||
|
"current":func(){prev},
|
||||||
|
"next":func(){
|
||||||
|
(current <= end) ? [true] {@current=current+1; @prev=current} :: {nil}
|
||||||
|
},
|
||||||
|
"reset":func(){@current=0; @prev=@current}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example
|
||||||
|
//;
|
||||||
|
//it=$(ds,3);
|
||||||
|
//it++;
|
||||||
|
//it."reset"
|
||||||
|
//it++;
|
||||||
|
//it++;
|
||||||
|
//add(it)
|
||||||
|
|
||||||
+31
@@ -4,8 +4,39 @@
|
|||||||
// iterator.go
|
// iterator.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operator names
|
||||||
|
|
||||||
|
const (
|
||||||
|
initName = "init"
|
||||||
|
cleanName = "clean"
|
||||||
|
resetName = "reset"
|
||||||
|
nextName = "next"
|
||||||
|
currentName = "current"
|
||||||
|
indexName = "index"
|
||||||
|
countName = "count"
|
||||||
|
)
|
||||||
|
|
||||||
type Iterator interface {
|
type Iterator interface {
|
||||||
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)
|
Current() (item any, err error)
|
||||||
Index() int
|
Index() int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExtIterator interface {
|
||||||
|
Iterator
|
||||||
|
HasOperation(name string) bool
|
||||||
|
CallOperation(name string, args []any) (value any, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errNoOperation(name string) error {
|
||||||
|
return fmt.Errorf("no %q function defined in the data-source", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidDataSource() error {
|
||||||
|
return errors.New("invalid data-source")
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// iterator_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIteratorParser(t *testing.T) {
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
|
||||||
|
/* 2 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
|
||||||
|
/* 3 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
|
||||||
|
/* 4 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
|
||||||
|
/* 5 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
|
||||||
|
/* 6 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
|
||||||
|
/* 7 */ {`builtin "math.arith"; include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
||||||
|
/* 8 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil},
|
||||||
|
/* 10 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
|
||||||
|
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
|
||||||
|
}
|
||||||
|
// inputs1 := []inputType{
|
||||||
|
// /* 1 */ {`0?{}`, nil, nil},
|
||||||
|
// }
|
||||||
|
parserTest(t, "Iterator", inputs)
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// list_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListParser(t *testing.T) {
|
||||||
|
section := "List"
|
||||||
|
|
||||||
|
type inputType struct {
|
||||||
|
source string
|
||||||
|
wantResult any
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`[]`, []any{}, nil},
|
||||||
|
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||||
|
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
||||||
|
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
||||||
|
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||||
|
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
||||||
|
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||||
|
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
||||||
|
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
||||||
|
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
||||||
|
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
||||||
|
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||||
|
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||||
|
/* 14 */ {`[1,2,3].1`, int64(2), nil},
|
||||||
|
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
|
||||||
|
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
|
||||||
|
|
||||||
|
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||||
|
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
// inputs1 := []inputType{
|
||||||
|
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||||
|
// }
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
var expr *ast
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
ctx := NewSimpleFuncStore()
|
||||||
|
ctx.SetVar("var1", int64(123))
|
||||||
|
ctx.SetVar("var2", "abc")
|
||||||
|
ImportMathFuncs(ctx)
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
logTest(t, i+1, "List", 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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotList, okGot := gotResult.([]any); okGot {
|
||||||
|
if wantList, okWant := input.wantResult.([]any); okWant {
|
||||||
|
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
} else {
|
||||||
|
equal := len(gotList) == len(wantList)
|
||||||
|
if equal {
|
||||||
|
for i, gotItem := range gotList {
|
||||||
|
wantItem := wantList[i]
|
||||||
|
equal = gotItem == wantItem
|
||||||
|
if !equal {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !equal {
|
||||||
|
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.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// module-register.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type module struct {
|
||||||
|
importFunc func(ExprContext)
|
||||||
|
description string
|
||||||
|
imported bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newModule(importFunc func(ExprContext), description string) *module {
|
||||||
|
return &module{importFunc, description, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
var moduleRegister map[string]*module
|
||||||
|
|
||||||
|
func registerImport(name string, importFunc func(ExprContext), description string) {
|
||||||
|
if moduleRegister == nil {
|
||||||
|
moduleRegister = make(map[string]*module)
|
||||||
|
}
|
||||||
|
if _, exists := moduleRegister[name]; exists {
|
||||||
|
panic(fmt.Errorf("module %q already registered", name))
|
||||||
|
}
|
||||||
|
moduleRegister[name] = newModule(importFunc, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||||
|
var mod *module
|
||||||
|
if mod, exists = moduleRegister[name]; exists {
|
||||||
|
mod.importFunc(ctx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||||
|
var matched bool
|
||||||
|
for name, mod := range moduleRegister {
|
||||||
|
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||||
|
if matched {
|
||||||
|
count++
|
||||||
|
mod.importFunc(ctx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterateModules(op func(name, description string, imported bool) bool) {
|
||||||
|
if op != nil {
|
||||||
|
for name, mod := range moduleRegister {
|
||||||
|
if !op(name, mod.description, mod.imported) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
func init() {
|
||||||
|
if moduleRegister == nil {
|
||||||
|
moduleRegister = make(map[string]*module)
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-4
@@ -6,6 +6,7 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- function call term
|
// -------- function call term
|
||||||
@@ -21,9 +22,28 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func call
|
// -------- eval func call
|
||||||
|
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
||||||
|
if info, exists := ctx.GetFuncInfo(name); exists {
|
||||||
|
if info.MinArgs() > len(params) {
|
||||||
|
if info.MaxArgs() < 0 {
|
||||||
|
err = fmt.Errorf("too few params -- expected %d or more, got %d", info.MinArgs(), len(params))
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
|
||||||
|
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||||
ctx := cloneContext(parentCtx)
|
ctx := cloneContext(parentCtx)
|
||||||
name, _ := self.tk.Value.(string)
|
name, _ := self.tk.Value.(string)
|
||||||
|
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
||||||
params := make([]any, len(self.children))
|
params := make([]any, len(self.children))
|
||||||
for i, tree := range self.children {
|
for i, tree := range self.children {
|
||||||
var param any
|
var param any
|
||||||
@@ -33,8 +53,10 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
|||||||
params[i] = param
|
params[i] = param
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if v, err = ctx.Call(name, params); err == nil {
|
if err = checkFunctionCall(ctx, name, params); err == nil {
|
||||||
exportObjects(parentCtx, ctx)
|
if v, err = ctx.Call(name, params); err == nil {
|
||||||
|
exportObjects(parentCtx, ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -43,9 +65,9 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
|||||||
// -------- function definition term
|
// -------- function definition term
|
||||||
func newFuncDefTerm(tk *Token, args []*term) *term {
|
func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk, // value is the expression body
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: args, // arg[0]=formal-param-list, arg[1]=*ast
|
children: args, // function params
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
priority: priValue,
|
priority: priValue,
|
||||||
evalFunc: evalFuncDef,
|
evalFunc: evalFuncDef,
|
||||||
|
|||||||
+116
-36
@@ -6,11 +6,13 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- iterator term
|
// -------- iterator term
|
||||||
|
|
||||||
func newIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
||||||
tk.Sym = SymIterator
|
tk.Sym = SymIterator
|
||||||
|
|
||||||
children := make([]*term, 0, 1+len(args))
|
children := make([]*term, 0, 1+len(args))
|
||||||
@@ -26,6 +28,18 @@ func newIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newIteratorTerm(tk *Token, args []*term) *term {
|
||||||
|
tk.Sym = SymIterator
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: args,
|
||||||
|
position: posLeaf,
|
||||||
|
priority: priValue,
|
||||||
|
evalFunc: evalIterator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------- eval iterator
|
// -------- eval iterator
|
||||||
|
|
||||||
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
||||||
@@ -41,64 +55,130 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
|
||||||
var value any
|
|
||||||
if len(self.children) < 1 || self.children[0] == nil {
|
if len(self.children) < 1 || self.children[0] == nil {
|
||||||
err = self.Errorf("missing the data-source parameter")
|
err = self.Errorf("missing the data-source parameter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if value, err = self.children[0].compute(ctx); err != nil {
|
value, err = self.children[0].compute(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if dictAny, ok := value.(map[any]any); ok {
|
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
||||||
|
if dictAny, ok := firstChildValue.(map[any]any); ok {
|
||||||
|
requiredFields := []string{currentName, nextName}
|
||||||
|
fieldsMask := 0b11
|
||||||
|
foundFields := 0
|
||||||
ds = make(map[string]Functor)
|
ds = make(map[string]Functor)
|
||||||
for _, k := range []string{initName, currentName, nextName} {
|
for keyAny, item := range dictAny {
|
||||||
if item, exists := dictAny[k]; exists && item != nil {
|
if key, ok := keyAny.(string); ok {
|
||||||
if functor, ok := item.(*funcDefFunctor); ok {
|
if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
ds[k] = functor
|
ds[key] = functor
|
||||||
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
|
foundFields |= 1 << index
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if k != initName {
|
|
||||||
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
// check required functions
|
||||||
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
if foundFields != fieldsMask {
|
||||||
|
missingFields := make([]string, 0, len(requiredFields))
|
||||||
|
for index, field := range requiredFields {
|
||||||
|
if (foundFields & (1 << index)) == 0 {
|
||||||
|
missingFields = append(missingFields, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var firstChildValue any
|
||||||
var ds map[string]Functor
|
var ds map[string]Functor
|
||||||
|
|
||||||
if ds, err = getDataSourceDict(ctx, self); err != nil {
|
if firstChildValue, err = evalFirstChild(ctx, self); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dc := newDataCursor(ctx)
|
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil {
|
||||||
|
return
|
||||||
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]
|
if ds != nil {
|
||||||
dc.currentFunc, _ = ds[currentName]
|
dc := newDataCursor(ctx, ds)
|
||||||
v = dc
|
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]
|
||||||
|
dc.cleanFunc, _ = ds[cleanName]
|
||||||
|
dc.resetFunc, _ = ds[resetName]
|
||||||
|
|
||||||
|
v = dc
|
||||||
|
} else if list, ok := firstChildValue.(*ListType); ok {
|
||||||
|
var args []any
|
||||||
|
if args, err = evalSibling(ctx, self.children, nil); err == nil {
|
||||||
|
v = NewListIterator(list, args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var list []any
|
||||||
|
if list, err = evalSibling(ctx, self.children, firstChildValue); err == nil {
|
||||||
|
v = NewArrayIterator(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
|
||||||
|
// items := make(ListType, len(terms))
|
||||||
|
// for i, tree := range terms {
|
||||||
|
// var param any
|
||||||
|
// if i == 0 && firstChildValue != nil {
|
||||||
|
// param = firstChildValue
|
||||||
|
// } else if param, err = tree.compute(ctx); err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// items[i] = param
|
||||||
|
// }
|
||||||
|
// if err == nil {
|
||||||
|
// list = &items
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
|
||||||
|
items := make([]any, 0, len(terms))
|
||||||
|
for i, tree := range terms {
|
||||||
|
var param any
|
||||||
|
if i == 0 {
|
||||||
|
if firstChildValue == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
param = firstChildValue
|
||||||
|
} else if param, err = tree.compute(ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
items = append(items, param)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
list = items
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-30
@@ -4,6 +4,64 @@
|
|||||||
// operand-list.go
|
// operand-list.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListType []any
|
||||||
|
|
||||||
|
func (ls *ListType) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteByte('[')
|
||||||
|
if len(*ls) > 0 {
|
||||||
|
if opt&MultiLine != 0 {
|
||||||
|
sb.WriteString("\n ")
|
||||||
|
}
|
||||||
|
for i, item := range []any(*ls) {
|
||||||
|
if i > 0 {
|
||||||
|
if opt&MultiLine != 0 {
|
||||||
|
sb.WriteString(",\n ")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s, ok := item.(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt&MultiLine != 0 {
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte(']')
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) String() string {
|
||||||
|
return ls.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListA(listAny ...any) (list *ListType) {
|
||||||
|
return newList(listAny)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newList(listAny []any) (list *ListType) {
|
||||||
|
if listAny != nil {
|
||||||
|
ls := make(ListType, len(listAny))
|
||||||
|
for i, item := range listAny {
|
||||||
|
ls[i] = item
|
||||||
|
}
|
||||||
|
list = &ls
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------- list term
|
// -------- list term
|
||||||
func newListTermA(args ...*term) *term {
|
func newListTermA(args ...*term) *term {
|
||||||
return newListTerm(args)
|
return newListTerm(args)
|
||||||
@@ -23,7 +81,7 @@ func newListTerm(args []*term) *term {
|
|||||||
// -------- list func
|
// -------- list func
|
||||||
func evalList(ctx ExprContext, self *term) (v any, err error) {
|
func evalList(ctx ExprContext, self *term) (v any, err error) {
|
||||||
list, _ := self.value().([]*term)
|
list, _ := self.value().([]*term)
|
||||||
items := make([]any, len(list))
|
items := make(ListType, len(list))
|
||||||
for i, tree := range list {
|
for i, tree := range list {
|
||||||
var param any
|
var param any
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
if param, err = tree.compute(ctx); err != nil {
|
||||||
@@ -32,35 +90,7 @@ func evalList(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
items[i] = param
|
items[i] = param
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v = items
|
v = &items
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// // -------- list term
|
|
||||||
// func newListTerm(args []*term) *term {
|
|
||||||
// return &term{
|
|
||||||
// tk: *NewToken(0, 0, SymList, "[]"),
|
|
||||||
// parent: nil,
|
|
||||||
// children: args,
|
|
||||||
// position: posLeaf,
|
|
||||||
// priority: priValue,
|
|
||||||
// evalFunc: evalList,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // -------- list func
|
|
||||||
// func evalList(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
// items := make([]any, len(self.children))
|
|
||||||
// for i, tree := range self.children {
|
|
||||||
// var param any
|
|
||||||
// if param, err = tree.compute(ctx); err != nil {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// items[i] = param
|
|
||||||
// }
|
|
||||||
// if err == nil {
|
|
||||||
// v = items
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|||||||
+16
-3
@@ -1,5 +1,5 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserightChilded.
|
||||||
|
|
||||||
// operator-assign.go
|
// operator-assign.go
|
||||||
package expr
|
package expr
|
||||||
@@ -27,9 +27,22 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err = self.children[1].compute(ctx); err == nil {
|
rightChild := self.children[1]
|
||||||
|
|
||||||
|
if v, err = rightChild.compute(ctx); err == nil {
|
||||||
if functor, ok := v.(Functor); ok {
|
if functor, ok := v.(Functor); ok {
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
var minArgs, maxArgs int = 0, -1
|
||||||
|
|
||||||
|
funcName := rightChild.source()
|
||||||
|
if info, exists := ctx.GetFuncInfo(funcName); exists {
|
||||||
|
minArgs = info.MinArgs()
|
||||||
|
maxArgs = info.MaxArgs()
|
||||||
|
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
|
||||||
|
l := len(funcDef.params)
|
||||||
|
minArgs = l
|
||||||
|
maxArgs = l
|
||||||
|
}
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.source(), v)
|
ctx.setVar(leftTerm.source(), v)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-13
@@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// operator-length.go
|
// operator-builtin.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
//-------- builtin term
|
//-------- builtin term
|
||||||
|
|
||||||
func newBuiltinTerm(tk *Token) (inst *term) {
|
func newBuiltinTerm(tk *Token) (inst *term) {
|
||||||
@@ -17,16 +19,20 @@ func newBuiltinTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var rightValue any
|
var childValue any
|
||||||
|
|
||||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
if isList(rightValue) {
|
if IsString(childValue) {
|
||||||
list, _ := rightValue.([]any)
|
module, _ := childValue.(string)
|
||||||
for i, moduleSpec := range list {
|
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||||
|
} else {
|
||||||
|
var moduleSpec any
|
||||||
|
it := NewAnyIterator(childValue)
|
||||||
|
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
||||||
if module, ok := moduleSpec.(string); ok {
|
if module, ok := moduleSpec.(string); ok {
|
||||||
if ImportInContext(ctx, module) {
|
if ImportInContext(ctx, module) {
|
||||||
count++
|
count++
|
||||||
@@ -35,18 +41,16 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
|
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if isString(rightValue) {
|
if err == io.EOF {
|
||||||
module, _ := rightValue.(string)
|
err = nil
|
||||||
count, err = ImportInContextByGlobPattern(ctx, module)
|
}
|
||||||
} else {
|
|
||||||
err = self.errIncompatibleType(rightValue)
|
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v = count
|
v = int64(count)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -11,7 +11,7 @@ func newContextTerm(tk *Token) (inst *term) {
|
|||||||
tk: *tk,
|
tk: *tk,
|
||||||
children: make([]*term, 0, 1),
|
children: make([]*term, 0, 1),
|
||||||
position: posPrefix,
|
position: posPrefix,
|
||||||
priority: priPrePost,
|
priority: priIncDec,
|
||||||
evalFunc: evalContextValue,
|
evalFunc: evalContextValue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,12 +31,20 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sourceCtx != nil {
|
if sourceCtx != nil {
|
||||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
if formatter, ok := ctx.(Formatter); ok {
|
||||||
d := make(map[string]any)
|
v = formatter.ToString(0)
|
||||||
for _, key := range keys {
|
} else {
|
||||||
d[key], _ = sourceCtx.GetVar(key)
|
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||||
|
d := make(map[string]any)
|
||||||
|
for _, key := range keys {
|
||||||
|
d[key], _ = sourceCtx.GetVar(key)
|
||||||
|
}
|
||||||
|
keys = sourceCtx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
for _, key := range keys {
|
||||||
|
d[key], _ = sourceCtx.GetFuncInfo(key)
|
||||||
|
}
|
||||||
|
v = d
|
||||||
}
|
}
|
||||||
v = d
|
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = self.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-30
@@ -17,50 +17,78 @@ func newDotTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
|
||||||
|
var v int
|
||||||
|
var indexValue any
|
||||||
|
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||||
|
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
|
||||||
|
if v >= 0 && v < maxValue {
|
||||||
|
index = v
|
||||||
|
} else if index >= -maxValue {
|
||||||
|
index = maxValue + v
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if err = self.checkOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
indexTerm := self.children[1]
|
indexTerm := self.children[1]
|
||||||
|
|
||||||
if isList(leftValue) {
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case *ListType:
|
||||||
var index int
|
var index int
|
||||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
array := ([]any)(*unboxedValue)
|
||||||
return
|
if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil {
|
||||||
|
v = array[index]
|
||||||
}
|
}
|
||||||
|
case string:
|
||||||
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
|
var index int
|
||||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||||
return
|
v = unboxedValue[index]
|
||||||
}
|
}
|
||||||
|
case map[any]any:
|
||||||
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
|
var ok bool
|
||||||
d, _ := leftValue.(map[any]any)
|
var indexValue any
|
||||||
if v, ok = d[rightValue]; !ok {
|
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
if v, ok = unboxedValue[indexValue]; !ok {
|
||||||
|
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
// case *dataCursor:
|
||||||
|
// if indexTerm.symbol() == SymIdentifier {
|
||||||
|
// opName := indexTerm.source()
|
||||||
|
// if opName == resetName {
|
||||||
|
// _, err = unboxedValue.Reset()
|
||||||
|
// } else if opName == cleanName {
|
||||||
|
// _, err = unboxedValue.Clean()
|
||||||
|
// } else {
|
||||||
|
// err = indexTerm.Errorf("iterators do not support command %q", opName)
|
||||||
|
// }
|
||||||
|
// v = err == nil
|
||||||
|
// }
|
||||||
|
case ExtIterator:
|
||||||
|
if indexTerm.symbol() == SymIdentifier {
|
||||||
|
opName := indexTerm.source()
|
||||||
|
if unboxedValue.HasOperation(opName) {
|
||||||
|
v, err = unboxedValue.CallOperation(opName, []any{})
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
|
||||||
|
v = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isInteger(leftValue) {
|
if IsInteger(leftValue) {
|
||||||
if i, _ := leftValue.(int64); i >= 0 {
|
if i, _ := leftValue.(int64); i >= 0 {
|
||||||
f := int64(1)
|
f := int64(1)
|
||||||
for k := int64(1); k <= i; k++ {
|
for k := int64(1); k <= i; k++ {
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-fraction.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fraction struct {
|
||||||
|
num, den int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFraction(num, den int64) *fraction {
|
||||||
|
/* if den < 0 {
|
||||||
|
den = -den
|
||||||
|
num = -num
|
||||||
|
}*/
|
||||||
|
num, den = simplifyIntegers(num, den)
|
||||||
|
return &fraction{num, den}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fraction) toFloat() float64 {
|
||||||
|
return float64(f.num) / float64(f.den)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fraction) String() string {
|
||||||
|
return f.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fraction) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if opt&MultiLine == 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
|
||||||
|
} else {
|
||||||
|
var s, num string
|
||||||
|
if f.num < 0 && opt&TTY == 0 {
|
||||||
|
num = strconv.FormatInt(-f.num, 10)
|
||||||
|
s = "-"
|
||||||
|
} else {
|
||||||
|
num = strconv.FormatInt(f.num, 10)
|
||||||
|
}
|
||||||
|
den := strconv.FormatInt(f.den, 10)
|
||||||
|
size := max(len(num), len(den))
|
||||||
|
if opt&TTY != 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
|
||||||
|
} else {
|
||||||
|
if len(s) > 0 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
if len(s) > 0 {
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("-", size))
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
if len(s) > 0 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- fraction term
|
||||||
|
func newFractionTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priFraction,
|
||||||
|
evalFunc: evalFraction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval func
|
||||||
|
func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var numValue, denValue any
|
||||||
|
var num, den int64
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if num, ok = numValue.(int64); !ok {
|
||||||
|
err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if den, ok = denValue.(int64); !ok {
|
||||||
|
err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if den == 0 {
|
||||||
|
err = errors.New("division by zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if den < 0 {
|
||||||
|
den = -den
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
g := gcd(num, den)
|
||||||
|
num = num / g
|
||||||
|
den = den / g
|
||||||
|
if den == 1 {
|
||||||
|
v = num
|
||||||
|
} else {
|
||||||
|
v = &fraction{num, den}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func gcd(a, b int64) (g int64) {
|
||||||
|
if a < 0 {
|
||||||
|
a = -a
|
||||||
|
}
|
||||||
|
if b < 0 {
|
||||||
|
b = -b
|
||||||
|
}
|
||||||
|
if a < b {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
r := a % b
|
||||||
|
for r > 0 {
|
||||||
|
a, b = b, r
|
||||||
|
r = a % b
|
||||||
|
}
|
||||||
|
g = b
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lcm(a, b int64) (l int64) {
|
||||||
|
g := gcd(a, b)
|
||||||
|
l = a * b / g
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sumFract(f1, f2 *fraction) (sum *fraction) {
|
||||||
|
m := lcm(f1.den, f2.den)
|
||||||
|
sum = newFraction(f1.num*(m/f1.den) + f2.num*(m/f2.den), m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mulFract(f1, f2 *fraction) (prod *fraction) {
|
||||||
|
prod = newFraction(f1.num * f2.num, f1.den * f2.den)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyToFract(v any) (f *fraction, err error) {
|
||||||
|
var ok bool
|
||||||
|
if f, ok = v.(*fraction); !ok {
|
||||||
|
if n, ok := v.(int64); ok {
|
||||||
|
f = intToFraction(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
err = errExpectedGot("fract", typeFraction, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
|
||||||
|
if f1, err = anyToFract(v1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f2, err = anyToFract(v2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sumAnyFract(af1, af2 any) (sum any, err error) {
|
||||||
|
var f1, f2 *fraction
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := sumFract(f1, f2)
|
||||||
|
if f.num == 0 {
|
||||||
|
sum = 0
|
||||||
|
} else {
|
||||||
|
sum = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subAnyFract(af1, af2 any) (sum any, err error) {
|
||||||
|
var f1, f2 *fraction
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f2.num = -f2.num
|
||||||
|
f := sumFract(f1, f2)
|
||||||
|
if f.num == 0 {
|
||||||
|
sum = 0
|
||||||
|
} else {
|
||||||
|
sum = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mulAnyFract(af1, af2 any) (prod any, err error) {
|
||||||
|
var f1, f2 *fraction
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f1.num == 0 || f2.num == 0 {
|
||||||
|
prod = 0
|
||||||
|
} else {
|
||||||
|
f := &fraction{f1.num * f2.num, f1.den * f2.den}
|
||||||
|
prod = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func divAnyFract(af1, af2 any) (quot any, err error) {
|
||||||
|
var f1, f2 *fraction
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f2.num == 0 {
|
||||||
|
err = errors.New("division by zero")
|
||||||
|
return
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f1.num == 0 || f2.den == 0 {
|
||||||
|
quot = 0
|
||||||
|
} else {
|
||||||
|
f := &fraction{f1.num * f2.den, f1.den * f2.num}
|
||||||
|
quot = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func simplifyFraction(f *fraction) (v any) {
|
||||||
|
f.num, f.den = simplifyIntegers(f.num, f.den)
|
||||||
|
if f.den == 1 {
|
||||||
|
v = f.num
|
||||||
|
} else {
|
||||||
|
v = &fraction{f.num, f.den}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func simplifyIntegers(num, den int64) (a, b int64) {
|
||||||
|
if num == 0 {
|
||||||
|
return 0, 1
|
||||||
|
}
|
||||||
|
if den == 0 {
|
||||||
|
panic("fraction with denominator == 0")
|
||||||
|
}
|
||||||
|
if den < 0 {
|
||||||
|
den = -den
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
g := gcd(num, den)
|
||||||
|
a = num / g
|
||||||
|
b = den / g
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToFraction(n int64) *fraction {
|
||||||
|
return &fraction{n, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFraction(v any) (ok bool) {
|
||||||
|
_, ok = v.(*fraction)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymVertBar, newFractionTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-include.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- include term
|
||||||
|
|
||||||
|
func newIncludeTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priSign,
|
||||||
|
evalFunc: evalInclude,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalInclude(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
|
||||||
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
if IsList(childValue) {
|
||||||
|
list, _ := childValue.([]any)
|
||||||
|
for i, filePathSpec := range list {
|
||||||
|
if filePath, ok := filePathSpec.(string); ok {
|
||||||
|
if v, err = EvalFile(ctx, filePath); err == nil {
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
err = self.Errorf("can't load file %q", filePath)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = self.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if IsString(childValue) {
|
||||||
|
filePath, _ := childValue.(string)
|
||||||
|
v, err = EvalFile(ctx, filePath)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = count
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwInclude, newIncludeTerm)
|
||||||
|
}
|
||||||
+8
-6
@@ -33,9 +33,10 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isList(rightValue) {
|
if IsList(rightValue) {
|
||||||
list, _ := rightValue.([]any)
|
list, _ := rightValue.(*ListType)
|
||||||
v = append([]any{leftValue}, list...)
|
newList := append(ListType{leftValue}, *list...)
|
||||||
|
v = &newList
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -49,9 +50,10 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isList(leftValue) {
|
if IsList(leftValue) {
|
||||||
list, _ := leftValue.([]any)
|
list, _ := leftValue.(*ListType)
|
||||||
v = append(list, rightValue)
|
newList := append(*list, rightValue)
|
||||||
|
v = &newList
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
// operator-iter-value.go
|
// operator-iter-value.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
|
||||||
//-------- iter value term
|
//-------- iter value term
|
||||||
|
|
||||||
func newIterValueTerm(tk *Token) (inst *term) {
|
func newIterValueTerm(tk *Token) (inst *term) {
|
||||||
@@ -18,16 +17,16 @@ func newIterValueTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
|
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue any
|
var childValue any
|
||||||
|
|
||||||
if leftValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if dc, ok := leftValue.(*dataCursor); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
v, err = dc.Current()
|
v, err = it.Current()
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(leftValue)
|
err = self.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-11
@@ -17,22 +17,27 @@ func newLengthTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var rightValue any
|
var childValue any
|
||||||
|
|
||||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isList(rightValue) {
|
if IsList(childValue) {
|
||||||
list, _ := rightValue.([]any)
|
list, _ := childValue.([]any)
|
||||||
v = len(list)
|
v = int64(len(list))
|
||||||
} else if isString(rightValue) {
|
} else if IsString(childValue) {
|
||||||
s, _ := rightValue.(string)
|
s, _ := childValue.(string)
|
||||||
v = len(s)
|
v = int64(len(s))
|
||||||
} else if it, ok := rightValue.(Iterator); ok {
|
} else if it, ok := childValue.(Iterator); ok {
|
||||||
v = it.Index()
|
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
||||||
|
count, _ := extIt.CallOperation(countName, nil)
|
||||||
|
v, _ = toInt(count, "")
|
||||||
|
} else {
|
||||||
|
v = int64(it.Index() + 1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
err = self.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,25 +12,25 @@ func newPostIncTerm(tk *Token) *term {
|
|||||||
parent: nil,
|
parent: nil,
|
||||||
children: make([]*term, 0, 1),
|
children: make([]*term, 0, 1),
|
||||||
position: posPostfix,
|
position: posPostfix,
|
||||||
priority: priPrePost,
|
priority: priIncDec,
|
||||||
evalFunc: evalPostInc,
|
evalFunc: evalPostInc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue any
|
var childValue any
|
||||||
if leftValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if dc, ok := leftValue.(*dataCursor); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
v, err = dc.Next()
|
v, err = it.Next()
|
||||||
} else if isInteger(leftValue) && self.children[0].symbol() == SymIdentifier {
|
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
||||||
v = leftValue
|
v = childValue
|
||||||
i, _ := leftValue.(int64)
|
i, _ := childValue.(int64)
|
||||||
ctx.SetVar(self.children[0].source(), i+1)
|
ctx.SetVar(self.children[0].source(), i+1)
|
||||||
} else {
|
} else {
|
||||||
self.errIncompatibleType(leftValue)
|
err = self.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-7
@@ -30,13 +30,15 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isString(leftValue) && isInteger(rightValue) {
|
if IsString(leftValue) && IsInteger(rightValue) {
|
||||||
s, _ := leftValue.(string)
|
s, _ := leftValue.(string)
|
||||||
n, _ := rightValue.(int64)
|
n, _ := rightValue.(int64)
|
||||||
v = strings.Repeat(s, int(n))
|
v = strings.Repeat(s, int(n))
|
||||||
} else if isNumber(leftValue) && isNumber(rightValue) {
|
} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) * numAsFloat(rightValue)
|
v = numAsFloat(leftValue) * numAsFloat(rightValue)
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
v, err = mulAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
@@ -69,14 +71,16 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
d := numAsFloat(rightValue)
|
d := numAsFloat(rightValue)
|
||||||
if d == 0.0 {
|
if d == 0.0 {
|
||||||
err = errors.New("division by zero")
|
err = errors.New("division by zero")
|
||||||
} else {
|
} else {
|
||||||
v = numAsFloat(leftValue) / d
|
v = numAsFloat(leftValue) / d
|
||||||
}
|
}
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
v, err = divAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
||||||
@@ -110,7 +114,7 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
d := numAsFloat(rightValue)
|
d := numAsFloat(rightValue)
|
||||||
if d == 0.0 {
|
if d == 0.0 {
|
||||||
err = errors.New("division by zero")
|
err = errors.New("division by zero")
|
||||||
@@ -144,7 +148,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
if rightInt == 0 {
|
if rightInt == 0 {
|
||||||
err = errors.New("division by zero")
|
err = errors.New("division by zero")
|
||||||
|
|||||||
+9
-9
@@ -25,15 +25,15 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
li, _ := leftValue.(int64)
|
li, _ := leftValue.(int64)
|
||||||
ri, _ := rightValue.(int64)
|
ri, _ := rightValue.(int64)
|
||||||
v = li == ri
|
v = li == ri
|
||||||
} else {
|
} else {
|
||||||
v = numAsFloat(leftValue) == numAsFloat(rightValue)
|
v = numAsFloat(leftValue) == numAsFloat(rightValue)
|
||||||
}
|
}
|
||||||
} else if isString(leftValue) && isString(rightValue) {
|
} else if IsString(leftValue) && IsString(rightValue) {
|
||||||
ls, _ := leftValue.(string)
|
ls, _ := leftValue.(string)
|
||||||
rs, _ := rightValue.(string)
|
rs, _ := rightValue.(string)
|
||||||
v = ls == rs
|
v = ls == rs
|
||||||
@@ -111,15 +111,15 @@ func evalLess(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
li, _ := leftValue.(int64)
|
li, _ := leftValue.(int64)
|
||||||
ri, _ := rightValue.(int64)
|
ri, _ := rightValue.(int64)
|
||||||
v = li < ri
|
v = li < ri
|
||||||
} else {
|
} else {
|
||||||
v = numAsFloat(leftValue) < numAsFloat(rightValue)
|
v = numAsFloat(leftValue) < numAsFloat(rightValue)
|
||||||
}
|
}
|
||||||
} else if isString(leftValue) && isString(rightValue) {
|
} else if IsString(leftValue) && IsString(rightValue) {
|
||||||
ls, _ := leftValue.(string)
|
ls, _ := leftValue.(string)
|
||||||
rs, _ := rightValue.(string)
|
rs, _ := rightValue.(string)
|
||||||
v = ls < rs
|
v = ls < rs
|
||||||
@@ -148,15 +148,15 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
li, _ := leftValue.(int64)
|
li, _ := leftValue.(int64)
|
||||||
ri, _ := rightValue.(int64)
|
ri, _ := rightValue.(int64)
|
||||||
v = li <= ri
|
v = li <= ri
|
||||||
} else {
|
} else {
|
||||||
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
|
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
|
||||||
}
|
}
|
||||||
} else if isString(leftValue) && isString(rightValue) {
|
} else if IsString(leftValue) && IsString(rightValue) {
|
||||||
ls, _ := leftValue.(string)
|
ls, _ := leftValue.(string)
|
||||||
rs, _ := rightValue.(string)
|
rs, _ := rightValue.(string)
|
||||||
v = ls <= rs
|
v = ls <= rs
|
||||||
|
|||||||
+2
-2
@@ -35,14 +35,14 @@ func evalSign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFloat(rightValue) {
|
if IsFloat(rightValue) {
|
||||||
if self.tk.Sym == SymChangeSign {
|
if self.tk.Sym == SymChangeSign {
|
||||||
f, _ := rightValue.(float64)
|
f, _ := rightValue.(float64)
|
||||||
v = -f
|
v = -f
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
}
|
}
|
||||||
} else if isInteger(rightValue) {
|
} else if IsInteger(rightValue) {
|
||||||
if self.tk.Sym == SymChangeSign {
|
if self.tk.Sym == SymChangeSign {
|
||||||
i, _ := rightValue.(int64)
|
i, _ := rightValue.(int64)
|
||||||
v = -i
|
v = -i
|
||||||
|
|||||||
+30
-22
@@ -28,33 +28,39 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
|
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
|
||||||
v = fmt.Sprintf("%v%v", leftValue, rightValue)
|
v = fmt.Sprintf("%v%v", leftValue, rightValue)
|
||||||
} else if isNumber(leftValue) && isNumber(rightValue) {
|
} else if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
v = leftInt + rightInt
|
v = leftInt + rightInt
|
||||||
}
|
}
|
||||||
} else if isList(leftValue) || isList(rightValue) {
|
} else if IsList(leftValue) || IsList(rightValue) {
|
||||||
var leftList, rightList []any
|
var leftList, rightList *ListType
|
||||||
var ok bool
|
var ok bool
|
||||||
if leftList, ok = leftValue.([]any); !ok {
|
if leftList, ok = leftValue.(*ListType); !ok {
|
||||||
leftList = []any{leftValue}
|
leftList = &ListType{leftValue}
|
||||||
}
|
}
|
||||||
if rightList, ok = rightValue.([]any); !ok {
|
if rightList, ok = rightValue.(*ListType); !ok {
|
||||||
rightList = []any{rightValue}
|
rightList = &ListType{rightValue}
|
||||||
}
|
}
|
||||||
sumList := make([]any, 0, len(leftList)+len(rightList))
|
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||||
for _, item := range leftList {
|
for _, item := range *leftList {
|
||||||
sumList = append(sumList, item)
|
sumList = append(sumList, item)
|
||||||
}
|
}
|
||||||
for _, item := range rightList {
|
for _, item := range *rightList {
|
||||||
sumList = append(sumList, item)
|
sumList = append(sumList, item)
|
||||||
}
|
}
|
||||||
v = sumList
|
v = &sumList
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
|
} else {
|
||||||
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -80,24 +86,26 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) - numAsFloat(rightValue)
|
v = numAsFloat(leftValue) - numAsFloat(rightValue)
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
v, err = subAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
v = leftInt - rightInt
|
v = leftInt - rightInt
|
||||||
}
|
}
|
||||||
} else if isList(leftValue) && isList(rightValue) {
|
} else if IsList(leftValue) && IsList(rightValue) {
|
||||||
leftList, _ := leftValue.([]any)
|
leftList, _ := leftValue.(*ListType)
|
||||||
rightList, _ := rightValue.([]any)
|
rightList, _ := rightValue.(*ListType)
|
||||||
diffList := make([]any, 0, len(leftList)-len(rightList))
|
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
|
||||||
for _, item := range leftList {
|
for _, item := range *leftList {
|
||||||
if slices.Index(rightList, item) < 0 {
|
if slices.Index(*rightList, item) < 0 {
|
||||||
diffList = append(diffList, item)
|
diffList = append(diffList, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v = diffList
|
v = &diffList
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,30 +62,45 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
|
|||||||
// // 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 {
|
||||||
|
// err = tk.ErrorExpectedGotString("param-name", subTree.root.String())
|
||||||
|
// }
|
||||||
|
// } else if itemExpected {
|
||||||
|
// prev := scanner.Previous()
|
||||||
|
// err = prev.ErrorExpectedGot("function-param")
|
||||||
|
// break
|
||||||
|
// }
|
||||||
// } else {
|
// } else {
|
||||||
// err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
|
||||||
// break
|
// break
|
||||||
// }
|
// }
|
||||||
// tk = scanner.Next()
|
// lastSym = scanner.Previous().Sym
|
||||||
|
// itemExpected = lastSym == SymComma
|
||||||
// }
|
// }
|
||||||
// if err == nil && tk.Sym != SymClosedRound {
|
|
||||||
// err = tk.Errorf("unterminate function params list")
|
// if err == nil && lastSym != SymClosedRound {
|
||||||
|
// err = tk.ErrorExpectedGot(")")
|
||||||
// }
|
// }
|
||||||
// if err == nil {
|
// if err == nil {
|
||||||
// tk = scanner.Next()
|
// tk = scanner.Next()
|
||||||
// if tk.Sym == SymOpenBrace {
|
// if tk.Sym == SymOpenBrace {
|
||||||
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||||
|
// } else {
|
||||||
|
// err = tk.ErrorExpectedGot("{")
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// if err == nil {
|
// if err == nil {
|
||||||
// // TODO Check arguments
|
// // TODO Check arguments
|
||||||
// if scanner.Previous().Sym != SymClosedBrace {
|
// if scanner.Previous().Sym != SymClosedBrace {
|
||||||
// err = scanner.Previous().Errorf("not properly terminated function body")
|
// err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
// } else {
|
// } else {
|
||||||
// tk = scanner.makeValueToken(SymExpression, "", body)
|
// tk = scanner.makeValueToken(SymExpression, "", body)
|
||||||
// tree = newFuncDefTerm(tk, args)
|
// tree = newFuncDefTerm(tk, args)
|
||||||
@@ -102,20 +117,14 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
itemExpected := false
|
itemExpected := false
|
||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
var subTree *ast
|
tk = scanner.Next()
|
||||||
if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
|
if tk.IsSymbol(SymIdentifier) {
|
||||||
if subTree.root != nil {
|
param := newTerm(tk, nil)
|
||||||
if subTree.root.symbol() == SymIdentifier {
|
args = append(args, param)
|
||||||
args = append(args, subTree.root)
|
tk = scanner.Next()
|
||||||
} else {
|
} else if itemExpected {
|
||||||
err = tk.Errorf("exptected identifier, got %q", subTree.root)
|
prev := scanner.Previous()
|
||||||
}
|
err = prev.ErrorExpectedGot("function-param")
|
||||||
} else if itemExpected {
|
|
||||||
prev := scanner.Previous()
|
|
||||||
err = prev.Errorf("expected function parameter, got %q", prev)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lastSym = scanner.Previous().Sym
|
lastSym = scanner.Previous().Sym
|
||||||
@@ -123,18 +132,20 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && lastSym != SymClosedRound {
|
if err == nil && lastSym != SymClosedRound {
|
||||||
err = tk.Errorf("unterminated function parameters list")
|
err = tk.ErrorExpectedGot(")")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
if tk.Sym == SymOpenBrace {
|
if tk.IsSymbol(SymOpenBrace) {
|
||||||
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||||
|
} else {
|
||||||
|
err = tk.ErrorExpectedGot("{")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
// TODO Check arguments
|
||||||
if scanner.Previous().Sym != SymClosedBrace {
|
if scanner.Previous().Sym != SymClosedBrace {
|
||||||
err = scanner.Previous().Errorf("not properly terminated function body")
|
err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
} else {
|
} else {
|
||||||
tk = scanner.makeValueToken(SymExpression, "", body)
|
tk = scanner.makeValueToken(SymExpression, "", body)
|
||||||
tree = newFuncDefTerm(tk, args)
|
tree = newFuncDefTerm(tk, args)
|
||||||
@@ -154,7 +165,7 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
args = append(args, subTree.root)
|
args = append(args, subTree.root)
|
||||||
} else if itemExpected {
|
} else if itemExpected {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.Errorf("expected list item, got %q", prev)
|
err = prev.ErrorExpectedGot("list-item")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -166,7 +177,7 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
// TODO Check arguments
|
||||||
if lastSym != SymClosedSquare {
|
if lastSym != SymClosedSquare {
|
||||||
err = scanner.Previous().Errorf("unterminate items list")
|
err = scanner.Previous().ErrorExpectedGot("]")
|
||||||
} else {
|
} else {
|
||||||
subtree = newListTerm(args)
|
subtree = newListTerm(args)
|
||||||
}
|
}
|
||||||
@@ -175,29 +186,18 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
var ds *term
|
|
||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
dsExpected := true
|
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
||||||
if subTree.root != nil {
|
if subTree.root != nil {
|
||||||
if dsExpected {
|
args = append(args, subTree.root)
|
||||||
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 {
|
} else if itemExpected {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.Errorf("expected iterator argument, got %q", prev)
|
err = prev.ErrorExpectedGot("iterator-param")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -209,11 +209,9 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
// TODO Check arguments
|
||||||
if lastSym != SymClosedRound {
|
if lastSym != SymClosedRound {
|
||||||
err = scanner.Previous().Errorf("unterminate iterator param list")
|
err = scanner.Previous().ErrorExpectedGot(")")
|
||||||
} else if ds != nil {
|
|
||||||
subtree = newIteratorTerm(tk, ds, args)
|
|
||||||
} else {
|
} else {
|
||||||
tk.Errorf("missing data-source param")
|
subtree = newIteratorTerm(tk, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -231,12 +229,13 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
|
|||||||
if tk.Sym == SymInteger || tk.Sym == SymString {
|
if tk.Sym == SymInteger || tk.Sym == SymString {
|
||||||
tkSep := scanner.Next()
|
tkSep := scanner.Next()
|
||||||
if tkSep.Sym != SymColon {
|
if tkSep.Sym != SymColon {
|
||||||
err = tkSep.Errorf("expected \":\", got %q", tkSep)
|
err = tkSep.ErrorExpectedGot(":")
|
||||||
} else {
|
} else {
|
||||||
key = tk.Value
|
key = tk.Value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
||||||
|
err = tk.ErrorExpectedGot("dictionary-key or }")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -254,7 +253,7 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
lastSym = tk.Sym
|
lastSym = tk.Sym
|
||||||
if itemExpected {
|
if itemExpected {
|
||||||
err = tk.Errorf("expected dictionary key, got %q", tk)
|
err = tk.ErrorExpectedGot("dictionary-key")
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -263,7 +262,7 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
args[key] = subTree.root
|
args[key] = subTree.root
|
||||||
} else if key != nil {
|
} else if key != nil {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.Errorf("expected dictionary value, got %q", prev)
|
err = prev.ErrorExpectedGot("dictionary-value")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -275,7 +274,7 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
// TODO Check arguments
|
||||||
if lastSym != SymClosedBrace {
|
if lastSym != SymClosedBrace {
|
||||||
err = scanner.Previous().Errorf("unterminated dictionary")
|
err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
} else {
|
} else {
|
||||||
subtree = newDictTerm(args)
|
subtree = newDictTerm(args)
|
||||||
}
|
}
|
||||||
@@ -309,7 +308,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = tk.Errorf("selector-case expected, got %q", tk.source)
|
err = tk.ErrorExpectedGot("{")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
+124
-231
@@ -6,19 +6,18 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParser(t *testing.T) {
|
type inputType struct {
|
||||||
type inputType struct {
|
source string
|
||||||
source string
|
wantResult any
|
||||||
wantResult any
|
wantErr error
|
||||||
wantErr error
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
func TestGeneralParser(t *testing.T) {
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
||||||
/* 2 */ {`3 == 4`, false, nil},
|
/* 2 */ {`3 == 4`, false, nil},
|
||||||
@@ -52,140 +51,133 @@ func TestParser(t *testing.T) {
|
|||||||
/* 30 */ {"-(-2+1)", int64(1), nil},
|
/* 30 */ {"-(-2+1)", int64(1), nil},
|
||||||
/* 31 */ {"(1+1)*5", int64(10), nil},
|
/* 31 */ {"(1+1)*5", int64(10), nil},
|
||||||
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
|
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
|
||||||
/* 33 */ {`add(1,2,3)`, int64(6), nil},
|
/* 33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
||||||
/* 34 */ {`mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
/* 34 */ {`(((1)))`, int64(1), nil},
|
||||||
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
|
||||||
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
/* 36 */ {`0 || 0.0 && "hello"`, false, nil},
|
||||||
/* 37 */ {`add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
/* 37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
||||||
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
/* 38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||||
/* 39 */ {`(((1)))`, int64(1), nil},
|
/* 39 */ {`false // very simple expression`, false, nil},
|
||||||
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
|
/* 40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||||
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
|
/* 41 */ {"", nil, nil},
|
||||||
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
/* 42 */ {"4!", int64(24), nil},
|
||||||
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
/* 43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||||
/* 44 */ {`false // very simple expression`, false, nil},
|
/* 44 */ {"-4!", int64(-24), nil},
|
||||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
/* 45 */ {"1.5 < 7", true, nil},
|
||||||
/* 46 */ {"", nil, nil},
|
/* 46 */ {"1.5 > 7", false, nil},
|
||||||
/* 47 */ {"4!", int64(24), nil},
|
/* 47 */ {"1.5 <= 7", true, nil},
|
||||||
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
/* 48 */ {"1.5 >= 7", false, nil},
|
||||||
/* 49 */ {"-4!", int64(-24), nil},
|
/* 49 */ {"1.5 != 7", true, nil},
|
||||||
/* 50 */ {"1.5 < 7", true, nil},
|
/* 50 */ {"1.5 == 7", false, nil},
|
||||||
/* 51 */ {"1.5 > 7", false, nil},
|
/* 51 */ {`"1.5" < "7"`, true, nil},
|
||||||
/* 52 */ {"1.5 <= 7", true, nil},
|
/* 52 */ {`"1.5" > "7"`, false, nil},
|
||||||
/* 53 */ {"1.5 >= 7", false, nil},
|
/* 53 */ {`"1.5" == "7"`, false, nil},
|
||||||
/* 54 */ {"1.5 != 7", true, nil},
|
/* 54 */ {`"1.5" != "7"`, true, nil},
|
||||||
/* 55 */ {"1.5 == 7", false, nil},
|
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
||||||
/* 56 */ {`"1.5" < "7"`, true, nil},
|
/* 56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
||||||
/* 57 */ {`"1.5" > "7"`, false, nil},
|
/* 57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
||||||
/* 58 */ {`"1.5" == "7"`, false, nil},
|
/* 58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
||||||
/* 59 */ {`"1.5" != "7"`, true, nil},
|
/* 59 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
/* 60 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
|
||||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
/* 61 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
||||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
/* 62 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
||||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
/* 63 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
||||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
|
/* 64 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
|
/* 65 */ {"+1.5", float64(1.5), nil},
|
||||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
||||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
||||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
|
||||||
/* 70 */ {"+1.5", float64(1.5), nil},
|
/* 70 */ {`123`, int64(123), nil},
|
||||||
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
/* 71 */ {`1.`, float64(1.0), nil},
|
||||||
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
/* 72 */ {`1.E-2`, float64(0.01), nil},
|
||||||
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
/* 73 */ {`1E2`, float64(100), nil},
|
||||||
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
|
/* 74 */ {`1 / 2`, int64(0), nil},
|
||||||
/* 75 */ {`123`, int64(123), nil},
|
/* 75 */ {`1.0 / 2`, float64(0.5), nil},
|
||||||
/* 76 */ {`1.`, float64(1.0), nil},
|
/* 76 */ {`1 ./ 2`, float64(0.5), nil},
|
||||||
/* 77 */ {`1.E-2`, float64(0.01), nil},
|
/* 77 */ {`5 % 2`, int64(1), nil},
|
||||||
/* 78 */ {`1E2`, float64(100), nil},
|
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
||||||
/* 79 */ {`1 / 2`, int64(0), nil},
|
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
||||||
/* 80 */ {`1.0 / 2`, float64(0.5), nil},
|
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
||||||
/* 81 */ {`1 ./ 2`, float64(0.5), nil},
|
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||||
/* 82 */ {`5 % 2`, int64(1), nil},
|
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||||
/* 83 */ {`5 % (-2)`, int64(1), nil},
|
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
||||||
/* 84 */ {`-5 % 2`, int64(-1), nil},
|
/* 84 */ {`~ 2 > 1`, false, nil},
|
||||||
/* 85 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
/* 85 */ {`~ true && true`, false, nil},
|
||||||
/* 86 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
/* 86 */ {`~ false || true`, true, nil},
|
||||||
/* 87 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
/* 87 */ {`false but true`, true, nil},
|
||||||
/* 88 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
/* 88 */ {`2+3 but 5*2`, int64(10), nil},
|
||||||
/* 89 */ {`~ 2 > 1`, false, nil},
|
/* 89 */ {`x=2`, int64(2), nil},
|
||||||
/* 90 */ {`~ true && true`, false, nil},
|
/* 90 */ {`x=2 but x*10`, int64(20), nil},
|
||||||
/* 91 */ {`~ false || true`, true, nil},
|
/* 91 */ {`false and true`, false, nil},
|
||||||
/* 92 */ {`false but true`, true, nil},
|
/* 92 */ {`false and (x==2)`, false, nil},
|
||||||
/* 93 */ {`2+3 but 5*2`, int64(10), nil},
|
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
/* 94 */ {`add(1,2) but var2`, "abc", nil},
|
/* 94 */ {`false or true`, true, nil},
|
||||||
/* 95 */ {`x=2`, int64(2), nil},
|
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
/* 96 */ {`x=2 but x*10`, int64(20), nil},
|
/* 96 */ {`a=5; a`, int64(5), nil},
|
||||||
/* 97 */ {`false and true`, false, nil},
|
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||||
/* 98 */ {`false and (x==2)`, false, nil},
|
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
||||||
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
/* 99 */ {`2+(a=5)`, int64(7), nil},
|
||||||
/* 100 */ {`false or true`, true, nil},
|
/* 100 */ {`x ?? "default"`, "default", nil},
|
||||||
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||||
/* 102 */ {`a=5; a`, int64(5), nil},
|
/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
||||||
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
|
/* 103 */ {`x ?= "default"; x`, "default", nil},
|
||||||
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
||||||
/* 105 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
||||||
/* 106 */ {`2+(a=5)`, int64(7), nil},
|
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
||||||
/* 107 */ {`two=func(){2}; two()`, int64(2), nil},
|
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
||||||
/* 108 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
||||||
/* 109 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
||||||
/* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
||||||
/* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
||||||
/* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
||||||
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||||
/* 114 */ {`x ?? "default"`, "default", nil},
|
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
||||||
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
/* 115 */ {`nil`, nil, nil},
|
||||||
/* 116 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||||
/* 117 */ {`x ?= "default"; x`, "default", nil},
|
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||||
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||||
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
/* 119 */ {`{}`, map[any]any{}, nil},
|
||||||
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
||||||
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||||
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||||
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||||
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||||
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||||
/* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
|
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
|
||||||
/* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
|
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
||||||
/* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
||||||
/* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
||||||
/* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
||||||
/* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||||
/* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
|
||||||
/* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||||
/* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||||
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||||
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||||
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
/* 137 */ {`builtin "os.file"`, int64(1), nil},
|
||||||
/* 138 */ {`nil`, nil, nil},
|
/* 138 */ {`v=10; v++; v`, int64(11), nil},
|
||||||
/* 139 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
|
||||||
/* 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
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
parserTest(t, "General", inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||||
|
|
||||||
succeeded := 0
|
succeeded := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 140 */ {`ds={}; $(ds)`, nil, nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
var expr Expr
|
var expr Expr
|
||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleFuncStore()
|
||||||
ctx.SetVar("var1", int64(123))
|
|
||||||
ctx.SetVar("var2", "abc")
|
|
||||||
ImportMathFuncs(ctx)
|
|
||||||
ImportImportFuncs(ctx)
|
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, "Iterator", input.source, input.wantResult, input.wantErr)
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
r := strings.NewReader(input.source)
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
@@ -213,114 +205,15 @@ func TestParser(t *testing.T) {
|
|||||||
succeeded++
|
succeeded++
|
||||||
} else {
|
} else {
|
||||||
failed++
|
failed++
|
||||||
if i+1 == check_env_expr_path {
|
|
||||||
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListParser(t *testing.T) {
|
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`[]`, []any{}, nil},
|
|
||||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
|
||||||
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
|
||||||
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
|
||||||
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
|
||||||
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
|
||||||
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
|
||||||
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
|
||||||
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
|
||||||
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 has wrong type string, number expected`)},
|
|
||||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
|
||||||
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
|
||||||
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr *ast
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
ctx.SetVar("var1", int64(123))
|
|
||||||
ctx.SetVar("var2", "abc")
|
|
||||||
ImportMathFuncs(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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotList, okGot := gotResult.([]any); okGot {
|
|
||||||
if wantList, okWant := input.wantResult.([]any); okWant {
|
|
||||||
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
} else {
|
|
||||||
equal := len(gotList) == len(wantList)
|
|
||||||
if equal {
|
|
||||||
for i, gotItem := range gotList {
|
|
||||||
wantItem := wantList[i]
|
|
||||||
equal = gotItem == wantItem
|
|
||||||
if !equal {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !equal {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
func logTest(t *testing.T, n int, source string, wantResult any, wantErr error) {
|
|
||||||
if wantErr == nil {
|
if wantErr == nil {
|
||||||
t.Log(fmt.Sprintf("[+]Test nr %3d -- %q --> %v", n, source, wantResult))
|
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||||
} else {
|
} else {
|
||||||
t.Log(fmt.Sprintf("[-]Test nr %3d -- %q --> %v", n, source, wantErr))
|
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,12 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
//} else if next == '/' {
|
//} else if next == '/' {
|
||||||
if next, _ := self.peek(); next == '/' {
|
if next, _ := self.peek(); next == '/' {
|
||||||
tk = self.moveOn(SymDotSlash, ch, next)
|
tk = self.moveOn(SymDotSlash, ch, next)
|
||||||
|
} else if next == '.' {
|
||||||
|
if next1, _ := self.peek(); next1 == '.' {
|
||||||
|
tk = self.moveOn(SymTripleDot, ch, next, next1)
|
||||||
|
} else {
|
||||||
|
tk = self.moveOn(SymDoubleDot, ch, next)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymDot, ch)
|
tk = self.makeToken(SymDot, ch)
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-3
@@ -4,7 +4,10 @@
|
|||||||
// simple-func-store.go
|
// simple-func-store.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type SimpleFuncStore struct {
|
type SimpleFuncStore struct {
|
||||||
SimpleVarStore
|
SimpleVarStore
|
||||||
@@ -18,6 +21,29 @@ type funcInfo struct {
|
|||||||
functor Functor
|
functor Functor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
var i int
|
||||||
|
sb.WriteString("func(")
|
||||||
|
for i = 0; i < info.minArgs; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||||
|
}
|
||||||
|
for ; i < info.maxArgs; i++ {
|
||||||
|
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||||
|
}
|
||||||
|
if info.maxArgs < 0 {
|
||||||
|
if info.minArgs > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString("...")
|
||||||
|
}
|
||||||
|
sb.WriteString(") {...}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (info *funcInfo) Name() string {
|
func (info *funcInfo) Name() string {
|
||||||
return info.name
|
return info.name
|
||||||
}
|
}
|
||||||
@@ -44,12 +70,44 @@ func NewSimpleFuncStore() *SimpleFuncStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||||
|
svs := ctx.SimpleVarStore
|
||||||
return &SimpleFuncStore{
|
return &SimpleFuncStore{
|
||||||
SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||||
funcStore: CloneMap(ctx.funcStore),
|
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||||
|
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("funcs: {\n")
|
||||||
|
first := true
|
||||||
|
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
value, _ := ctx.GetFuncInfo(name)
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
sb.WriteString(name)
|
||||||
|
sb.WriteString("=")
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(0))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
||||||
|
funcsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||||
info, exists = ctx.funcStore[name]
|
info, exists = ctx.funcStore[name]
|
||||||
return
|
return
|
||||||
|
|||||||
+53
-2
@@ -4,7 +4,10 @@
|
|||||||
// simple-var-store.go
|
// simple-var-store.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type SimpleVarStore struct {
|
type SimpleVarStore struct {
|
||||||
varStore map[string]any
|
varStore map[string]any
|
||||||
@@ -16,9 +19,14 @@ func NewSimpleVarStore() *SimpleVarStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
|
||||||
|
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
||||||
|
// fmt.Println("*** Cloning context ***")
|
||||||
clone = &SimpleVarStore{
|
clone = &SimpleVarStore{
|
||||||
varStore: CloneMap(ctx.varStore),
|
varStore: ctx.cloneVars(),
|
||||||
}
|
}
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
@@ -29,10 +37,12 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
ctx.varStore[varName] = value
|
ctx.varStore[varName] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||||
if allowedValue, ok := fromGenericAny(value); ok {
|
if allowedValue, ok := fromGenericAny(value); ok {
|
||||||
ctx.varStore[varName] = allowedValue
|
ctx.varStore[varName] = allowedValue
|
||||||
} else {
|
} else {
|
||||||
@@ -68,3 +78,44 @@ func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, m
|
|||||||
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("vars: {\n")
|
||||||
|
first := true
|
||||||
|
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
value, _ := ctx.GetVar(name)
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
sb.WriteString(name)
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(f.ToString(0))
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
sb.WriteString("func(){}")
|
||||||
|
} else if _, ok = value.(map[any]any); ok {
|
||||||
|
sb.WriteString("dict{}")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func varsCtxToString(ctx ExprContext, indent int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
varsCtxToBuilder(&sb, ctx, indent)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
varsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const (
|
|||||||
SymBackTick // 22: '`'
|
SymBackTick // 22: '`'
|
||||||
SymExclamation // 23: '!'
|
SymExclamation // 23: '!'
|
||||||
SymQuestion // 24: '?'
|
SymQuestion // 24: '?'
|
||||||
SymAmpersand // 25: '&&'
|
SymAmpersand // 25: '&'
|
||||||
SymDoubleAmpersand // 26: '&&'
|
SymDoubleAmpersand // 26: '&&'
|
||||||
SymPercent // 27: '%'
|
SymPercent // 27: '%'
|
||||||
SymAt // 28: '@'
|
SymAt // 28: '@'
|
||||||
@@ -64,7 +64,9 @@ const (
|
|||||||
SymCaret // 53: '^'
|
SymCaret // 53: '^'
|
||||||
SymDollarRound // 54: '$('
|
SymDollarRound // 54: '$('
|
||||||
SymOpenClosedRound // 55: '()'
|
SymOpenClosedRound // 55: '()'
|
||||||
SymDoubleDollar // 56: '$$
|
SymDoubleDollar // 56: '$$'
|
||||||
|
SymDoubleDot // 57: '..'
|
||||||
|
SymTripleDot // 58: '...'
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
@@ -96,6 +98,7 @@ const (
|
|||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
SymKwBuiltin
|
SymKwBuiltin
|
||||||
|
SymKwInclude
|
||||||
SymKwNil
|
SymKwNil
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,6 +111,7 @@ func init() {
|
|||||||
"BUILTIN": SymKwBuiltin,
|
"BUILTIN": SymKwBuiltin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
|
"INCLUDE": SymKwInclude,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
"OR": SymKwOr,
|
"OR": SymKwOr,
|
||||||
"NIL": SymKwNil,
|
"NIL": SymKwNil,
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ const (
|
|||||||
priRelational
|
priRelational
|
||||||
priSum
|
priSum
|
||||||
priProduct
|
priProduct
|
||||||
|
priFraction
|
||||||
priSelector
|
priSelector
|
||||||
priSign
|
priSign
|
||||||
priFact
|
priFact
|
||||||
priIterValue
|
priIterValue
|
||||||
priCoalesce
|
priCoalesce
|
||||||
priPrePost
|
priIncDec
|
||||||
priDot
|
priDot
|
||||||
priValue
|
priValue
|
||||||
)
|
)
|
||||||
@@ -154,7 +155,7 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
|
|||||||
if index64, ok := computedValue.(int64); ok {
|
if index64, ok := computedValue.(int64); ok {
|
||||||
i = int(index64)
|
i = int(index64)
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("%s, got %T", valueDescription, computedValue)
|
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -217,9 +218,9 @@ func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *term) evalPrefix(ctx ExprContext) (rightValue any, err error) {
|
func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
|
||||||
if err = self.checkOperands(); err == nil {
|
if err = self.checkOperands(); err == nil {
|
||||||
rightValue, err = self.children[0].compute(ctx)
|
childValue, err = self.children[0].compute(ctx)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func NewValueToken(row, col int, sym Symbol, source string, value any) *Token {
|
|||||||
|
|
||||||
func NewErrorToken(row, col int, err error) *Token {
|
func NewErrorToken(row, col int, err error) *Token {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return NewToken(row, col, SymEos, "")
|
return NewToken(row, col, SymEos, "<EOF>")
|
||||||
}
|
}
|
||||||
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
|
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,10 @@ func (tk *Token) IsTerm(termSymbols []Symbol) bool {
|
|||||||
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
|
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tk *Token) IsSymbol(sym Symbol) bool {
|
||||||
|
return tk.Sym == sym
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Token) Errorf(template string, args ...any) (err error) {
|
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
|
||||||
@@ -81,3 +85,13 @@ func (self *Token) Errors(msg string) (err error) {
|
|||||||
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
|
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Token) ErrorExpectedGot(symbol string) (err error) {
|
||||||
|
err = fmt.Errorf("[%d:%d] expected %q, got %q", self.row, self.col, symbol, self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Token) ErrorExpectedGotString(symbol, got string) (err error) {
|
||||||
|
err = fmt.Errorf("[%d:%d] expected %q, got %q", self.row, self.col, symbol, got)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
echo >&2 "Usage: $(basename "${0}") <module-name> <func-name>..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODULE_NAME=${1,,}
|
||||||
|
GO_FILE="func-${MODULE_NAME//[-.]/_}.go"
|
||||||
|
|
||||||
|
shift
|
||||||
|
FUNC_LIST=
|
||||||
|
i=0
|
||||||
|
for name; do
|
||||||
|
if [ ${i} -gt 0 ]; then
|
||||||
|
if [ $((i+1)) -eq $# ]; then
|
||||||
|
FUNC_LIST+=" and "
|
||||||
|
else
|
||||||
|
FUNC_LIST+=", "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
FUNC_LIST+="${name}()"
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > "${GO_FILE}" <<EOF
|
||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-${MODULE_NAME}.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// --- Start of function definitions
|
||||||
|
|
||||||
|
$(
|
||||||
|
for name; do
|
||||||
|
cat <<IEOF
|
||||||
|
func ${name}Func(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
IEOF
|
||||||
|
|
||||||
|
done
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- End of function definitions
|
||||||
|
|
||||||
|
// Import above functions in the context
|
||||||
|
func Import${MODULE_NAME^}Funcs(ctx ExprContext) {
|
||||||
|
$(
|
||||||
|
for name; do
|
||||||
|
cat <<IEOF
|
||||||
|
ctx.RegisterFunc("${name}", &simpleFunctor{f: ${name}Func}, 1, -1)
|
||||||
|
IEOF
|
||||||
|
|
||||||
|
done
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the import function in the import-register.
|
||||||
|
// That will allow to import all function of this module by the "builtin" operator."
|
||||||
|
func init() {
|
||||||
|
registerImport("${MODULE_NAME}", Import${name}Funcs, "The \"${MODULE_NAME}\" module implements the ${FUNC_LIST} function(s)")
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
@@ -4,46 +4,67 @@
|
|||||||
// utils.go
|
// utils.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "reflect"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
func isString(v any) (ok bool) {
|
func IsString(v any) (ok bool) {
|
||||||
_, ok = v.(string)
|
_, ok = v.(string)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInteger(v any) (ok bool) {
|
func IsInteger(v any) (ok bool) {
|
||||||
_, ok = v.(int64)
|
_, ok = v.(int64)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func isFloat(v any) (ok bool) {
|
func IsFloat(v any) (ok bool) {
|
||||||
_, ok = v.(float64)
|
_, ok = v.(float64)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func isList(v any) (ok bool) {
|
func IsList(v any) (ok bool) {
|
||||||
_, ok = v.([]any)
|
_, ok = v.(*ListType)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDict(v any) (ok bool) {
|
func IsDict(v any) (ok bool) {
|
||||||
_, ok = v.(map[any]any)
|
_, ok = v.(map[any]any)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNumber(v any) (ok bool) {
|
func IsNumber(v any) (ok bool) {
|
||||||
return isFloat(v) || isInteger(v)
|
return IsFloat(v) || IsInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNumOrFract(v any) (ok bool) {
|
||||||
|
return IsFloat(v) || IsInteger(v) || isFraction(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNumberString(v any) (ok bool) {
|
func isNumberString(v any) (ok bool) {
|
||||||
return isString(v) || isNumber(v)
|
return IsString(v) || IsNumber(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFunctor(v any) (ok bool) {
|
||||||
|
_, ok = v.(Functor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIterator(v any) (ok bool) {
|
||||||
|
_, ok = v.(Iterator)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func numAsFloat(v any) (f float64) {
|
func numAsFloat(v any) (f float64) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if f, ok = v.(float64); !ok {
|
if f, ok = v.(float64); !ok {
|
||||||
i, _ := v.(int64)
|
if fract, ok := v.(*fraction); ok {
|
||||||
f = float64(i)
|
f = fract.toFloat()
|
||||||
|
} else {
|
||||||
|
i, _ := v.(int64)
|
||||||
|
f = float64(i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,3 +157,32 @@ func CloneMap[K comparable, V any](source map[K]V) map[K]V {
|
|||||||
dest := make(map[K]V, len(source))
|
dest := make(map[K]V, len(source))
|
||||||
return CopyMap(dest, source)
|
return CopyMap(dest, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||||
|
// fmt.Printf("--- Clone with filter %p\n", filter)
|
||||||
|
if filter == nil {
|
||||||
|
return CopyMap(dest, source)
|
||||||
|
} else {
|
||||||
|
for k, v := range source {
|
||||||
|
if filter(k) {
|
||||||
|
// fmt.Printf("\tClone var %q\n", k)
|
||||||
|
dest[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||||
|
dest := make(map[K]V, len(source))
|
||||||
|
return CopyFilteredMap(dest, source, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt(value any, description string) (i int, err error) {
|
||||||
|
if valueInt64, ok := value.(int64); ok {
|
||||||
|
i = int(valueInt64)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user