Compare commits
205 Commits
hook-training
...
v0.14.0
| Author | SHA1 | Date | |
|---|---|---|---|
| d84e690ef3 | |||
| 4b25a07699 | |||
| 3736214c5a | |||
| 78cbb7b36f | |||
| 2c87d6bf9e | |||
| 691c213d17 | |||
| fa136cb70b | |||
| 76dd01afcd | |||
| 4283fab816 | |||
| 03d4c192c2 | |||
| e5f63c3d9d | |||
| d545a35acf | |||
| e4275e2cb6 | |||
| 1ff5770264 | |||
| ba32e2dccf | |||
| f22b5a6f0b | |||
| 7c8dbb0ac7 | |||
| e5c5920db0 | |||
| 61efdb4eef | |||
| 82ec78719d | |||
| 554ff1a9dd | |||
| 6bb891e09d | |||
| 1c4ffd7d64 | |||
| b92b19e1dd | |||
| 9967918418 | |||
| 6c14c07d66 | |||
| 9ea170e53b | |||
| a543360151 | |||
| 24a25bbf94 | |||
| d6a1607041 | |||
| 4d43ab2c2f | |||
| 9bd4a0ba23 | |||
| 2b184cf3f2 | |||
| 263e419d9a | |||
| c39970fa7e | |||
| 14bb9e942b | |||
| 9451958218 | |||
| 91fdc1926e | |||
| 9a3abdf1b6 | |||
| ac3e690f87 | |||
| f0a152a17a | |||
| ca89931ca9 | |||
| d2e8aed4f7 | |||
| 8138cd2a80 | |||
| 6786666cf4 | |||
| 35e794701a | |||
| 52ef134be6 | |||
| 624318d84e | |||
| aa1338cd51 | |||
| c9db4b84e3 | |||
| e7e9330b71 | |||
| 8eb25bbc86 | |||
| efc92d434b | |||
| 4151f3f5e2 | |||
| f028485caa | |||
| ab07405cda | |||
| 47be0c66cf | |||
| 50e7168214 | |||
| 0a9543543d | |||
| 775751c67b | |||
| 4aaffd6c44 | |||
| 924051fbcd | |||
| 5a9b6525a2 | |||
| 8c66d90532 | |||
| 4aa0113c6a | |||
| d035fa0d5e | |||
| cf73b5c98d | |||
| 8346e28340 | |||
| 9c2eca40d7 | |||
| c3198e4c79 | |||
| 99e0190b9c | |||
| d4f63a3837 | |||
| 389d48b646 | |||
| 1f7b9131fc | |||
| dce49fd2b7 | |||
| 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 | |||
| 723976b37e | |||
| 361b84f31f | |||
| 70892aa980 | |||
| 10eec286fa | |||
| 894b1884eb | |||
| d2bab5fd9e | |||
| f94f369547 | |||
| 107ec4958f | |||
| a22047e84e | |||
| 80b7d5b988 | |||
| 2ab896bbac | |||
| 62e16219f7 | |||
| 750c660331 | |||
| 7a88449cd1 | |||
| b14dc2f1ee | |||
| d354102c6a | |||
| 761ec868e6 | |||
| 7941c2dfec | |||
| ebb2811ed3 | |||
| 75c0c0f681 | |||
| 268a968548 | |||
| 323308d86f | |||
| b28d6a8f02 | |||
| ab82bcf1ef | |||
| a628bfac39 | |||
| d1122da566 | |||
| 6ae5ca34ed | |||
| 730b59e6d3 | |||
| f198ba47e1 | |||
| 943ef3327e | |||
| 475ef3c80a | |||
| 3c0307524b | |||
| c27e487fc3 | |||
| ed973c9b7b | |||
| 15bbfacd47 | |||
| 04f934ab04 |
@@ -5,7 +5,6 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,10 +60,10 @@ func (self *ast) addToken(tk *Token) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) addToken2(tk *Token) (t *term, err error) {
|
func (self *ast) addToken2(tk *Token) (t *term, err error) {
|
||||||
if t = newTerm(tk, nil); t != nil {
|
if t = newTerm(tk); 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
|
||||||
}
|
}
|
||||||
@@ -84,8 +83,9 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
|
|||||||
if tree.isComplete() {
|
if tree.isComplete() {
|
||||||
var subRoot *term
|
var subRoot *term
|
||||||
last := tree.removeLastChild()
|
last := tree.removeLastChild()
|
||||||
subRoot, err = self.insert(last, node)
|
if subRoot, err = self.insert(last, node); err == nil {
|
||||||
subRoot.setParent(tree)
|
subRoot.setParent(tree)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
node.setParent(tree)
|
node.setParent(tree)
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
if self.forest != nil {
|
if self.forest != nil {
|
||||||
for _, root := range self.forest {
|
for _, root := range self.forest {
|
||||||
if result, err = root.compute(ctx); err == nil {
|
if result, err = root.compute(ctx); err == nil {
|
||||||
ctx.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
} else {
|
} else {
|
||||||
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
||||||
break
|
break
|
||||||
@@ -128,9 +128,10 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
result, err = self.root.compute(ctx)
|
result, err = self.root.compute(ctx)
|
||||||
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
}
|
}
|
||||||
} else {
|
// } else {
|
||||||
err = errors.New("empty expression")
|
// err = errors.New("empty expression")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-errors.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func errTooFewParams(minArgs, maxArgs, argCount int) (err error) {
|
||||||
|
if maxArgs < 0 {
|
||||||
|
err = fmt.Errorf("too few params -- expected %d or more, got %d", minArgs, argCount)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func errTooMuchParams(maxArgs, argCount int) (err error) {
|
||||||
|
err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errDivisionByZero(funcName string) error {
|
||||||
|
return fmt.Errorf("%s() division by zero", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 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,30 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-params.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const (
|
||||||
|
paramCount = "count"
|
||||||
|
paramItem = "item"
|
||||||
|
paramParts = "parts"
|
||||||
|
paramSeparator = "separator"
|
||||||
|
paramSource = "source"
|
||||||
|
paramSuffix = "suffix"
|
||||||
|
paramPrefix = "prefix"
|
||||||
|
paramStart = "start"
|
||||||
|
paramEnd = "end"
|
||||||
|
paramValue = "value"
|
||||||
|
paramEllipsis = "..."
|
||||||
|
typeFilepath = "filepath"
|
||||||
|
typeDirpath = "dirpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// typeInteger = "int"
|
||||||
|
// typeFloat = "float"
|
||||||
|
// typeString = "string"
|
||||||
|
// typeFraction = "fract"
|
||||||
|
// typeList = "list"
|
||||||
|
// typeDict = "dict"
|
||||||
|
// )
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-type-names.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeAny = "any"
|
||||||
|
typeBoolean = "boolean"
|
||||||
|
typeFloat = "float"
|
||||||
|
typeFraction = "fraction"
|
||||||
|
typeHandle = "handle"
|
||||||
|
typeInt = "integer"
|
||||||
|
typeItem = "item"
|
||||||
|
typeNumber = "number"
|
||||||
|
typePair = "pair"
|
||||||
|
typeString = "string"
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// context-helpers.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
func cloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
|
||||||
|
if sourceCtx != nil {
|
||||||
|
clonedCtx = sourceCtx.Clone()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportVar(ctx ExprContext, name string, value any) {
|
||||||
|
if name[0] == '@' {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
ctx.UnsafeSetVar(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||||
|
if name[0] == '@' {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||||
|
// ctx.RegisterFuncInfo(name, info)
|
||||||
|
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
|
exportAll := isEnabled(sourceCtx, control_export_all)
|
||||||
|
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
||||||
|
// Export variables
|
||||||
|
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||||
|
// fmt.Printf("\tExporting %q\n", refName)
|
||||||
|
refValue, _ := sourceCtx.GetVar(refName)
|
||||||
|
exportVar(destCtx, refName, refValue)
|
||||||
|
}
|
||||||
|
// Export functions
|
||||||
|
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||||
|
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
|
||||||
|
exportFunc(destCtx, refName, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-12
@@ -4,28 +4,31 @@
|
|||||||
// context.go
|
// context.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// ---- Function template
|
|
||||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
|
||||||
|
|
||||||
// ---- Functor interface
|
// ---- Functor interface
|
||||||
type Functor interface {
|
type Functor interface {
|
||||||
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
||||||
|
SetFunc(info ExprFunc)
|
||||||
|
GetFunc() ExprFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleFunctor struct {
|
// ---- Function Param Info
|
||||||
f FuncTemplate
|
type ExprFuncParam interface {
|
||||||
}
|
Name() string
|
||||||
|
Type() string
|
||||||
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
IsOptional() bool
|
||||||
return functor.f(ctx, name, args)
|
IsRepeat() bool
|
||||||
|
DefaultValue() any
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Function Info
|
// ---- Function Info
|
||||||
type ExprFunc interface {
|
type ExprFunc interface {
|
||||||
|
Formatter
|
||||||
Name() string
|
Name() string
|
||||||
MinArgs() int
|
MinArgs() int
|
||||||
MaxArgs() int
|
MaxArgs() int
|
||||||
Functor() Functor
|
Functor() Functor
|
||||||
|
Params() []ExprFuncParam
|
||||||
|
ReturnType() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----Expression Context
|
// ----Expression Context
|
||||||
@@ -33,10 +36,12 @@ type ExprContext interface {
|
|||||||
Clone() ExprContext
|
Clone() ExprContext
|
||||||
GetVar(varName string) (value any, exists bool)
|
GetVar(varName string) (value any, exists bool)
|
||||||
SetVar(varName string, value any)
|
SetVar(varName string, value any)
|
||||||
setVar(varName string, value any)
|
UnsafeSetVar(varName string, value any)
|
||||||
EnumVars(func(name string) (accept bool)) (varNames []string)
|
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||||
GetFuncInfo(name string) ExprFunc
|
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||||
Call(name string, args []any) (result any, err error)
|
Call(name string, args []any) (result any, err error)
|
||||||
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||||
|
RegisterFuncInfo(info ExprFunc)
|
||||||
|
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||||
}
|
}
|
||||||
|
|||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// data-cursors.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dataCursor struct {
|
||||||
|
ds map[string]Functor
|
||||||
|
ctx ExprContext
|
||||||
|
index int
|
||||||
|
resource any
|
||||||
|
nextFunc Functor
|
||||||
|
cleanFunc Functor
|
||||||
|
resetFunc Functor
|
||||||
|
currentFunc Functor
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
|
||||||
|
dc = &dataCursor{
|
||||||
|
ds: ds,
|
||||||
|
index: -1,
|
||||||
|
ctx: ctx.Clone(),
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
exportObjects(dc.ctx, ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
|
if dc.resource != nil {
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||||
|
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
||||||
|
if item == nil {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Index() int {
|
||||||
|
return dc.index
|
||||||
|
}
|
||||||
+487
-128
@@ -22,49 +22,242 @@ Expressions calculator
|
|||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
#TODO: Work in progress#
|
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)#
|
||||||
|
|
||||||
== 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.
|
||||||
|
|
||||||
|
|
||||||
=== Concepts and terminology
|
=== Concepts and terminology
|
||||||
#TODO#
|
#TODO#
|
||||||
|
|
||||||
|
image::expression-diagram.png[]
|
||||||
|
|
||||||
|
=== `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, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
|
||||||
|
|
||||||
|
`dev-expr` can 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 can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
|
||||||
|
|
||||||
|
Here are some examples of execution.
|
||||||
|
|
||||||
|
.Run `dev-expr` in REPL mode and ask for help
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
# Type 'exit' or Ctrl+D to quit the program.
|
||||||
|
|
||||||
|
[user]$ ./dev-expr
|
||||||
|
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
|
||||||
|
Based on the Expr package v0.10.0
|
||||||
|
Type help to get the list of available commands
|
||||||
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
|
|
||||||
|
>>> help
|
||||||
|
--- REPL commands:
|
||||||
|
source -- Load a file as input
|
||||||
|
tty -- Enable/Disable ansi output <1>
|
||||||
|
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
|
||||||
|
|
||||||
|
--- 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]$ ./dev-expr
|
||||||
|
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
|
||||||
|
Based on the Expr package v0.10.0
|
||||||
|
Type help to get the list of available commands
|
||||||
|
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.
|
||||||
|
|
||||||
== 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.
|
||||||
|
|
||||||
=== Numbers
|
=== Numbers
|
||||||
Numbers can be integers (GO int64) or float (GO float64). In mixed operations involving integers and floats, integers are automatically promoted to floats.
|
_Expr_ supports three type of numbers:
|
||||||
|
|
||||||
|
. [blue]#Integers#
|
||||||
|
. [blue]#Floats#
|
||||||
|
. [blue]#Factions#
|
||||||
|
|
||||||
|
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
|
||||||
|
|
||||||
|
==== Integers
|
||||||
|
__Expr__'s integers are a subset of the integer set. Internally they are stored as Golang _int64_ values.
|
||||||
|
|
||||||
|
.Integer literal syntax
|
||||||
|
====
|
||||||
|
*_integer_* = [_sign_] _digit-seq_ +
|
||||||
|
_sign_ = "**+**" | "**-**" +
|
||||||
|
_digit-seq_ = _dec-seq_ | _bin-seq_ | _oct-seq_ | _hex-seq_ +
|
||||||
|
_dec-seq_ = {__dec-digit__} +
|
||||||
|
_dec-digit_ = "**0**"|"**1**"|...|"**9**" +
|
||||||
|
_bin-seq_ = "**0b**"{__bin-digit__} +
|
||||||
|
_bin-digit_ = "**0**"|"**1**" +
|
||||||
|
_oct-seq_ = "**0o**"{__oct-digit__} +
|
||||||
|
_oct-digit_ = "**0**"|"**1**"|...|"**7**" +
|
||||||
|
_hex-seq_ = "**0x**"{__hex-digit__} +
|
||||||
|
_hex-digit_ = "**0**"|"**1**"|...|"**9**"|"**a**"|...|"**z**"|"**A**"|...|"**Z**"
|
||||||
|
====
|
||||||
|
|
||||||
|
Value range: *-9223372036854775808* to *9223372036854775807*
|
||||||
|
|
||||||
.Arithmetic operators
|
.Arithmetic operators
|
||||||
[cols="^1,^2,6,4"]
|
[cols="^1,^2,6,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` -> 1
|
||||||
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ +
|
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> 2
|
||||||
[blue]`-(+2)` _[-2]_
|
| [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
|
||||||
|
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5
|
||||||
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` _[1]_ +
|
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
|
||||||
[blue]`4 + 0.5` _[4.5]_
|
|
||||||
|
|
||||||
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` _[2]_ +
|
|
||||||
[blue]`4 - 0.5` _[3.5]_
|
|
||||||
|
|
||||||
| [blue]`*` | _product_ | Multiply two values | `-1 * 2` _[-2]_ +
|
|
||||||
[blue]`4 * 0.5` _[2.0]_
|
|
||||||
|
|
||||||
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`-1 / 2` _[0]_ +
|
|
||||||
[blue]`1.0 / 2` _[0.5]_
|
|
||||||
|
|
||||||
| [blue]`./` | _Float division_ | Force float division | [blue]`-1 ./ 2` _[-0.5]_
|
|
||||||
|
|
||||||
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` _[1]_
|
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== String
|
^(*)^ See also the _float division_ [blue]`./` below.
|
||||||
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
|
|
||||||
|
|
||||||
|
==== Floats
|
||||||
|
__Expr__'s floats are a subset of the rational number set. Note that they can't hold the exact value of an unlimited number; floats can only approximate them. Internally floats are stored as Golang's _float64_ values.
|
||||||
|
|
||||||
|
|
||||||
|
.Float literal syntax
|
||||||
|
====
|
||||||
|
*_float_* = [_sign_] _dec-seq_ "**.**" [_dec-seq_] [("**e**"|"**E**") [_sign_] _dec-seq_] +
|
||||||
|
_sign_ = "**+**" | "**-**" +
|
||||||
|
_dec-seq_ = _see-integer-literal-syntax_
|
||||||
|
====
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`1.0` +
|
||||||
|
[green]`1` +
|
||||||
|
`>>>` [blue]`0.123` +
|
||||||
|
[green]`0.123` +
|
||||||
|
`>>>` [blue]`4.5e+3` +
|
||||||
|
[green]`4500` +
|
||||||
|
`>>>` [blue]`4.5E-33` +
|
||||||
|
[green]`4.5e-33` +
|
||||||
|
`>>>` [blue]`4.5E-3` +
|
||||||
|
[green]`0.0045` +
|
||||||
|
`>>>` [blue]`4.5E10` +
|
||||||
|
[green]`4.5e+10`
|
||||||
|
|
||||||
|
|
||||||
|
.Arithmetic operators
|
||||||
|
[cols="^1,^2,6,4"]
|
||||||
|
|===
|
||||||
|
| Symbol | Operation | Description | Examples
|
||||||
|
| [blue]`+` | _sum_ | Add two values | [blue]`4 + 0.5` -> 4.5
|
||||||
|
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`4 - 0.5` -> 3.5
|
||||||
|
| [blue]`*` | _product_ | Multiply two values | [blue]`4 * 0.5` -> 2.0
|
||||||
|
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
|
||||||
|
| [blue]`./`| _Float division_ | Force float division | [blue]`-1 ./ 2` -> -0.5
|
||||||
|
|===
|
||||||
|
|
||||||
|
==== Fractions
|
||||||
|
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
|
||||||
|
|
||||||
|
.Fraction literal syntax
|
||||||
|
====
|
||||||
|
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
|
||||||
|
_sign_ = "**+**" | "**-**" +
|
||||||
|
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
|
||||||
|
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
|
||||||
|
_dec-seq_ = _see-integer-literal-syntax_ +
|
||||||
|
_digit-seq_ = _see-integer-literal-syntax_
|
||||||
|
====
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
// [source,go]
|
||||||
|
// ----
|
||||||
|
`>>>` [blue]`1 | 2` +
|
||||||
|
[green]`1|2` +
|
||||||
|
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
|
||||||
|
[green]`2|3` +
|
||||||
|
`>>>` [blue]`1|2 + 2|3` +
|
||||||
|
[green]`7|6` +
|
||||||
|
`>>>` [blue]`1|2 * 2|3` +
|
||||||
|
[green]`1|3` +
|
||||||
|
`>>>` [blue]`1|2 / 1|3` +
|
||||||
|
[green]`3|2` +
|
||||||
|
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
|
||||||
|
[green]`1.5` +
|
||||||
|
`>>>` [blue]`-1|2` +
|
||||||
|
[green]`-1|2` +
|
||||||
|
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
|
||||||
|
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
|
||||||
|
`>>>` [blue]`1|(-2)` +
|
||||||
|
[green]`-1|2`
|
||||||
|
// ----
|
||||||
|
|
||||||
|
Fractions can be used together with integers and floats in expressions.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`1|2 + 5` +
|
||||||
|
[green]`11|2` +
|
||||||
|
`>>>` [blue]`4 - 1|2` +
|
||||||
|
[green]`7|2` +
|
||||||
|
`>>>` [blue]`1.0 + 1|2` +
|
||||||
|
[green]`1.5`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=== Strings
|
||||||
|
Strings are character sequences enclosed between two double quote [blue]`"`.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`"I'm a string"` +
|
||||||
|
[green]`I'm a string` +
|
||||||
|
`>>>` [blue]`"123abc?!"` +
|
||||||
|
[green]`123abc?!` +
|
||||||
|
`>>>` [blue]`"123\nabc"` +
|
||||||
|
[green]`123` +
|
||||||
|
[green]`abc` +
|
||||||
|
`>>>` [blue]`"123\tabc"` +
|
||||||
|
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
|
||||||
|
|
||||||
Some arithmetic operators can also be used with strings.
|
Some arithmetic operators can also be used with strings.
|
||||||
|
|
||||||
@@ -73,55 +266,75 @@ Some arithmetic operators can also be used with strings.
|
|||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
|
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` -> _"onetwo"_ +
|
||||||
[blue]`"one" + 2` _["one2"]_
|
[blue]`"one" + 2` -> _"one2"_
|
||||||
|
|
||||||
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
The items of strings can be accessed using the dot `.` operator.
|
||||||
|
|
||||||
=== Boolean
|
.Item access syntax
|
||||||
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
|
====
|
||||||
|
_item_ = _string-expr_ "**.**" _integer-expr_
|
||||||
|
====
|
||||||
|
|
||||||
|
.String examples
|
||||||
|
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ +
|
||||||
|
[green]`abc` +
|
||||||
|
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
|
||||||
|
[green]`b` +
|
||||||
|
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
|
||||||
|
[green]`c` +
|
||||||
|
`>>>` [blue]`\#s` [gray]_// number of chars_ +
|
||||||
|
[gren]`3` +
|
||||||
|
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
|
||||||
.Relational operators
|
=== Booleans
|
||||||
|
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||||
|
|
||||||
|
.Relational operators^(*)^
|
||||||
[cols="^1,^2,6,4"]
|
[cols="^1,^2,6,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ +
|
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` -> _false_ +
|
||||||
[blue]`"a" == "a"` _[true]_
|
[blue]`"a" == "a"` -> _true_
|
||||||
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` _[true]_ +
|
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` -> _true_ +
|
||||||
[blue]`"a" != "a"` _[false]_
|
[blue]`"a" != "a"` -> _false_
|
||||||
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ +
|
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` -> _false_ +
|
||||||
[blue]`"a" < "b"` _[true]_
|
[blue]`"a" < "b"` -> _true_
|
||||||
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` _[false]_ +
|
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` -> _false_ +
|
||||||
[blue]`"b" \<= "b"` _[true]_
|
[blue]`"b" \<= "b"` -> _true_
|
||||||
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ +
|
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` -> _true_ +
|
||||||
[blue]`"a" < "b"` _[false]_
|
[blue]`"a" < "b"` -> _false_
|
||||||
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` _[true]_ +
|
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` -> _true_ +
|
||||||
[blue]`"b" \<= "b"` _[true]_
|
[blue]`"b" \<= "b"` -> _true_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
|
||||||
|
|
||||||
|
|
||||||
.Boolean operators
|
.Boolean operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ +
|
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` -> _false_ +
|
||||||
[blue]`NOT (2 < 1)` _[true]_
|
[blue]`NOT (2 < 1)` -> _true_
|
||||||
|
|
||||||
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` _[false]_ +
|
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` -> _false_ +
|
||||||
[blue]`"a" < "b" AND NOT (2 < 1)` _[true]_
|
[blue]`"a" < "b" AND NOT (2 < 1)` -> _true_
|
||||||
|
|
||||||
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` _[true]_ +
|
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` -> _true_ +
|
||||||
[blue]`"a" == "b" OR (2 == 1)` _[false]_
|
[blue]`"a" == "b" OR (2 == 1)` -> _false_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
[CAUTION]
|
[CAUTION]
|
||||||
====
|
====
|
||||||
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of operators [blue]`and` and [blue]`or` is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
|
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
[source,go]
|
||||||
@@ -131,127 +344,273 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
|
|||||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== List
|
=== Lists
|
||||||
_Expr_ supports list of mixed-type values, also specified by normal expressions.
|
_Expr_ supports list of mixed-type values, also specified by normal expressions. Internally, _Expr_'s lists are Go arrays.
|
||||||
|
|
||||||
.List examples
|
.List literal syntax
|
||||||
[source,go]
|
====
|
||||||
----
|
*_list_* = _empty-list_ | _non-empty-list_ +
|
||||||
[1, 2, 3] // List of integers
|
_empty-list_ = "**[]**" +
|
||||||
["one", "two", "three"] // List of strings
|
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
||||||
["one", 2, false, 4.1] // List of mixed-types
|
====
|
||||||
["one"+1, 2.0*(9-2)] // List of expressions
|
|
||||||
[ [1,"one"], [2,"two"]] // List of lists
|
|
||||||
----
|
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
|
||||||
|
[green]`[1, 2, 3]` +
|
||||||
|
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
|
||||||
|
[green]`["one", "two", "three"]` +
|
||||||
|
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
|
||||||
|
[green]`["one", 2, false, 4.1]` +
|
||||||
|
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
|
||||||
|
[green]`["one1", 14]` +
|
||||||
|
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
|
||||||
|
[green]`[[1, "one"], [2, "two"]]`
|
||||||
|
|
||||||
.List operators
|
.List operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` _[ [1,2,3] ]_
|
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
|
||||||
|
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
|
||||||
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
|
||||||
|
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
|
||||||
|
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
|
||||||
|
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
|
||||||
|
[blue]`6 in [1,2,3]` -> _false_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
== Variables
|
The items of array can be accessed using the dot `.` operator.
|
||||||
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
|
|
||||||
|
.Item access syntax
|
||||||
|
====
|
||||||
|
_item_ = _list-expr_ "**.**" _integer-expr_
|
||||||
|
====
|
||||||
|
|
||||||
|
.Items of list
|
||||||
|
`>>>` [blue]`[1,2,3].1` +
|
||||||
|
[green]`2` +
|
||||||
|
`>>>` [blue]`list=[1,2,3]; list.1` +
|
||||||
|
[green]`2` +
|
||||||
|
`>>>` [blue]`["one","two","three"].1` +
|
||||||
|
[green]`two` +
|
||||||
|
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
|
||||||
|
[green]`two` +
|
||||||
|
`>>>` [blue]`list.(-1)` +
|
||||||
|
[green]`three` +
|
||||||
|
`>>>` [blue]`list.(10)` +
|
||||||
|
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
||||||
|
`>>>` [blue]`#list` +
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
|
||||||
|
[green]`c`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=== Dictionaries
|
||||||
|
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
|
||||||
|
|
||||||
|
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
|
||||||
|
|
||||||
|
.Dict literal syntax
|
||||||
|
====
|
||||||
|
*_dict_* = _empty-dict_ | _non-empty-dict_ +
|
||||||
|
_empty-dict_ = "**{}**" +
|
||||||
|
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
|
||||||
|
====
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||||
----
|
[green]`{1: "one", 2: "two"}` +
|
||||||
a=1
|
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||||
x = 5.2 * (9-3)
|
[green]`{"one": 1, "two": 2}` +
|
||||||
x = 1; y = 2*x
|
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||||
----
|
[green]`{"sum": 6, "prod": 6}`
|
||||||
|
|
||||||
|
|
||||||
|
.Dict operators
|
||||||
|
[cols="^2,^2,4,5"]
|
||||||
|
|===
|
||||||
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
|
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
|
||||||
|
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
|
||||||
|
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
|
||||||
|
[blue]`"six" in {"one":1, "two":2}` -> _false_
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
== Variables
|
||||||
|
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
|
||||||
|
|
||||||
|
.Variable literal syntax
|
||||||
|
====
|
||||||
|
*_variable_* = _identifier_ "*=*" _any-value_ +
|
||||||
|
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
|
||||||
|
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
|
||||||
|
====
|
||||||
|
|
||||||
|
NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`a=1` +
|
||||||
|
[green]`1` +
|
||||||
|
`>>>` [blue]`a_b=1+2` +
|
||||||
|
[green]`1+2` +
|
||||||
|
`>>>` [blue]`a_b` +
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
|
||||||
|
[green]`31.200000000000003` +
|
||||||
|
`>>>` [blue]`x = 1; y = 2*x` +
|
||||||
|
[green]`2` +
|
||||||
|
`>>>` [blue]`_a=2` +
|
||||||
|
[red]`Parse Error: [1:2] unexpected token "_"` +
|
||||||
|
`>>>` [blue]`1=2` +
|
||||||
|
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
|
||||||
|
|
||||||
|
|
||||||
== 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 value of the latter is the final result.
|
||||||
|
|
||||||
|
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
`>>>` [blue]`a=1; b=2; c=3; a+b+c` +
|
||||||
----
|
[green]`6`
|
||||||
a=1; b=2; c=3; a+b+c // returns 6
|
|
||||||
----
|
The value of each sub-expression is stored in the automatica variable _last_.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
`>>>` [blue]`2+3; b=last+10; last` +
|
||||||
|
[green]`15`
|
||||||
|
|
||||||
|
|
||||||
=== [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.
|
||||||
|
|
||||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
.Examples
|
||||||
|
[blue]`5 but 2` +
|
||||||
|
[green]`2` +
|
||||||
|
[blue]`x=2*3 but x-1` +
|
||||||
|
[green]`5`.
|
||||||
|
|
||||||
|
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and 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 or to change their value in the evaluation context (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
|
||||||
[source,go]
|
`>>>` [blue]`a=15+1`
|
||||||
----
|
[green]`16`
|
||||||
a=15+1 // returns 16
|
|
||||||
----
|
|
||||||
|
|
||||||
=== Selector operator [blue]`? : ::`
|
=== Selector operator [blue]`? : ::`
|
||||||
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
||||||
|
|
||||||
.Syntax
|
.Selector literal Syntax
|
||||||
[source,bnf]
|
_selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
|
||||||
----
|
_selector-case_ = [_match-list_] _case-value_ +
|
||||||
<selector-operator> ::= <expression> "?" <selector-case> { ":" <selector-case> } ["::" <case-value>]
|
_match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
|
||||||
<selector-case> ::= [<list>] <case-value>
|
_item_ = _expression_ +
|
||||||
<case-value> ::= "{" <multi-expr> "}"
|
_case-multi-expression_ = "*{*" _multi-expression_ "*}*" +
|
||||||
<multi-expr> ::= <expr> {";" <expr>}
|
_multi-expression_ = _expression_ { "*;*" _expression_ } +
|
||||||
----
|
_default-multi-expression_ = _multi-expression_
|
||||||
|
|
||||||
.Example
|
In other words, the selector operator evaluates the _select-expression_ on the left-hand side of the [blue]`?` symbol; it then compares the result obtained with the values listed in the __match-list__'s, from left to right. If the comparision finds 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.
|
||||||
[source,go]
|
|
||||||
----
|
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.
|
||||||
1 ? {"a"} : {"b"} // returns "b"
|
|
||||||
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
|
The [blue]`:` 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 [blue]`::` symbol (double-colon). Also note that the default expression has no _match-list_.
|
||||||
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
|
|
||||||
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
|
.Examples
|
||||||
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
|
`>>>` [blue]`1 ? {"a"} : {"b"}` +
|
||||||
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
|
[green]`b` +
|
||||||
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
|
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
|
||||||
----
|
[green]`c' +
|
||||||
|
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
|
||||||
|
[green]`b` +
|
||||||
|
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
|
||||||
|
[red]`Parse Error: [1:34] case list in default clause` +
|
||||||
|
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
|
||||||
|
[green]`b` +
|
||||||
|
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` +
|
||||||
|
[green]`b` +
|
||||||
|
`>>>` [blue]`10 ? {"a"} : {"b"}` +
|
||||||
|
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
|
||||||
|
|
||||||
|
|
||||||
|
=== Variable default value [blue]`??` and [blue]`?=`
|
||||||
|
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.
|
||||||
|
|
||||||
|
IMPORTANT: If the left variable is defined, the right expression is not evuated at all.
|
||||||
|
|
||||||
|
The [blue]`??` do not change the status of the left variable.
|
||||||
|
|
||||||
|
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`var ?? (1+2)`'
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`var` +
|
||||||
|
[red]`Eval Error: undefined variable or function "var"` +
|
||||||
|
`>>>` [blue]`var ?= (1+2)` +
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`var` +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
|
||||||
|
|
||||||
== Priorities of operators
|
== Priorities of operators
|
||||||
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,^6"]
|
||||||
|===
|
|===
|
||||||
| Priority | Operators | Position | Operation | Operands and results
|
| Priority | Operators | Position | Operation | Operands and results
|
||||||
|
|
||||||
1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ "!" -> _integer_
|
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
|
||||||
1+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| ("+"\|"-") _number_ -> _number_
|
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
|
||||||
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ "*" _number_ -> _number_
|
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||||
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ "*" _integer_ -> _string_
|
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||||
| [blue]`/` | _Infix_ | _Division_ | _number_ "/" _number_ -> _number_
|
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ "./" _number_ -> _float_
|
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_
|
||||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ "%" _integer_ -> _integer_
|
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_
|
||||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ "+" _number_ -> _number_
|
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_
|
||||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) "+" (_string_\|_number_) -> _string_
|
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_
|
||||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ "+" _list_ -> _list_
|
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_
|
||||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ "-" _number_ -> _number_
|
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
||||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ "-" _list_ -> _list_
|
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
||||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ "<" _comparable_ -> _boolean_
|
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
||||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ "\<=" _comparable_ -> _boolean_
|
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ ">" _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
||||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ ">=" _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
||||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ "==" _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
|
||||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ "!=" _comparable_ -> _boolean_
|
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
||||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | "not" _boolean_ -> _boolean_
|
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
||||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
|
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
|
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
||||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
|
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
||||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ "\|\|" _boolean_ -> _boolean_
|
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
||||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
||||||
|
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _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_
|
||||||
|
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||||
|
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
|
||||||
|
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
|
||||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|||||||
+1881
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
@@ -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,65 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// formatter.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type FmtOpt uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
TTY FmtOpt = 1 << iota
|
||||||
|
MultiLine
|
||||||
|
Truncate
|
||||||
|
Base2
|
||||||
|
Base8
|
||||||
|
Base10
|
||||||
|
Base16
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TruncateEllipsis = "(...)"
|
||||||
|
MinTruncateSize = 10
|
||||||
|
TruncateSize = MinTruncateSize + 15
|
||||||
|
)
|
||||||
|
|
||||||
|
func TruncateString(s string) (trunc string) {
|
||||||
|
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
|
||||||
|
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Formatter interface {
|
||||||
|
ToString(options FmtOpt) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFormatted(v any, opt FmtOpt) (text string) {
|
||||||
|
if v == nil {
|
||||||
|
text = "(nil)"
|
||||||
|
} else if s, ok := v.(string); ok {
|
||||||
|
text = s
|
||||||
|
} else if formatter, ok := v.(Formatter); ok {
|
||||||
|
text = formatter.ToString(opt)
|
||||||
|
} else {
|
||||||
|
text = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Typer interface {
|
||||||
|
TypeName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTypeName(v any) (name string) {
|
||||||
|
if typer, ok := v.(Typer); ok {
|
||||||
|
name = typer.TypeName()
|
||||||
|
} else if IsInteger(v) {
|
||||||
|
name = "integer"
|
||||||
|
} else if IsFloat(v) {
|
||||||
|
name = "float"
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%T", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// fraction-type.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FractionType struct {
|
||||||
|
num, den int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFraction(num, den int64) *FractionType {
|
||||||
|
num, den = simplifyIntegers(num, den)
|
||||||
|
return &FractionType{num, den}
|
||||||
|
}
|
||||||
|
|
||||||
|
func float64ToFraction(f float64) (fract *FractionType, err error) {
|
||||||
|
var sign string
|
||||||
|
intPart, decPart := math.Modf(f)
|
||||||
|
if decPart < 0.0 {
|
||||||
|
sign = "-"
|
||||||
|
intPart = -intPart
|
||||||
|
decPart = -decPart
|
||||||
|
}
|
||||||
|
dec := fmt.Sprintf("%.12f", decPart)
|
||||||
|
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
|
||||||
|
// fmt.Printf("S: '%s'\n",s)
|
||||||
|
return makeGeneratingFraction(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
|
||||||
|
/*
|
||||||
|
func _float64ToFraction(f float64) (num, den int64, err error) {
|
||||||
|
const expMask = 1<<11 - 1
|
||||||
|
bits := math.Float64bits(f)
|
||||||
|
mantissa := bits & (1<<52 - 1)
|
||||||
|
exp := int((bits >> 52) & expMask)
|
||||||
|
switch exp {
|
||||||
|
case expMask: // non-finite
|
||||||
|
err = errors.New("infite")
|
||||||
|
return
|
||||||
|
case 0: // denormal
|
||||||
|
exp -= 1022
|
||||||
|
default: // normal
|
||||||
|
mantissa |= 1 << 52
|
||||||
|
exp -= 1023
|
||||||
|
}
|
||||||
|
|
||||||
|
shift := 52 - exp
|
||||||
|
|
||||||
|
// Optimization (?): partially pre-normalise.
|
||||||
|
for mantissa&1 == 0 && shift > 0 {
|
||||||
|
mantissa >>= 1
|
||||||
|
shift--
|
||||||
|
}
|
||||||
|
|
||||||
|
if f < 0 {
|
||||||
|
num = -int64(mantissa)
|
||||||
|
} else {
|
||||||
|
num = int64(mantissa)
|
||||||
|
}
|
||||||
|
den = int64(1)
|
||||||
|
|
||||||
|
if shift > 0 {
|
||||||
|
den = den << shift
|
||||||
|
} else {
|
||||||
|
num = num << (-shift)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func makeGeneratingFraction(s string) (f *FractionType, err error) {
|
||||||
|
var num, den int64
|
||||||
|
var sign int64 = 1
|
||||||
|
var parts []string
|
||||||
|
if len(s) == 0 {
|
||||||
|
goto exit
|
||||||
|
}
|
||||||
|
if s[0] == '-' {
|
||||||
|
sign = int64(-1)
|
||||||
|
s = s[1:]
|
||||||
|
} else if s[0] == '+' {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(s, "()") {
|
||||||
|
s = s[0 : len(s)-2]
|
||||||
|
}
|
||||||
|
parts = strings.SplitN(s, ".", 2)
|
||||||
|
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parts) == 1 {
|
||||||
|
f = newFraction(sign*num, 1)
|
||||||
|
} else if len(parts) == 2 {
|
||||||
|
subParts := strings.SplitN(parts[1], "(", 2)
|
||||||
|
if len(subParts) == 1 {
|
||||||
|
den = 1
|
||||||
|
dec := parts[1]
|
||||||
|
lsd := len(dec)
|
||||||
|
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
|
||||||
|
lsd--
|
||||||
|
}
|
||||||
|
for _, c := range dec[0:lsd] {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return nil, errExpectedGot("fract", "digit", c)
|
||||||
|
}
|
||||||
|
num = num*10 + int64(c-'0')
|
||||||
|
den = den * 10
|
||||||
|
}
|
||||||
|
f = newFraction(sign*num, den)
|
||||||
|
} else if len(subParts) == 2 {
|
||||||
|
sub := num
|
||||||
|
mul := int64(1)
|
||||||
|
for _, c := range subParts[0] {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return nil, errExpectedGot("fract", "digit", c)
|
||||||
|
}
|
||||||
|
num = num*10 + int64(c-'0')
|
||||||
|
sub = sub*10 + int64(c-'0')
|
||||||
|
mul *= 10
|
||||||
|
}
|
||||||
|
if len(subParts) == 2 {
|
||||||
|
if s[len(s)-1] != ')' {
|
||||||
|
goto exit
|
||||||
|
}
|
||||||
|
p := subParts[1][0 : len(subParts[1])-1]
|
||||||
|
for _, c := range p {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return nil, errExpectedGot("fract", "digit", c)
|
||||||
|
}
|
||||||
|
num = num*10 + int64(c-'0')
|
||||||
|
den = den*10 + 9
|
||||||
|
}
|
||||||
|
den *= mul
|
||||||
|
}
|
||||||
|
num -= sub
|
||||||
|
f = newFraction(sign*num, den)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
if f == nil {
|
||||||
|
err = errors.New("bad syntax")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) toFloat() float64 {
|
||||||
|
return float64(f.num) / float64(f.den)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) String() string {
|
||||||
|
return f.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) TypeName() string {
|
||||||
|
return "fraction"
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- fraction utility functions
|
||||||
|
|
||||||
|
// greatest common divider
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// lower common multiple
|
||||||
|
func lcm(a, b int64) (l int64) {
|
||||||
|
g := gcd(a, b)
|
||||||
|
l = a * b / g
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum two fractions
|
||||||
|
func sumFract(f1, f2 *FractionType) (sum *FractionType) {
|
||||||
|
m := lcm(f1.den, f2.den)
|
||||||
|
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply two fractions
|
||||||
|
func mulFract(f1, f2 *FractionType) (prod *FractionType) {
|
||||||
|
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyToFract(v any) (f *FractionType, err error) {
|
||||||
|
var ok bool
|
||||||
|
if f, ok = v.(*FractionType); !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 *FractionType, 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 *FractionType
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns
|
||||||
|
//
|
||||||
|
// <0 if af1 < af2
|
||||||
|
// =0 if af1 == af2
|
||||||
|
// >0 if af1 > af2
|
||||||
|
// err if af1 or af2 is not convertible to fraction
|
||||||
|
func cmpAnyFract(af1, af2 any) (result int, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = cmpFract(f1, f2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns
|
||||||
|
//
|
||||||
|
// <0 if af1 < af2
|
||||||
|
// =0 if af1 == af2
|
||||||
|
// >0 if af1 > af2
|
||||||
|
func cmpFract(f1, f2 *FractionType) (result int) {
|
||||||
|
f2.num = -f2.num
|
||||||
|
f := sumFract(f1, f2)
|
||||||
|
if f.num < 0 {
|
||||||
|
result = -1
|
||||||
|
} else if f.num > 0 {
|
||||||
|
result = 1
|
||||||
|
} else {
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subAnyFract(af1, af2 any) (sum any, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
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 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f1.num == 0 || f2.num == 0 {
|
||||||
|
prod = 0
|
||||||
|
} else {
|
||||||
|
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
|
||||||
|
prod = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func divAnyFract(af1, af2 any) (quot any, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
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 := &FractionType{f1.num * f2.den, f1.den * f2.num}
|
||||||
|
quot = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func simplifyFraction(f *FractionType) (v any) {
|
||||||
|
f.num, f.den = simplifyIntegers(f.num, f.den)
|
||||||
|
if f.den == 1 {
|
||||||
|
v = f.num
|
||||||
|
} else {
|
||||||
|
v = &FractionType{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) *FractionType {
|
||||||
|
return &FractionType{n, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFraction(v any) (ok bool) {
|
||||||
|
_, ok = v.(*FractionType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
+186
@@ -0,0 +1,186 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-builtins.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = args[0] == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsInteger(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsFloat(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsBool(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsString(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsFract(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRationalFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsRational(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsList(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsDict(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = (v != 0)
|
||||||
|
case *FractionType:
|
||||||
|
result = v.num != 0
|
||||||
|
case float64:
|
||||||
|
result = v != 0.0
|
||||||
|
case bool:
|
||||||
|
result = v
|
||||||
|
case string:
|
||||||
|
result = len(v) > 0
|
||||||
|
default:
|
||||||
|
err = errCantConvert(name, v, "bool")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = v
|
||||||
|
case float64:
|
||||||
|
result = int64(math.Trunc(v))
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = int64(1)
|
||||||
|
} else {
|
||||||
|
result = int64(0)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var i int
|
||||||
|
if i, err = strconv.Atoi(v); err == nil {
|
||||||
|
result = int64(i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = errCantConvert(name, v, "int")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = float64(v)
|
||||||
|
case float64:
|
||||||
|
result = v
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = float64(1)
|
||||||
|
} else {
|
||||||
|
result = float64(0)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var f float64
|
||||||
|
if f, err = strconv.ParseFloat(v, 64); err == nil {
|
||||||
|
result = f
|
||||||
|
}
|
||||||
|
case *FractionType:
|
||||||
|
result = v.toFloat()
|
||||||
|
default:
|
||||||
|
err = errCantConvert(name, v, "float")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
var den int64 = 1
|
||||||
|
if len(args) > 1 {
|
||||||
|
var ok bool
|
||||||
|
if den, ok = args[1].(int64); !ok {
|
||||||
|
err = errExpectedGot(name, "integer", args[1])
|
||||||
|
} else if den == 0 {
|
||||||
|
err = errDivisionByZero(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
result = newFraction(v, den)
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
result, err = float64ToFraction(v)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = newFraction(1, 1)
|
||||||
|
} else {
|
||||||
|
result = newFraction(0, 1)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
result, err = makeGeneratingFraction(v)
|
||||||
|
case *FractionType:
|
||||||
|
result = v
|
||||||
|
default:
|
||||||
|
err = errCantConvert(name, v, "float")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||||
|
anyParams := []ExprFuncParam{
|
||||||
|
newFuncParam(paramValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
|
||||||
|
|
||||||
|
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams)
|
||||||
|
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams)
|
||||||
|
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{
|
||||||
|
newFuncParam(paramValue),
|
||||||
|
newFuncParamFlagDef("denominator", pfOptional, 1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||||
|
}
|
||||||
+13
-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
|
||||||
@@ -137,6 +137,14 @@ 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", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerImport("import", ImportImportFuncs, "Functions import() and include()")
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-23
@@ -9,36 +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 *FractionType
|
||||||
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 {
|
||||||
|
v = NewListIterator(list, nil)
|
||||||
|
}
|
||||||
|
if subIter, ok := v.(Iterator); ok {
|
||||||
|
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
|
||||||
if array, ok := v.([]any); ok {
|
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
|
||||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
count++
|
||||||
|
|
||||||
if !sumAsFloat && isFloat(v) {
|
if !sumAsFloat {
|
||||||
|
if IsFloat(v) {
|
||||||
sumAsFloat = true
|
sumAsFloat = true
|
||||||
|
if sumAsFract {
|
||||||
|
floatSum = fractSum.toFloat()
|
||||||
|
} else {
|
||||||
floatSum = float64(intSum)
|
floatSum = float64(intSum)
|
||||||
}
|
}
|
||||||
|
} else if !sumAsFract && isFraction(v) {
|
||||||
|
fractSum = newFraction(intSum, 1)
|
||||||
|
sumAsFract = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if sumAsFloat {
|
if sumAsFloat {
|
||||||
floatSum += numAsFloat(v)
|
floatSum += numAsFloat(v)
|
||||||
|
} else if sumAsFract {
|
||||||
|
var item *FractionType
|
||||||
|
var ok bool
|
||||||
|
if item, ok = v.(*FractionType); !ok {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
item = newFraction(iv, 1)
|
||||||
|
}
|
||||||
|
fractSum = sumFract(fractSum, item)
|
||||||
} else {
|
} else {
|
||||||
iv, _ := v.(int64)
|
iv, _ := v.(int64)
|
||||||
intSum += iv
|
intSum += iv
|
||||||
@@ -48,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
|
||||||
}
|
}
|
||||||
@@ -56,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 *FractionType
|
||||||
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 {
|
||||||
|
v = NewListIterator(list, nil)
|
||||||
|
}
|
||||||
|
if subIter, ok := v.(Iterator); ok {
|
||||||
|
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
|
||||||
if array, ok := v.([]any); ok {
|
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
|
||||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
if !mulAsFloat && isFloat(v) {
|
if !mulAsFloat {
|
||||||
|
if IsFloat(v) {
|
||||||
mulAsFloat = true
|
mulAsFloat = true
|
||||||
|
if mulAsFract {
|
||||||
|
floatProd = fractProd.toFloat()
|
||||||
|
} else {
|
||||||
floatProd = float64(intProd)
|
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 *FractionType
|
||||||
|
var ok bool
|
||||||
|
if item, ok = v.(*FractionType); !ok {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
item = newFraction(iv, 1)
|
||||||
|
}
|
||||||
|
fractProd = mulFract(fractProd, item)
|
||||||
} else {
|
} else {
|
||||||
iv, _ := v.(int64)
|
iv, _ := v.(int64)
|
||||||
intProd *= iv
|
intProd *= iv
|
||||||
@@ -92,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
|
||||||
}
|
}
|
||||||
@@ -100,15 +162,20 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportMathFuncs(ctx ExprContext) {
|
func ImportMathFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
|
||||||
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("math.arith", ImportMathFuncs)
|
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()")
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-8
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,10 +158,28 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportOsFuncs(ctx ExprContext) {
|
func ImportOsFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
|
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
newFuncParam(typeFilepath),
|
||||||
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
})
|
||||||
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
|
newFuncParam(typeFilepath),
|
||||||
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
|
})
|
||||||
|
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
|
||||||
|
newFuncParam(typeFilepath),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
newFuncParamFlagDef("limitCh", pfOptional, "\\n"),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
|
||||||
}
|
}
|
||||||
|
|||||||
+222
@@ -0,0 +1,222 @@
|
|||||||
|
// 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 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 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 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 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 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", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSeparator),
|
||||||
|
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlagDef(paramStart, pfOptional, 0),
|
||||||
|
newFuncParamFlagDef(paramCount, pfOptional, -1),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
|
||||||
|
newFuncParamFlagDef(paramCount, pfOptional, -1),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlag(paramPrefix, pfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlag(paramSuffix, pfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- Function template
|
||||||
|
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||||
|
|
||||||
|
// ---- Common functor definition
|
||||||
|
type baseFunctor struct {
|
||||||
|
info ExprFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
|
||||||
|
if functor.info != nil {
|
||||||
|
s = functor.info.ToString(opt)
|
||||||
|
} else {
|
||||||
|
s = "func() {<body>}"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) SetFunc(info ExprFunc) {
|
||||||
|
functor.info = info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) GetFunc() ExprFunc {
|
||||||
|
return functor.info
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Linking with the functions of Go
|
||||||
|
type golangFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
f FuncTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGolangFunctor(f FuncTemplate) *golangFunctor {
|
||||||
|
return &golangFunctor{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return functor.f(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Linking with the functions of Expr
|
||||||
|
type exprFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
params []string
|
||||||
|
expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExprFunctor(e Expr, params []string) *exprFunctor {
|
||||||
|
return &exprFunctor{expr: e, params: params}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
for i, p := range functor.params {
|
||||||
|
if i < len(args) {
|
||||||
|
arg := args[i]
|
||||||
|
if funcArg, ok := arg.(Functor); ok {
|
||||||
|
// ctx.RegisterFunc(p, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParam(paramValue),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p, arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result, err = functor.expr.eval(ctx, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Parameters
|
||||||
|
type paramFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
pfOptional paramFlags = 1 << iota
|
||||||
|
pfRepeat
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcParamInfo struct {
|
||||||
|
name string
|
||||||
|
flags paramFlags
|
||||||
|
defaultValue any
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParam(name string) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name, flags: flags}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Name() string {
|
||||||
|
return param.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Type() string {
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsOptional() bool {
|
||||||
|
return (param.flags & pfOptional) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsRepeat() bool {
|
||||||
|
return (param.flags & pfRepeat) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) DefaultValue() any {
|
||||||
|
return param.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Functions
|
||||||
|
type funcInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
params []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
if params != nil {
|
||||||
|
for _, p := range params {
|
||||||
|
if maxArgs == -1 {
|
||||||
|
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsOptional() {
|
||||||
|
maxArgs++
|
||||||
|
} else if maxArgs == minArgs {
|
||||||
|
minArgs++
|
||||||
|
maxArgs++
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
maxArgs = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info = &funcInfo{
|
||||||
|
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||||
|
}
|
||||||
|
functor.SetFunc(info)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
return newFuncInfo("unnamed", functor, returnType, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Params() []ExprFuncParam {
|
||||||
|
return info.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ReturnType() string {
|
||||||
|
return info.returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if len(info.Name()) == 0 {
|
||||||
|
sb.WriteString("func")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(info.Name())
|
||||||
|
}
|
||||||
|
sb.WriteByte('(')
|
||||||
|
if info.params != nil {
|
||||||
|
for i, p := range info.params {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(p.Name())
|
||||||
|
|
||||||
|
if p.IsOptional() {
|
||||||
|
sb.WriteByte('=')
|
||||||
|
if s, ok := p.DefaultValue().(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.maxArgs < 0 {
|
||||||
|
sb.WriteString(" ...")
|
||||||
|
}
|
||||||
|
sb.WriteString("): ")
|
||||||
|
if len(info.returnType) > 0 {
|
||||||
|
sb.WriteString(info.returnType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(typeAny)
|
||||||
|
}
|
||||||
|
sb.WriteString(" {<body>}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Name() string {
|
||||||
|
return info.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MinArgs() int {
|
||||||
|
return info.minArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MaxArgs() int {
|
||||||
|
return info.maxArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Functor() Functor {
|
||||||
|
return info.functor
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// global-context.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
var globalCtx *SimpleStore
|
||||||
|
|
||||||
|
func ImportInContext(name string) (exists bool) {
|
||||||
|
var mod *module
|
||||||
|
if mod, exists = moduleRegister[name]; exists {
|
||||||
|
mod.importFunc(globalCtx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContextByGlobPattern(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(globalCtx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVar(ctx ExprContext, name string) (value any, exists bool) {
|
||||||
|
if value, exists = ctx.GetVar(name); !exists {
|
||||||
|
value, exists = globalCtx.GetVar(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
|
||||||
|
if item, exists = ctx.GetFuncInfo(name); exists {
|
||||||
|
ownerCtx = ctx
|
||||||
|
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
|
||||||
|
ownerCtx = globalCtx
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
globalCtx = NewSimpleStore()
|
||||||
|
ImportBuiltinsFuncs(globalCtx)
|
||||||
|
}
|
||||||
+29
-3
@@ -6,6 +6,8 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EvalStringV(source string, args []Arg) (result any, err error) {
|
func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if isFunc(arg.Value) {
|
if isFunc(arg.Value) {
|
||||||
if f, ok := arg.Value.(FuncTemplate); ok {
|
if f, ok := arg.Value.(FuncTemplate); ok {
|
||||||
functor := &simpleFunctor{f: f}
|
functor := newGolangFunctor(f)
|
||||||
ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||||
}
|
}
|
||||||
@@ -59,3 +64,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
|
||||||
|
}
|
||||||
|
|||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// iter-list.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListIterator struct {
|
||||||
|
a *ListType
|
||||||
|
count int
|
||||||
|
index int
|
||||||
|
start int
|
||||||
|
stop int
|
||||||
|
step int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListIterator(list *ListType, args []any) (it *ListIterator) {
|
||||||
|
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 NewArrayIterator(array []any) (it *ListIterator) {
|
||||||
|
it = &ListIterator{a: (*ListType)(&array), count: 0, index: -1, start: 0, stop: len(array) - 1, step: 1}
|
||||||
|
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 {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Next() (item any, err error) {
|
||||||
|
it.index += it.step
|
||||||
|
if item, err = it.Current(); err != io.EOF {
|
||||||
|
it.count++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Index() int {
|
||||||
|
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)
|
||||||
|
|
||||||
+25
-23
@@ -4,37 +4,39 @@
|
|||||||
// iterator.go
|
// iterator.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "io"
|
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 {
|
||||||
Reset()
|
|
||||||
Next() (item any, err error) // must return io.EOF after the last item
|
Next() (item any, err error) // must return io.EOF after the last item
|
||||||
|
Current() (item any, err error)
|
||||||
Index() int
|
Index() int
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlatArrayIterator struct {
|
type ExtIterator interface {
|
||||||
a []any
|
Iterator
|
||||||
index int
|
HasOperation(name string) bool
|
||||||
|
CallOperation(name string, args []any) (value any, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
|
func errNoOperation(name string) error {
|
||||||
return &FlatArrayIterator{a: array, index: 0}
|
return fmt.Errorf("no %q function defined in the data-source", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Reset() {
|
func errInvalidDataSource() error {
|
||||||
it.index = 0
|
return errors.New("invalid data-source")
|
||||||
}
|
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Next() (item any, err error) {
|
|
||||||
if it.index < len(it.a) {
|
|
||||||
item = it.a[it.index]
|
|
||||||
it.index++
|
|
||||||
} else {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Index() int {
|
|
||||||
return it.index - 1
|
|
||||||
}
|
}
|
||||||
|
|||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// list-type.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListType []any
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) ToString(opt FmtOpt) (s 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(']')
|
||||||
|
s = sb.String()
|
||||||
|
if opt&Truncate != 0 && len(s) > TruncateSize {
|
||||||
|
s = TruncateString(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) String() string {
|
||||||
|
return ls.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) TypeName() string {
|
||||||
|
return "list"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *ListType) indexDeepCmp(target any) (index int) {
|
||||||
|
index = -1
|
||||||
|
for i, item := range *list {
|
||||||
|
if reflect.DeepEqual(item, target) {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) contains(t *ListType) (answer bool) {
|
||||||
|
if len(*ls) >= len(*t) {
|
||||||
|
answer = true
|
||||||
|
for _, item := range *t {
|
||||||
|
if answer = ls.indexDeepSameCmp(item) >= 0; !answer {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *ListType) indexDeepSameCmp(target any) (index int) {
|
||||||
|
var eq bool
|
||||||
|
var err error
|
||||||
|
index = -1
|
||||||
|
for i, item := range *list {
|
||||||
|
if eq, err = deepSame(item, target, sameContent); err != nil {
|
||||||
|
break
|
||||||
|
} else if eq {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameContent(a, b any) (same bool, err error) {
|
||||||
|
la, _ := a.(*ListType)
|
||||||
|
lb, _ := b.(*ListType)
|
||||||
|
if len(*la) == len(*lb) {
|
||||||
|
same = true
|
||||||
|
for _, item := range *la {
|
||||||
|
if pos := lb.indexDeepSameCmp(item); pos < 0 {
|
||||||
|
same = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepSame(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
|
||||||
|
if isNumOrFract(a) && isNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
|
if IsInteger(a) && IsInteger(b) {
|
||||||
|
li, _ := a.(int64)
|
||||||
|
ri, _ := b.(int64)
|
||||||
|
eq = li == ri
|
||||||
|
} else {
|
||||||
|
eq = numAsFloat(a) == numAsFloat(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cmp int
|
||||||
|
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||||
|
eq = cmp == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if deepCmp != nil && IsList(a) && IsList(b) {
|
||||||
|
eq, err = deepCmp(a, b)
|
||||||
|
} else {
|
||||||
|
eq = reflect.DeepEqual(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// module-register.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// operand-const.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
// -------- bool const term
|
|
||||||
func newBoolTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
// class: classConst,
|
|
||||||
// kind: kindBool,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- integer const term
|
|
||||||
func newIntegerTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- float const term
|
|
||||||
func newFloatTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
// class: classConst,
|
|
||||||
// kind: kindFloat,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- string const term
|
|
||||||
func newStringTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
// class: classConst,
|
|
||||||
// kind: kindString,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- eval func
|
|
||||||
func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
v = self.tk.Value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
|
||||||
func init() {
|
|
||||||
registerTermConstructor(SymString, newStringTerm)
|
|
||||||
registerTermConstructor(SymInteger, newIntegerTerm)
|
|
||||||
registerTermConstructor(SymFloat, newFloatTerm)
|
|
||||||
registerTermConstructor(SymBool, newBoolTerm)
|
|
||||||
}
|
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-dict.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DictType map[any]any
|
||||||
|
|
||||||
|
func newDict(dictAny map[any]*term) (dict *DictType) {
|
||||||
|
var d DictType
|
||||||
|
if dictAny != nil {
|
||||||
|
d = make(DictType, len(dictAny))
|
||||||
|
for i, item := range dictAny {
|
||||||
|
d[i] = item
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = make(DictType)
|
||||||
|
}
|
||||||
|
dict = &d
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("{\n")
|
||||||
|
|
||||||
|
first := true
|
||||||
|
for name, value := range *dict {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
if key, ok := name.(string); ok {
|
||||||
|
sb.WriteString(string('"') + key + string('"'))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", name))
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(f.ToString(MultiLine))
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
sb.WriteString("func(){}")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("\n}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if opt&MultiLine != 0 {
|
||||||
|
dict.toMultiLine(&sb, 0)
|
||||||
|
} else {
|
||||||
|
sb.WriteByte('{')
|
||||||
|
first := true
|
||||||
|
for key, value := range *dict {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
if s, ok := key.(string); ok {
|
||||||
|
sb.WriteString(string('"') + s + string('"'))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", key))
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(opt))
|
||||||
|
} else if t, ok := value.(*term); ok {
|
||||||
|
sb.WriteString(t.String())
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%#v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte('}')
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) String() string {
|
||||||
|
return dict.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) TypeName() string {
|
||||||
|
return "dict"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) hasKey(target any) (ok bool) {
|
||||||
|
for key := range *dict {
|
||||||
|
if ok = reflect.DeepEqual(key, target); ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) clone() (c *DictType) {
|
||||||
|
c = newDict(nil)
|
||||||
|
for k, v := range *dict {
|
||||||
|
(*c)[k] = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) merge(second *DictType) {
|
||||||
|
if second != nil {
|
||||||
|
for k, v := range *second {
|
||||||
|
(*dict)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- dict term
|
||||||
|
func newDictTerm(args map[any]*term) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *NewValueToken(0, 0, SymDict, "{}", args),
|
||||||
|
parent: nil,
|
||||||
|
children: nil,
|
||||||
|
position: posLeaf,
|
||||||
|
priority: priValue,
|
||||||
|
evalFunc: evalDict,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- dict func
|
||||||
|
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
dict, _ := self.value().(map[any]*term)
|
||||||
|
items := make(DictType, len(dict))
|
||||||
|
for key, tree := range dict {
|
||||||
|
var param any
|
||||||
|
if param, err = tree.compute(ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
items[key] = param
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = &items
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+4
-8
@@ -7,7 +7,8 @@ package expr
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// -------- expr term
|
// -------- expr term
|
||||||
func newExprTerm(tk *Token) *term {
|
func newExprTerm(root *term) *term {
|
||||||
|
tk := NewValueToken(root.tk.row, root.tk.col, SymExpression, root.source(), root)
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
parent: nil,
|
parent: nil,
|
||||||
@@ -20,15 +21,10 @@ func newExprTerm(tk *Token) *term {
|
|||||||
|
|
||||||
// -------- eval expr
|
// -------- eval expr
|
||||||
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if expr, ok := self.value().(Expr); ok {
|
if expr, ok := self.value().(*term); ok {
|
||||||
v, err = expr.eval(ctx, false)
|
v, err = expr.compute(ctx)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("expression expected, got %T", self.value())
|
err = fmt.Errorf("expression expected, got %T", self.value())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
|
||||||
// func init() {
|
|
||||||
// registerTermConstructor(SymExpression, newExprTerm)
|
|
||||||
// }
|
|
||||||
|
|||||||
+53
-60
@@ -6,14 +6,13 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- function call term
|
// -------- function call term
|
||||||
func newFuncCallTerm(tk *Token, args []*term) *term {
|
func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classVar,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: args,
|
children: args,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
@@ -23,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, owner := GetFuncInfo(ctx, name); exists {
|
||||||
|
if info.MinArgs() > len(params) {
|
||||||
|
err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params))
|
||||||
|
}
|
||||||
|
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
|
||||||
|
err = errTooMuchParams(info.MaxArgs(), len(params))
|
||||||
|
}
|
||||||
|
if err == nil && owner != ctx {
|
||||||
|
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||||
|
ctx.RegisterFuncInfo(info)
|
||||||
|
}
|
||||||
|
} 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 := parentCtx.Clone()
|
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
|
||||||
@@ -35,44 +53,21 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
|||||||
params[i] = param
|
params[i] = param
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if err = checkFunctionCall(ctx, name, params); err == nil {
|
||||||
if v, err = ctx.Call(name, params); err == nil {
|
if v, err = ctx.Call(name, params); err == nil {
|
||||||
exportAll := isEnabled(ctx, control_export_all)
|
exportObjects(parentCtx, ctx)
|
||||||
// Export variables
|
|
||||||
for _, refName := range ctx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
|
||||||
refValue, _ := ctx.GetVar(refName)
|
|
||||||
exportVar(parentCtx, refName, refValue)
|
|
||||||
}
|
|
||||||
// Export functions
|
|
||||||
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
|
|
||||||
if info := ctx.GetFuncInfo(refName); info != nil {
|
|
||||||
exportFunc(parentCtx, refName, info)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportVar(ctx ExprContext, name string, value any) {
|
|
||||||
if name[0] == '@' {
|
|
||||||
name = name[1:]
|
|
||||||
}
|
|
||||||
ctx.setVar(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
|
||||||
if name[0] == '@' {
|
|
||||||
name = name[1:]
|
|
||||||
}
|
|
||||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- function definition term
|
// -------- function definition term
|
||||||
func newFuncDefTerm(tk *Token, args []*term) *term {
|
func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
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,
|
||||||
@@ -81,27 +76,30 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
|||||||
|
|
||||||
// -------- eval func def
|
// -------- eval func def
|
||||||
// TODO
|
// TODO
|
||||||
type funcDefFunctor struct {
|
// type funcDefFunctor struct {
|
||||||
params []string
|
// params []string
|
||||||
expr Expr
|
// expr Expr
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
for i, p := range functor.params {
|
// for i, p := range funcDef.params {
|
||||||
if i < len(args) {
|
// if i < len(args) {
|
||||||
arg := args[i]
|
// arg := args[i]
|
||||||
if functor, ok := arg.(Functor); ok {
|
// if functor, ok := arg.(Functor); ok {
|
||||||
ctx.RegisterFunc(p, functor, 0, -1)
|
// // ctx.RegisterFunc(p, functor, 0, -1)
|
||||||
} else {
|
// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
|
||||||
ctx.setVar(p, arg)
|
// newFuncParam(paramValue),
|
||||||
}
|
// })
|
||||||
} else {
|
// } else {
|
||||||
ctx.setVar(p, nil)
|
// ctx.UnsafeSetVar(p, arg)
|
||||||
}
|
// }
|
||||||
}
|
// } else {
|
||||||
result, err = functor.expr.eval(ctx, false)
|
// ctx.UnsafeSetVar(p, nil)
|
||||||
return
|
// }
|
||||||
}
|
// }
|
||||||
|
// result, err = funcDef.expr.eval(ctx, false)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||||
bodySpec := self.value()
|
bodySpec := self.value()
|
||||||
@@ -109,17 +107,12 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
paramList := make([]string, 0, len(self.children))
|
paramList := make([]string, 0, len(self.children))
|
||||||
for _, param := range self.children {
|
for _, param := range self.children {
|
||||||
paramList = append(paramList, param.source())
|
paramList = append(paramList, param.source())
|
||||||
// if paramName, ok := param.value().(string); ok {
|
}
|
||||||
// paramList = append(paramList, paramName)
|
v = newExprFunctor(expr, paramList)
|
||||||
// } else {
|
// v = &funcDefFunctor{
|
||||||
// err = fmt.Errorf("invalid function definition: formal param nr %d must be an identifier", i+1)
|
// params: paramList,
|
||||||
// break
|
// expr: expr,
|
||||||
// }
|
// }
|
||||||
}
|
|
||||||
v = &funcDefFunctor{
|
|
||||||
params: paramList,
|
|
||||||
expr: expr,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("invalid function definition: the body specification must be an expression")
|
err = errors.New("invalid function definition: the body specification must be an expression")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- iterator term
|
||||||
|
|
||||||
|
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
||||||
|
tk.Sym = SymIterator
|
||||||
|
|
||||||
|
children := make([]*term, 0, 1+len(args))
|
||||||
|
children = append(children, dsTerm)
|
||||||
|
children = append(children, args...)
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: children,
|
||||||
|
position: posLeaf,
|
||||||
|
priority: priValue,
|
||||||
|
evalFunc: evalIterator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
||||||
|
values = make([]any, len(a))
|
||||||
|
for i, t := range a {
|
||||||
|
var value any
|
||||||
|
if value, err = t.compute(ctx); err == nil {
|
||||||
|
values[i] = value
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
|
||||||
|
if len(self.children) < 1 || self.children[0] == nil {
|
||||||
|
err = self.Errorf("missing the data-source parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err = self.children[0].compute(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
||||||
|
// if dictAny, ok := firstChildValue.(map[any]any); ok {
|
||||||
|
if dictAny, ok := firstChildValue.(*DictType); ok {
|
||||||
|
requiredFields := []string{currentName, nextName}
|
||||||
|
fieldsMask := 0b11
|
||||||
|
foundFields := 0
|
||||||
|
ds = make(map[string]Functor)
|
||||||
|
for keyAny, item := range *dictAny {
|
||||||
|
if key, ok := keyAny.(string); ok {
|
||||||
|
//if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
|
if functor, ok := item.(Functor); ok {
|
||||||
|
ds[key] = functor
|
||||||
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
|
foundFields |= 1 << index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check required functions
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var firstChildValue any
|
||||||
|
var ds map[string]Functor
|
||||||
|
|
||||||
|
if firstChildValue, err = evalFirstChild(ctx, self); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds != nil {
|
||||||
|
dc := newDataCursor(ctx, ds)
|
||||||
|
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
|
||||||
|
}
|
||||||
+5
-33
@@ -6,12 +6,12 @@ package expr
|
|||||||
|
|
||||||
// -------- list term
|
// -------- list term
|
||||||
func newListTermA(args ...*term) *term {
|
func newListTermA(args ...*term) *term {
|
||||||
return newListTerm(args)
|
return newListTerm(0, 0, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListTerm(args []*term) *term {
|
func newListTerm(row, col int, args []*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *NewValueToken(0, 0, SymList, "[]", args),
|
tk: *NewValueToken(row, col, SymList, "[]", args),
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: nil,
|
children: nil,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
@@ -23,7 +23,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 +32,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
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-literal.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- literal term
|
||||||
|
func newLiteralTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: nil,
|
||||||
|
position: posLeaf,
|
||||||
|
priority: priValue,
|
||||||
|
evalFunc: evalLiteral,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval func
|
||||||
|
func evalLiteral(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
v = self.tk.Value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymString, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymInteger, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymFloat, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymFraction, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymBool, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymKwNil, newLiteralTerm)
|
||||||
|
}
|
||||||
+10
-3
@@ -8,7 +8,7 @@ import "fmt"
|
|||||||
|
|
||||||
// -------- variable term
|
// -------- variable term
|
||||||
func newVarTerm(tk *Token) *term {
|
func newVarTerm(tk *Token) *term {
|
||||||
return &term{
|
t := &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classVar,
|
// class: classVar,
|
||||||
// kind: kindUnknown,
|
// kind: kindUnknown,
|
||||||
@@ -18,13 +18,20 @@ func newVarTerm(tk *Token) *term {
|
|||||||
priority: priValue,
|
priority: priValue,
|
||||||
evalFunc: evalVar,
|
evalFunc: evalVar,
|
||||||
}
|
}
|
||||||
|
t.tk.Sym = SymVariable
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
func evalVar(ctx ExprContext, self *term) (v any, err error) {
|
func evalVar(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var exists bool
|
var exists bool
|
||||||
if v, exists = ctx.GetVar(self.tk.source); !exists {
|
name := self.source()
|
||||||
err = fmt.Errorf("undefined variable %q", self.tk.source)
|
if v, exists = GetVar(ctx, name); !exists {
|
||||||
|
if info, exists, _ := GetFuncInfo(ctx, name); exists {
|
||||||
|
v = info.Functor()
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("undefined variable or function %q", name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-5
@@ -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
|
||||||
@@ -22,16 +22,32 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
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)
|
funcName := rightChild.source()
|
||||||
|
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
|
||||||
|
// ctx.RegisterFuncInfo(info)
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
|
||||||
|
} else if funcDef, ok := functor.(*exprFunctor); ok {
|
||||||
|
paramSpecs := ForAll(funcDef.params, newFuncParam)
|
||||||
|
// paramCount := len(funcDef.params)
|
||||||
|
// paramSpecs := make([]ExprFuncParam, paramCount)
|
||||||
|
// for i := range paramSpecs {
|
||||||
|
// paramSpecs[i] = newFuncParam(funcDef.params[i])
|
||||||
|
// }
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs)
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.tk.source, v)
|
err = self.Errorf("unknown function %s()", funcName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
+18
-14
@@ -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,36 +19,38 @@ 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(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(module) {
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("unknown module %q", module)
|
err = self.Errorf("unknown module %q", module)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-16
@@ -9,8 +9,6 @@ package expr
|
|||||||
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priCoalesce,
|
priority: priCoalesce,
|
||||||
@@ -26,7 +24,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -34,12 +32,8 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if _, ok := rightValue.(Functor); ok {
|
|
||||||
err = errCoalesceNoFunc(self.children[1])
|
|
||||||
} else {
|
|
||||||
v = rightValue
|
v = rightValue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +57,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -71,21 +65,19 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if _, ok := rightValue.(Functor); ok {
|
if functor, ok := rightValue.(Functor); ok {
|
||||||
err = errCoalesceNoFunc(self.children[1])
|
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
ctx.setVar(leftTerm.source(), rightValue)
|
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// utils
|
|
||||||
func errCoalesceNoFunc(t *term) error {
|
|
||||||
return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-context-value.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- context term
|
||||||
|
|
||||||
|
func newContextTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priIncDec,
|
||||||
|
evalFunc: evalContextValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
|
||||||
|
var sourceCtx ExprContext
|
||||||
|
if self.children == nil || len(self.children) == 0 {
|
||||||
|
sourceCtx = ctx
|
||||||
|
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
|
||||||
|
sourceCtx = globalCtx
|
||||||
|
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
||||||
|
if dc, ok := childValue.(*dataCursor); ok {
|
||||||
|
sourceCtx = dc.ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceCtx != nil {
|
||||||
|
if formatter, ok := sourceCtx.(Formatter); ok {
|
||||||
|
v = formatter.ToString(0)
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymDoubleDollar, newContextTerm)
|
||||||
|
}
|
||||||
+17
-24
@@ -18,40 +18,33 @@ func newDotTerm(tk *Token) (inst *term) {
|
|||||||
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 !isInteger(rightValue) {
|
|
||||||
err = indexTerm.Errorf("index expression must be integer, got %T", rightValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
index64, _ := rightValue.(int64)
|
switch unboxedValue := leftValue.(type) {
|
||||||
index := int(index64)
|
case ExtIterator:
|
||||||
|
if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
|
||||||
if isList(leftValue) {
|
opName := indexTerm.source()
|
||||||
list, _ := leftValue.([]any)
|
if unboxedValue.HasOperation(opName) {
|
||||||
if index >= 0 && index < len(list) {
|
v, err = unboxedValue.CallOperation(opName, []any{})
|
||||||
v = list[index]
|
|
||||||
} else if index >= -len(list) {
|
|
||||||
v = list[len(list)+index]
|
|
||||||
} else {
|
} else {
|
||||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
|
||||||
}
|
v = false
|
||||||
} else if isString(leftValue) {
|
|
||||||
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 {
|
} else {
|
||||||
|
err = indexTerm.tk.ErrorExpectedGot("identifier")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
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,66 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-fraction.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- 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 = &FractionType{num, den}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymVertBar, newFractionTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-in.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- in term
|
||||||
|
|
||||||
|
func newInTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priRelational,
|
||||||
|
evalFunc: evalIn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func hasKey(d map[any]any, target any) (ok bool) {
|
||||||
|
// _, ok = d[target]
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsList(rightValue) {
|
||||||
|
list, _ := rightValue.(*ListType)
|
||||||
|
v = list.indexDeepSameCmp(leftValue) >= 0
|
||||||
|
} else if IsDict(rightValue) {
|
||||||
|
dict, _ := rightValue.(*DictType)
|
||||||
|
v = dict.hasKey(leftValue)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwIn, newInTerm)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-index.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- index term
|
||||||
|
func newIndexTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDot,
|
||||||
|
evalFunc: evalIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
|
||||||
|
index = (*indexList)[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
|
||||||
|
var v int
|
||||||
|
|
||||||
|
if v, err = toInt((*indexList)[0], "index expression"); err == nil {
|
||||||
|
if v < 0 && v >= -maxValue {
|
||||||
|
v = maxValue + v
|
||||||
|
}
|
||||||
|
if v >= 0 && v < maxValue {
|
||||||
|
index = v
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
|
||||||
|
v, _ := ((*indexList)[0]).(*intPair)
|
||||||
|
startIndex = v.a
|
||||||
|
endIndex = v.b
|
||||||
|
if startIndex < 0 && startIndex >= -maxValue {
|
||||||
|
startIndex = maxValue + startIndex
|
||||||
|
}
|
||||||
|
if endIndex < 0 && endIndex >= -maxValue {
|
||||||
|
endIndex = maxValue + endIndex + 1
|
||||||
|
}
|
||||||
|
if startIndex < 0 || startIndex > maxValue {
|
||||||
|
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
|
||||||
|
} else if endIndex < 0 || endIndex > maxValue {
|
||||||
|
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
|
||||||
|
} else if startIndex > endIndex {
|
||||||
|
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
var indexList *ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTerm := self.children[1]
|
||||||
|
if indexList, ok = rightValue.(*ListType); !ok {
|
||||||
|
err = self.Errorf("invalid index expression")
|
||||||
|
return
|
||||||
|
} else if len(*indexList) != 1 {
|
||||||
|
err = indexTerm.Errorf("one index only is allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsInteger((*indexList)[0]) {
|
||||||
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case *ListType:
|
||||||
|
var index int
|
||||||
|
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||||
|
v = (*unboxedValue)[index]
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var index int
|
||||||
|
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||||
|
v = string(unboxedValue[index])
|
||||||
|
}
|
||||||
|
case *DictType:
|
||||||
|
var ok bool
|
||||||
|
var indexValue any
|
||||||
|
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
|
||||||
|
if v, ok = (*unboxedValue)[indexValue]; !ok {
|
||||||
|
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
} else if isIntPair((*indexList)[0]) {
|
||||||
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case *ListType:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||||
|
sublist := ListType((*unboxedValue)[start:end])
|
||||||
|
v = &sublist
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||||
|
v = unboxedValue[start:end]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymIndex, newIndexTerm)
|
||||||
|
}
|
||||||
+32
-6
@@ -33,9 +33,13 @@ 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
|
||||||
|
if self.children[1].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(self.children[1].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -49,15 +53,37 @@ 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
|
||||||
|
if self.children[0].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(self.children[0].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
// var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if IsList(leftValue) {
|
||||||
|
// list, _ := leftValue.(*ListType)
|
||||||
|
// newList := append(*list, rightValue)
|
||||||
|
// v = &newList
|
||||||
|
// if
|
||||||
|
// } else {
|
||||||
|
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymInsert, newInsertTerm)
|
registerTermConstructor(SymInsert, newInsertTerm)
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-iter-value.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- iter value term
|
||||||
|
|
||||||
|
func newIterValueTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priIterValue,
|
||||||
|
evalFunc: evalIterValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
|
||||||
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, ok := childValue.(Iterator); ok {
|
||||||
|
v, err = it.Current()
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
|
||||||
|
}
|
||||||
+20
-12
@@ -17,23 +17,31 @@ 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)
|
ls, _ := childValue.(*ListType)
|
||||||
v = len(list)
|
v = int64(len(*ls))
|
||||||
} else if isString(rightValue) {
|
} else if IsString(childValue) {
|
||||||
s, _ := rightValue.(string)
|
s, _ := childValue.(string)
|
||||||
v = len(s)
|
v = int64(len(s))
|
||||||
// } else {
|
} else if IsDict(childValue) {
|
||||||
// v = 1
|
// m, _ := childValue.(map[any]any)
|
||||||
// }
|
m, _ := childValue.(*DictType)
|
||||||
|
v = int64(len(*m))
|
||||||
|
} else if it, ok := childValue.(Iterator); ok {
|
||||||
|
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
||||||
|
count, _ := extIt.CallOperation(countName, nil)
|
||||||
|
v, _ = toInt(count, "")
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
v = int64(it.Index() + 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-post-inc.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- post increment term
|
||||||
|
|
||||||
|
func newPostIncTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPostfix,
|
||||||
|
priority: priIncDec,
|
||||||
|
evalFunc: evalPostInc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, ok := childValue.(Iterator); ok {
|
||||||
|
v, err = it.Next()
|
||||||
|
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable {
|
||||||
|
v = childValue
|
||||||
|
i, _ := childValue.(int64)
|
||||||
|
ctx.SetVar(self.children[0].source(), i+1)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymDoublePlus, newPostIncTerm)
|
||||||
|
}
|
||||||
+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")
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-range.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// -------- range term
|
||||||
|
type intPair struct {
|
||||||
|
a, b int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *intPair) TypeName() string {
|
||||||
|
return typePair
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *intPair) ToString(opt FmtOpt) string {
|
||||||
|
return fmt.Sprintf("(%d, %d)", p.a, p.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIntPair(v any) bool {
|
||||||
|
_, ok := v.(*intPair)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRangeTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDot,
|
||||||
|
evalFunc: evalRange,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalRange(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if err = self.checkOperands(); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
if len(self.children) == 0 {
|
||||||
|
leftValue = int64(0)
|
||||||
|
rightValue = int64(-1)
|
||||||
|
} else if len(self.children) == 1 {
|
||||||
|
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rightValue = int64(-1)
|
||||||
|
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex, _ := leftValue.(int64)
|
||||||
|
endIndex, _ := rightValue.(int64)
|
||||||
|
|
||||||
|
v = &intPair{int(startIndex), int(endIndex)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymColon, newRangeTerm)
|
||||||
|
}
|
||||||
+89
-87
@@ -4,13 +4,13 @@
|
|||||||
// operator-rel.go
|
// operator-rel.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
//-------- equal term
|
//-------- equal term
|
||||||
|
|
||||||
func newEqualTerm(tk *Token) (inst *term) {
|
func newEqualTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -18,6 +18,33 @@ func newEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type deepFuncTemplate func(a, b any) (eq bool, err error)
|
||||||
|
|
||||||
|
func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
|
||||||
|
if isNumOrFract(a) && isNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
|
if IsInteger(a) && IsInteger(b) {
|
||||||
|
li, _ := a.(int64)
|
||||||
|
ri, _ := b.(int64)
|
||||||
|
eq = li == ri
|
||||||
|
} else {
|
||||||
|
eq = numAsFloat(a) == numAsFloat(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cmp int
|
||||||
|
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||||
|
eq = cmp == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if deepCmp != nil && IsList(a) && IsList(b) {
|
||||||
|
eq, err = deepCmp(a, b)
|
||||||
|
} else {
|
||||||
|
eq = reflect.DeepEqual(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
@@ -25,21 +52,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
v, err = equals(leftValue, rightValue, nil)
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
|
||||||
li, _ := leftValue.(int64)
|
|
||||||
ri, _ := rightValue.(int64)
|
|
||||||
v = li == ri
|
|
||||||
} else {
|
|
||||||
v = numAsFloat(leftValue) == numAsFloat(rightValue)
|
|
||||||
}
|
|
||||||
} else if isString(leftValue) && isString(rightValue) {
|
|
||||||
ls, _ := leftValue.(string)
|
|
||||||
rs, _ := rightValue.(string)
|
|
||||||
v = ls == rs
|
|
||||||
} else {
|
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +61,6 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newNotEqualTerm(tk *Token) (inst *term) {
|
func newNotEqualTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -65,38 +76,11 @@ func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
|
|
||||||
// var leftValue, rightValue any
|
|
||||||
|
|
||||||
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if isNumber(leftValue) && isNumber(rightValue) {
|
|
||||||
// if isInteger(leftValue) && isInteger(rightValue) {
|
|
||||||
// li, _ := leftValue.(int64)
|
|
||||||
// ri, _ := rightValue.(int64)
|
|
||||||
// v = li != ri
|
|
||||||
// } else {
|
|
||||||
// v = numAsFloat(leftValue) != numAsFloat(rightValue)
|
|
||||||
// }
|
|
||||||
// } else if isString(leftValue) && isString(rightValue) {
|
|
||||||
// ls, _ := leftValue.(string)
|
|
||||||
// rs, _ := rightValue.(string)
|
|
||||||
// v = ls != rs
|
|
||||||
// } else {
|
|
||||||
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
//-------- less term
|
//-------- less term
|
||||||
|
|
||||||
func newLessTerm(tk *Token) (inst *term) {
|
func newLessTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -104,28 +88,44 @@ func newLessTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lessThan(self *term, a, b any) (isLess bool, err error) {
|
||||||
|
if isNumOrFract(a) && isNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
|
if IsInteger(a) && IsInteger(b) {
|
||||||
|
li, _ := a.(int64)
|
||||||
|
ri, _ := b.(int64)
|
||||||
|
isLess = li < ri
|
||||||
|
} else {
|
||||||
|
isLess = numAsFloat(a) < numAsFloat(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cmp int
|
||||||
|
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||||
|
isLess = cmp < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if IsString(a) && IsString(b) {
|
||||||
|
ls, _ := a.(string)
|
||||||
|
rs, _ := b.(string)
|
||||||
|
isLess = ls < rs
|
||||||
|
// Inclusion test
|
||||||
|
} else if IsList(a) && IsList(b) {
|
||||||
|
aList, _ := a.(*ListType)
|
||||||
|
bList, _ := b.(*ListType)
|
||||||
|
isLess = len(*aList) < len(*bList) && bList.contains(aList)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(a, b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalLess(ctx ExprContext, self *term) (v any, err error) {
|
func evalLess(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 leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
v, err = lessThan(self, leftValue, rightValue)
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
|
||||||
li, _ := leftValue.(int64)
|
|
||||||
ri, _ := rightValue.(int64)
|
|
||||||
v = li < ri
|
|
||||||
} else {
|
|
||||||
v = numAsFloat(leftValue) < numAsFloat(rightValue)
|
|
||||||
}
|
|
||||||
} else if isString(leftValue) && isString(rightValue) {
|
|
||||||
ls, _ := leftValue.(string)
|
|
||||||
rs, _ := rightValue.(string)
|
|
||||||
v = ls < rs
|
|
||||||
} else {
|
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +141,19 @@ func newLessEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
|
||||||
|
if isLessEq, err = lessThan(self, a, b); err == nil {
|
||||||
|
if !isLessEq {
|
||||||
|
if IsList(a) && IsList(b) {
|
||||||
|
isLessEq, err = sameContent(a, b)
|
||||||
|
} else {
|
||||||
|
isLessEq, err = equals(a, b, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
@@ -148,21 +161,8 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
v, err = lessThanOrEqual(self, leftValue, rightValue)
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
|
||||||
li, _ := leftValue.(int64)
|
|
||||||
ri, _ := rightValue.(int64)
|
|
||||||
v = li <= ri
|
|
||||||
} else {
|
|
||||||
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
|
|
||||||
}
|
|
||||||
} else if isString(leftValue) && isString(rightValue) {
|
|
||||||
ls, _ := leftValue.(string)
|
|
||||||
rs, _ := rightValue.(string)
|
|
||||||
v = ls <= rs
|
|
||||||
} else {
|
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +171,6 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newGreaterTerm(tk *Token) (inst *term) {
|
func newGreaterTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -181,10 +179,13 @@ func newGreaterTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if v, err = evalLessEqual(ctx, self); err == nil {
|
var leftValue, rightValue any
|
||||||
b, _ := toBool(v)
|
|
||||||
v = !b
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v, err = lessThan(self, rightValue, leftValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +194,6 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newGreaterEqualTerm(tk *Token) (inst *term) {
|
func newGreaterEqualTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -203,10 +202,13 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
|
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if v, err = evalLess(ctx, self); err == nil {
|
var leftValue, rightValue any
|
||||||
b, _ := toBool(v)
|
|
||||||
v = !b
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v, err = lessThanOrEqual(self, rightValue, leftValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-63
@@ -4,65 +4,7 @@
|
|||||||
// operator-selector.go
|
// operator-selector.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// //-------- export all term
|
//-------- selector term
|
||||||
|
|
||||||
// func newSelectorTerm(tk *Token) (inst *term) {
|
|
||||||
// return &term{
|
|
||||||
// tk: *tk,
|
|
||||||
// children: make([]*term, 0, 3),
|
|
||||||
// position: posMultifix,
|
|
||||||
// priority: priSelector,
|
|
||||||
// evalFunc: evalSelector,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
|
|
||||||
// caseData, _ := caseSel.(*selectorCase)
|
|
||||||
// if caseData.filterList == nil {
|
|
||||||
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
|
||||||
// } else {
|
|
||||||
// filterList := caseData.filterList.children
|
|
||||||
// if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
|
||||||
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
|
||||||
// } else {
|
|
||||||
// var caseValue any
|
|
||||||
// for _, caseTerm := range filterList {
|
|
||||||
// if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
|
||||||
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
// var exprValue any
|
|
||||||
// // var caseList []*term
|
|
||||||
|
|
||||||
// if err = self.checkOperands(); err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// exprTerm := self.children[0]
|
|
||||||
// if exprValue, err = exprTerm.compute(ctx); err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// caseList := self.children[1:]
|
|
||||||
// for i, caseTerm := range caseList {
|
|
||||||
// caseSel := caseTerm.value()
|
|
||||||
// if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if err == nil && v == nil {
|
|
||||||
// err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
//-------- export all term
|
|
||||||
|
|
||||||
func newSelectorTerm(tk *Token) (inst *term) {
|
func newSelectorTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -74,18 +16,21 @@ func newSelectorTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
|
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
|
||||||
caseData, _ := caseSel.(*selectorCase)
|
caseData, _ := caseSel.(*selectorCase)
|
||||||
if caseData.filterList == nil {
|
if caseData.filterList == nil {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||||
|
match = true
|
||||||
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
||||||
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||||
|
match = true
|
||||||
} else {
|
} else {
|
||||||
var caseValue any
|
var caseValue any
|
||||||
for _, caseTerm := range filterList {
|
for _, caseTerm := range filterList {
|
||||||
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||||
|
match = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +41,7 @@ func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (sel
|
|||||||
|
|
||||||
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var exprValue any
|
var exprValue any
|
||||||
// var caseList []*term
|
var match bool
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
if err = self.checkOperands(); err != nil {
|
||||||
return
|
return
|
||||||
@@ -110,11 +55,11 @@ func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
caseList, _ := caseListTerm.value().([]*term)
|
caseList, _ := caseListTerm.value().([]*term)
|
||||||
for i, caseTerm := range caseList {
|
for i, caseTerm := range caseList {
|
||||||
caseSel := caseTerm.value()
|
caseSel := caseTerm.value()
|
||||||
if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
|
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil && v == nil {
|
if err == nil && !match {
|
||||||
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+35
-25
@@ -28,33 +28,41 @@ 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
|
leftList, _ = leftValue.(*ListType)
|
||||||
if leftList, ok = leftValue.([]any); !ok {
|
rightList, _ = rightValue.(*ListType)
|
||||||
leftList = []any{leftValue}
|
|
||||||
}
|
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||||
if rightList, ok = rightValue.([]any); !ok {
|
for _, item := range *leftList {
|
||||||
rightList = []any{rightValue}
|
|
||||||
}
|
|
||||||
sumList := make([]any, 0, len(leftList)+len(rightList))
|
|
||||||
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) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
|
||||||
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
|
} else {
|
||||||
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
} else if IsDict(leftValue) && IsDict(rightValue) {
|
||||||
|
leftDict, _ := leftValue.(*DictType)
|
||||||
|
rightDict, _ := rightValue.(*DictType)
|
||||||
|
c := leftDict.clone()
|
||||||
|
c.merge(rightDict)
|
||||||
|
v = c
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -80,24 +88,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,34 +58,94 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||||
|
// // Example: "add = func(x,y) {x+y}
|
||||||
|
// var body *ast
|
||||||
|
// args := make([]*term, 0)
|
||||||
|
// lastSym := SymUnknown
|
||||||
|
// itemExpected := false
|
||||||
|
// tk := scanner.Previous()
|
||||||
|
// for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
|
// var subTree *ast
|
||||||
|
// if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
|
||||||
|
// if subTree.root != nil {
|
||||||
|
// if subTree.root.symbol() == SymIdentifier {
|
||||||
|
// args = append(args, subTree.root)
|
||||||
|
// } else {
|
||||||
|
// err = tk.ErrorExpectedGotString("param-name", subTree.root.String())
|
||||||
|
// }
|
||||||
|
// } else if itemExpected {
|
||||||
|
// prev := scanner.Previous()
|
||||||
|
// err = prev.ErrorExpectedGot("function-param")
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// lastSym = scanner.Previous().Sym
|
||||||
|
// itemExpected = lastSym == SymComma
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err == nil && lastSym != SymClosedRound {
|
||||||
|
// err = tk.ErrorExpectedGot(")")
|
||||||
|
// }
|
||||||
|
// if err == nil {
|
||||||
|
// tk = scanner.Next()
|
||||||
|
// if tk.Sym == SymOpenBrace {
|
||||||
|
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||||
|
// } else {
|
||||||
|
// err = tk.ErrorExpectedGot("{")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if err == nil {
|
||||||
|
// // TODO Check arguments
|
||||||
|
// if scanner.Previous().Sym != SymClosedBrace {
|
||||||
|
// err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
|
// } else {
|
||||||
|
// tk = scanner.makeValueToken(SymExpression, "", body)
|
||||||
|
// tree = newFuncDefTerm(tk, args)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||||
// Example: "add = func(x,y) {x+y}
|
// Example: "add = func(x,y) {x+y}
|
||||||
var body *ast
|
var body *ast
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
tk := scanner.Next()
|
lastSym := SymUnknown
|
||||||
for tk.Sym != SymClosedRound && tk.Sym != SymEos {
|
itemExpected := false
|
||||||
if tk.Sym == SymIdentifier {
|
tk := scanner.Previous()
|
||||||
t := newTerm(tk, nil)
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
args = append(args, t)
|
tk = scanner.Next()
|
||||||
} else {
|
if tk.IsSymbol(SymIdentifier) {
|
||||||
err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
param := newTerm(tk)
|
||||||
|
args = append(args, param)
|
||||||
|
tk = scanner.Next()
|
||||||
|
} else if itemExpected {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.ErrorExpectedGot("function-param")
|
||||||
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.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)
|
||||||
@@ -94,26 +154,148 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
|
||||||
|
r, c := scanner.lastPos()
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
|
itemExpected := false
|
||||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
|
zeroRequired := scanner.current.Sym == SymColon
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
||||||
if subTree.root != nil {
|
root := subTree.root
|
||||||
args = append(args, subTree.root)
|
if root != nil {
|
||||||
|
if !parsingIndeces && root.symbol() == SymColon {
|
||||||
|
err = root.Errorf("unexpected range expression")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args = append(args, root)
|
||||||
|
if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
|
||||||
|
if len(root.children) == 1 {
|
||||||
|
root.children = append(root.children, root.children[0])
|
||||||
|
} else if len(root.children) > 1 {
|
||||||
|
err = root.Errorf("invalid range specification")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
|
||||||
|
zeroTerm := newTerm(zeroTk)
|
||||||
|
zeroTerm.setParent(root)
|
||||||
|
root.children[0] = zeroTerm
|
||||||
|
}
|
||||||
|
} else if itemExpected {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.ErrorExpectedGot("list-item")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lastSym = scanner.Previous().Sym
|
lastSym = scanner.Previous().Sym
|
||||||
|
itemExpected = lastSym == SymComma
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if lastSym != SymClosedSquare {
|
||||||
|
err = scanner.Previous().ErrorExpectedGot("]")
|
||||||
|
} else {
|
||||||
|
subtree = newListTerm(r, c, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
|
tk := scanner.Previous()
|
||||||
|
args := make([]*term, 0)
|
||||||
|
lastSym := SymUnknown
|
||||||
|
itemExpected := false
|
||||||
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
|
var subTree *ast
|
||||||
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
||||||
|
if subTree.root != nil {
|
||||||
|
args = append(args, subTree.root)
|
||||||
|
} else if itemExpected {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.ErrorExpectedGot("iterator-param")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastSym = scanner.Previous().Sym
|
||||||
|
itemExpected = lastSym == SymComma
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
// TODO Check arguments
|
||||||
if lastSym != SymClosedSquare {
|
if lastSym != SymClosedRound {
|
||||||
err = scanner.Previous().Errorf("unterminate items list")
|
err = scanner.Previous().ErrorExpectedGot(")")
|
||||||
} else {
|
} else {
|
||||||
subtree = newListTerm(args)
|
subtree = newIteratorTerm(tk, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
|
||||||
|
tk := scanner.Next()
|
||||||
|
if tk.Sym == SymError {
|
||||||
|
err = tk.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tk.Sym == SymInteger || tk.Sym == SymString {
|
||||||
|
tkSep := scanner.Next()
|
||||||
|
if tkSep.Sym != SymColon {
|
||||||
|
err = tkSep.ErrorExpectedGot(":")
|
||||||
|
} else {
|
||||||
|
key = tk.Value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
||||||
|
err = tk.ErrorExpectedGot("dictionary-key or }")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
|
args := make(map[any]*term, 0)
|
||||||
|
lastSym := SymUnknown
|
||||||
|
itemExpected := false
|
||||||
|
for lastSym != SymClosedBrace && lastSym != SymEos {
|
||||||
|
var subTree *ast
|
||||||
|
var key any
|
||||||
|
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
|
||||||
|
break
|
||||||
|
} else if key == nil {
|
||||||
|
tk := scanner.Previous()
|
||||||
|
lastSym = tk.Sym
|
||||||
|
if itemExpected {
|
||||||
|
err = tk.ErrorExpectedGot("dictionary-key")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
|
||||||
|
if subTree.root != nil {
|
||||||
|
args[key] = subTree.root
|
||||||
|
} else if key != nil {
|
||||||
|
prev := scanner.Previous()
|
||||||
|
err = prev.ErrorExpectedGot("dictionary-value")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastSym = scanner.Previous().Sym
|
||||||
|
itemExpected = lastSym == SymComma
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// TODO Check arguments
|
||||||
|
if lastSym != SymClosedBrace {
|
||||||
|
err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
|
} else {
|
||||||
|
subtree = newDictTerm(args)
|
||||||
|
// subtree = newMapTerm(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -130,14 +312,14 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
|||||||
err = tk.Errorf("case list in default clause")
|
err = tk.Errorf("case list in default clause")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
|
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
startRow = tk.row
|
startRow = tk.row
|
||||||
startCol = tk.col
|
startCol = tk.col
|
||||||
} else if !defaultCase {
|
} else if !defaultCase {
|
||||||
filterList = newListTerm(make([]*term, 0))
|
filterList = newListTerm(startRow, startCol, make([]*term, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
if tk.Sym == SymOpenBrace {
|
if tk.Sym == SymOpenBrace {
|
||||||
@@ -145,7 +327,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 {
|
||||||
@@ -188,6 +370,14 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
|
|||||||
return self.parseGeneral(scanner, true, false, termSymbols...)
|
return self.parseGeneral(scanner, true, false, termSymbols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func couldBeACollection(t *term) bool {
|
||||||
|
var sym = SymUnknown
|
||||||
|
if t != nil {
|
||||||
|
sym = t.symbol()
|
||||||
|
}
|
||||||
|
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
|
||||||
|
}
|
||||||
|
|
||||||
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
var selectorTerm *term = nil
|
var selectorTerm *term = nil
|
||||||
var currentTerm *term = nil
|
var currentTerm *term = nil
|
||||||
@@ -226,7 +416,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
||||||
subTree.root.priority = priValue
|
subTree.root.priority = priValue
|
||||||
err = tree.addTerm(subTree.root)
|
err = tree.addTerm(newExprTerm(subTree.root))
|
||||||
currentTerm = subTree.root
|
currentTerm = subTree.root
|
||||||
}
|
}
|
||||||
case SymFuncCall:
|
case SymFuncCall:
|
||||||
@@ -237,10 +427,29 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
}
|
}
|
||||||
case SymOpenSquare:
|
case SymOpenSquare:
|
||||||
var listTerm *term
|
var listTerm *term
|
||||||
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
|
parsingIndeces := couldBeACollection(currentTerm)
|
||||||
|
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
|
||||||
|
if parsingIndeces {
|
||||||
|
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
|
||||||
|
indexTerm := newTerm(indexTk)
|
||||||
|
if err = tree.addTerm(indexTerm); err == nil {
|
||||||
err = tree.addTerm(listTerm)
|
err = tree.addTerm(listTerm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = tree.addTerm(listTerm)
|
||||||
|
}
|
||||||
currentTerm = listTerm
|
currentTerm = listTerm
|
||||||
}
|
}
|
||||||
|
case SymOpenBrace:
|
||||||
|
if currentTerm != nil && currentTerm.symbol() == SymColon {
|
||||||
|
err = currentTerm.Errorf(`selector-case outside of a selector context`)
|
||||||
|
} else {
|
||||||
|
var mapTerm *term
|
||||||
|
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||||
|
err = tree.addTerm(mapTerm)
|
||||||
|
currentTerm = mapTerm
|
||||||
|
}
|
||||||
|
}
|
||||||
case SymEqual:
|
case SymEqual:
|
||||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
@@ -251,6 +460,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
err = tree.addTerm(funcDefTerm)
|
err = tree.addTerm(funcDefTerm)
|
||||||
currentTerm = funcDefTerm
|
currentTerm = funcDefTerm
|
||||||
}
|
}
|
||||||
|
case SymDollarRound:
|
||||||
|
var iterDefTerm *term
|
||||||
|
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
|
||||||
|
err = tree.addTerm(iterDefTerm)
|
||||||
|
currentTerm = iterDefTerm
|
||||||
|
}
|
||||||
case SymIdentifier:
|
case SymIdentifier:
|
||||||
if tk.source[0] == '@' && !allowVarRef {
|
if tk.source[0] == '@' && !allowVarRef {
|
||||||
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
||||||
@@ -263,15 +478,17 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
}
|
}
|
||||||
case SymColon, SymDoubleColon:
|
case SymColon, SymDoubleColon:
|
||||||
var caseTerm *term
|
var caseTerm *term
|
||||||
if selectorTerm == nil {
|
if selectorTerm != nil {
|
||||||
err = tk.Errorf("selector-case outside of a selector context")
|
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||||
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
|
||||||
addSelectorCase(selectorTerm, caseTerm)
|
addSelectorCase(selectorTerm, caseTerm)
|
||||||
currentTerm = caseTerm
|
currentTerm = caseTerm
|
||||||
if tk.Sym == SymDoubleColon {
|
if tk.Sym == SymDoubleColon {
|
||||||
selectorTerm = nil
|
selectorTerm = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
currentTerm, err = tree.addToken2(tk)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
}
|
}
|
||||||
|
|||||||
-317
@@ -1,317 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// parser_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParser(t *testing.T) {
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
|
||||||
/* 2 */ {`3 == 4`, false, nil},
|
|
||||||
/* 3 */ {`3 != 4`, true, nil},
|
|
||||||
/* 4 */ {`4 == (3-1)*(10/5)`, true, nil},
|
|
||||||
/* 5 */ {`3 <= 4`, true, nil},
|
|
||||||
/* 6 */ {`3 < 4`, true, nil},
|
|
||||||
/* 7 */ {`4 < 3`, false, nil},
|
|
||||||
/* 8 */ {`1+5 < 4`, false, nil},
|
|
||||||
/* 9 */ {`3 > 4`, false, nil},
|
|
||||||
/* 10 */ {`4 >= 4`, true, nil},
|
|
||||||
/* 11 */ {`4 > 3`, true, nil},
|
|
||||||
/* 12 */ {`1+5 > 4`, true, nil},
|
|
||||||
/* 13 */ {`true`, true, nil},
|
|
||||||
/* 14 */ {`not true`, false, nil},
|
|
||||||
/* 15 */ {`true and false`, false, nil},
|
|
||||||
/* 16 */ {`true or false`, true, nil},
|
|
||||||
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
|
|
||||||
/* 18 */ {`"uno" + 2`, `uno2`, nil},
|
|
||||||
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
|
|
||||||
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
|
|
||||||
/* 21 */ {"1", int64(1), nil},
|
|
||||||
/* 22 */ {"1.5", float64(1.5), nil},
|
|
||||||
/* 23 */ {"1.5*2", float64(3.0), nil},
|
|
||||||
/* 24 */ {"+1", int64(1), nil},
|
|
||||||
/* 25 */ {"-1", int64(-1), nil},
|
|
||||||
/* 26 */ {"435 + 105 * 2 - 1", int64(644), nil},
|
|
||||||
/* 27 */ {"200 / 2 - 1", int64(99), nil},
|
|
||||||
/* 28 */ {"(1+1)", int64(2), nil},
|
|
||||||
/* 29 */ {"-(1+1)", int64(-2), nil},
|
|
||||||
/* 30 */ {"-(-2+1)", int64(1), nil},
|
|
||||||
/* 31 */ {"(1+1)*5", int64(10), nil},
|
|
||||||
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
|
|
||||||
/* 33 */ {`add(1,2,3)`, int64(6), nil},
|
|
||||||
/* 34 */ {`mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
|
||||||
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
|
||||||
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
|
||||||
/* 37 */ {`add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
|
||||||
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
|
||||||
/* 39 */ {`(((1)))`, int64(1), nil},
|
|
||||||
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
|
|
||||||
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
|
|
||||||
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
|
||||||
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
|
||||||
/* 44 */ {`false // very simple expression`, false, nil},
|
|
||||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil operands, got 1`)},
|
|
||||||
/* 46 */ {"", nil, errors.New(`empty expression`)},
|
|
||||||
/* 47 */ {"4!", int64(24), nil},
|
|
||||||
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
|
||||||
/* 49 */ {"-4!", int64(-24), nil},
|
|
||||||
/* 50 */ {"1.5 < 7", true, nil},
|
|
||||||
/* 51 */ {"1.5 > 7", false, nil},
|
|
||||||
/* 52 */ {"1.5 <= 7", true, nil},
|
|
||||||
/* 53 */ {"1.5 >= 7", false, nil},
|
|
||||||
/* 54 */ {"1.5 != 7", true, nil},
|
|
||||||
/* 55 */ {"1.5 == 7", false, nil},
|
|
||||||
/* 56 */ {`"1.5" < "7"`, true, nil},
|
|
||||||
/* 57 */ {`"1.5" > "7"`, false, nil},
|
|
||||||
/* 58 */ {`"1.5" == "7"`, false, nil},
|
|
||||||
/* 59 */ {`"1.5" != "7"`, true, nil},
|
|
||||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)},
|
|
||||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)},
|
|
||||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)},
|
|
||||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)},
|
|
||||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)},
|
|
||||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)},
|
|
||||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)},
|
|
||||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)},
|
|
||||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)},
|
|
||||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)},
|
|
||||||
/* 70 */ {"+1.5", float64(1.5), nil},
|
|
||||||
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
|
||||||
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
|
||||||
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
|
||||||
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
|
|
||||||
/* 75 */ {`123`, int64(123), nil},
|
|
||||||
/* 76 */ {`1.`, float64(1.0), nil},
|
|
||||||
/* 77 */ {`1.E-2`, float64(0.01), nil},
|
|
||||||
/* 78 */ {`1E2`, float64(100), nil},
|
|
||||||
/* 79 */ {`1 / 2`, int64(0), nil},
|
|
||||||
/* 80 */ {`1.0 / 2`, float64(0.5), nil},
|
|
||||||
/* 81 */ {`1 ./ 2`, float64(0.5), nil},
|
|
||||||
/* 82 */ {`5 % 2`, int64(1), nil},
|
|
||||||
/* 83 */ {`5 % (-2)`, int64(1), nil},
|
|
||||||
/* 84 */ {`-5 % 2`, int64(-1), nil},
|
|
||||||
/* 85 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
|
||||||
/* 86 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
|
||||||
/* 87 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
|
||||||
/* 88 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
|
||||||
/* 89 */ {`~ 2 > 1`, false, nil},
|
|
||||||
/* 90 */ {`~ true && true`, false, nil},
|
|
||||||
/* 91 */ {`~ false || true`, true, nil},
|
|
||||||
/* 92 */ {`false but true`, true, nil},
|
|
||||||
/* 93 */ {`2+3 but 5*2`, int64(10), nil},
|
|
||||||
/* 94 */ {`add(1,2) but var2`, "abc", nil},
|
|
||||||
/* 95 */ {`x=2`, int64(2), nil},
|
|
||||||
/* 96 */ {`x=2 but x*10`, int64(20), nil},
|
|
||||||
/* 97 */ {`false and true`, false, nil},
|
|
||||||
/* 98 */ {`false and (x==2)`, false, nil},
|
|
||||||
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable "x"`)},
|
|
||||||
/* 100 */ {`false or true`, true, nil},
|
|
||||||
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
|
|
||||||
/* 102 */ {`a=5; a`, int64(5), nil},
|
|
||||||
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
|
|
||||||
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
|
||||||
/* 105 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
|
||||||
/* 106 */ {`2+(a=5)`, int64(7), nil},
|
|
||||||
/* 107 */ {`two=func(){2}; two()`, int64(2), nil},
|
|
||||||
/* 108 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
|
||||||
/* 109 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
|
||||||
/* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
|
||||||
/* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 114 */ {`x ?? "default"`, "default", nil},
|
|
||||||
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
|
||||||
/* 116 */ {`x ?? func(){}"`, nil, errors.New(`[1:15] the right operand of a coalescing operation cannot be a function definition`)},
|
|
||||||
/* 117 */ {`x ?= "default"; x`, "default", nil},
|
|
||||||
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
|
||||||
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
|
||||||
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
|
||||||
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
|
||||||
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)},
|
|
||||||
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
|
||||||
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
|
||||||
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
|
||||||
/* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
|
|
||||||
/* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
|
|
||||||
/* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
|
||||||
/* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
|
||||||
/* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
|
||||||
/* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
|
||||||
/* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
|
||||||
/* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
|
||||||
/* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
|
||||||
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
|
||||||
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
|
||||||
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
|
||||||
}
|
|
||||||
check_env_expr_path := 113
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr Expr
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
ctx.SetVar("var1", int64(123))
|
|
||||||
ctx.SetVar("var2", "abc")
|
|
||||||
ImportMathFuncs(ctx)
|
|
||||||
ImportImportFuncs(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++
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListParser(t *testing.T) {
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// {`add(1,2,3)`, int64(6), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`[]`, []any{}, nil},
|
|
||||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
|
||||||
/* 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
|
|
||||||
|
|
||||||
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 {
|
|
||||||
t.Log(fmt.Sprintf("[+]Test nr %3d -- %q --> %v", n, source, wantResult))
|
|
||||||
} else {
|
|
||||||
t.Log(fmt.Sprintf("[-]Test nr %3d -- %q --> %v", n, source, wantErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+53
-3
@@ -74,6 +74,14 @@ func (self *scanner) unreadChar() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *scanner) lastPos() (r, c int) {
|
||||||
|
if self.prev != nil {
|
||||||
|
r = self.prev.row
|
||||||
|
c = self.prev.col
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *scanner) Previous() *Token {
|
func (self *scanner) Previous() *Token {
|
||||||
return self.prev
|
return self.prev
|
||||||
}
|
}
|
||||||
@@ -86,13 +94,14 @@ func (self *scanner) Next() (tk *Token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchNextToken() (tk *Token) {
|
func (self *scanner) fetchNextToken() (tk *Token) {
|
||||||
|
var ch byte
|
||||||
if err := self.skipBlanks(); err != nil {
|
if err := self.skipBlanks(); err != nil {
|
||||||
return self.makeErrorToken(err)
|
return self.makeErrorToken(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
escape := false
|
escape := false
|
||||||
for {
|
for {
|
||||||
ch, _ := self.readChar()
|
ch, _ = self.readChar()
|
||||||
switch ch {
|
switch ch {
|
||||||
case '+':
|
case '+':
|
||||||
if next, _ := self.peek(); next == '+' {
|
if next, _ := self.peek(); next == '+' {
|
||||||
@@ -143,6 +152,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
}
|
}
|
||||||
case ',':
|
case ',':
|
||||||
tk = self.makeToken(SymComma, ch)
|
tk = self.makeToken(SymComma, ch)
|
||||||
|
case '^':
|
||||||
|
tk = self.makeToken(SymCaret, ch)
|
||||||
case ':':
|
case ':':
|
||||||
if next, _ := self.peek(); next == ':' {
|
if next, _ := self.peek(); next == ':' {
|
||||||
tk = self.moveOn(SymDoubleColon, ch, next)
|
tk = self.moveOn(SymDoubleColon, ch, next)
|
||||||
@@ -157,6 +168,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)
|
||||||
}
|
}
|
||||||
@@ -236,9 +253,20 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
tk = self.makeToken(SymGreater, ch)
|
tk = self.makeToken(SymGreater, ch)
|
||||||
}
|
}
|
||||||
case '$':
|
case '$':
|
||||||
|
if next, _ := self.peek(); next == '(' {
|
||||||
|
tk = self.moveOn(SymDollarRound, ch, next)
|
||||||
|
tk.source += ")"
|
||||||
|
} else if next == '$' {
|
||||||
|
tk = self.moveOn(SymDoubleDollar, ch, next)
|
||||||
|
} else {
|
||||||
tk = self.makeToken(SymDollar, ch)
|
tk = self.makeToken(SymDollar, ch)
|
||||||
|
}
|
||||||
case '(':
|
case '(':
|
||||||
|
if next, _ := self.peek(); next == ')' {
|
||||||
|
tk = self.moveOn(SymOpenClosedRound, ch, next)
|
||||||
|
} else {
|
||||||
tk = self.makeToken(SymOpenRound, ch)
|
tk = self.makeToken(SymOpenRound, ch)
|
||||||
|
}
|
||||||
case ')':
|
case ')':
|
||||||
tk = self.makeToken(SymClosedRound, ch)
|
tk = self.makeToken(SymClosedRound, ch)
|
||||||
case '[':
|
case '[':
|
||||||
@@ -271,6 +299,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if tk == nil {
|
||||||
|
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +393,8 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil && (ch == 'e' || ch == 'E') {
|
if err == nil {
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
if ch, err = self.readChar(); err == nil {
|
if ch, err = self.readChar(); err == nil {
|
||||||
@@ -375,7 +407,23 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("expected integer exponent")
|
err = fmt.Errorf("[%d:%d] expected integer exponent, got %c", self.row, self.column, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ch == '(' {
|
||||||
|
sym = SymFraction
|
||||||
|
sb.WriteByte(ch)
|
||||||
|
ch, err = self.readChar()
|
||||||
|
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||||
|
sb.WriteByte(ch)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if ch != ')' {
|
||||||
|
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", self.row, self.column, ch)
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(ch)
|
||||||
|
_, err = self.readChar()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,6 +437,8 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
txt := sb.String()
|
txt := sb.String()
|
||||||
if sym == SymFloat {
|
if sym == SymFloat {
|
||||||
value, err = strconv.ParseFloat(txt, 64)
|
value, err = strconv.ParseFloat(txt, 64)
|
||||||
|
} else if sym == SymFraction {
|
||||||
|
value, err = makeGeneratingFraction(txt)
|
||||||
} else {
|
} else {
|
||||||
value, err = strconv.ParseInt(txt, numBase, 64)
|
value, err = strconv.ParseInt(txt, numBase, 64)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// simple-func-store.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type SimpleFuncStore struct {
|
|
||||||
SimpleVarStore
|
|
||||||
funcStore map[string]*funcInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcInfo struct {
|
|
||||||
name string
|
|
||||||
minArgs int
|
|
||||||
maxArgs int
|
|
||||||
functor Functor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) Name() string {
|
|
||||||
return info.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) MinArgs() int {
|
|
||||||
return info.minArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) MaxArgs() int {
|
|
||||||
return info.maxArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) Functor() Functor {
|
|
||||||
return info.functor
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
|
||||||
return &SimpleFuncStore{
|
|
||||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
|
||||||
funcStore: make(map[string]*funcInfo),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
|
||||||
return &SimpleFuncStore{
|
|
||||||
SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
|
||||||
funcStore: CloneMap(ctx.funcStore),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc) {
|
|
||||||
info, _ = ctx.funcStore[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
|
||||||
ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
|
||||||
funcNames = make([]string, 0)
|
|
||||||
for name := range ctx.funcStore {
|
|
||||||
if acceptor != nil {
|
|
||||||
if acceptor(name) {
|
|
||||||
funcNames = append(funcNames, name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
funcNames = append(funcNames, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
|
||||||
if info, exists := ctx.funcStore[name]; exists {
|
|
||||||
functor := info.functor
|
|
||||||
result, err = functor.Invoke(ctx, name, args)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unknown function %s()", name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// simple-func-store.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleFuncStore struct {
|
||||||
|
SimpleVarStore
|
||||||
|
funcStore map[string]*funcInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
pfOptional paramFlags = 1 << iota
|
||||||
|
pfRepeat
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcParamInfo struct {
|
||||||
|
name string
|
||||||
|
flags paramFlags
|
||||||
|
defaultValue any
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParam(name string) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Name() string {
|
||||||
|
return param.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Type() string {
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsOptional() bool {
|
||||||
|
return (param.flags & pfOptional) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsRepeat() bool {
|
||||||
|
return (param.flags & pfRepeat) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) DefaultValue() any {
|
||||||
|
return param.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
params []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Params() []ExprFuncParam {
|
||||||
|
return info.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ReturnType() string {
|
||||||
|
return info.returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteByte('(')
|
||||||
|
if info.params != nil {
|
||||||
|
for i, p := range info.params {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(p.Name())
|
||||||
|
if p.IsOptional() {
|
||||||
|
sb.WriteByte('=')
|
||||||
|
if s, ok := p.DefaultValue().(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.maxArgs < 0 {
|
||||||
|
sb.WriteString(" ...")
|
||||||
|
}
|
||||||
|
sb.WriteString(") -> ")
|
||||||
|
if len(info.returnType) > 0 {
|
||||||
|
sb.WriteString(info.returnType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(typeAny)
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Name() string {
|
||||||
|
return info.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MinArgs() int {
|
||||||
|
return info.minArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MaxArgs() int {
|
||||||
|
return info.maxArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Functor() Functor {
|
||||||
|
return info.functor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||||
|
ctx := &SimpleFuncStore{
|
||||||
|
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||||
|
funcStore: make(map[string]*funcInfo),
|
||||||
|
}
|
||||||
|
//ImportBuiltinsFuncs(ctx)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||||
|
svs := ctx.SimpleVarStore
|
||||||
|
return &SimpleFuncStore{
|
||||||
|
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||||
|
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
|
||||||
|
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range names {
|
||||||
|
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) {
|
||||||
|
info, exists = ctx.funcStore[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
||||||
|
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
if params != nil {
|
||||||
|
for _, p := range params {
|
||||||
|
if maxArgs == -1 {
|
||||||
|
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||||
|
// } else if p.IsRepeat() {
|
||||||
|
// maxArgs = -1
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
if p.IsOptional() {
|
||||||
|
maxArgs++
|
||||||
|
} else if maxArgs == minArgs {
|
||||||
|
minArgs++
|
||||||
|
maxArgs++
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
maxArgs = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.funcStore[name] = &funcInfo{
|
||||||
|
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
|
funcNames = make([]string, 0)
|
||||||
|
for name := range ctx.funcStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
||||||
|
if info, exists := ctx.funcStore[name]; exists {
|
||||||
|
functor := info.functor
|
||||||
|
result, err = functor.Invoke(ctx, name, args)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// simple-store.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleStore struct {
|
||||||
|
varStore map[string]any
|
||||||
|
funcStore map[string]*funcInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleStore() *SimpleStore {
|
||||||
|
ctx := &SimpleStore{
|
||||||
|
varStore: make(map[string]any),
|
||||||
|
funcStore: make(map[string]*funcInfo),
|
||||||
|
}
|
||||||
|
//ImportBuiltinsFuncs(ctx)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) Clone() ExprContext {
|
||||||
|
return &SimpleStore{
|
||||||
|
varStore: CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' }),
|
||||||
|
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("funcs: {\n")
|
||||||
|
first := true
|
||||||
|
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range names {
|
||||||
|
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 *SimpleStore) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
varsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
funcsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||||
|
v, exists = ctx.varStore[varName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
ctx.varStore[varName] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) SetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
if allowedValue, ok := fromGenericAny(value); ok {
|
||||||
|
ctx.varStore[varName] = allowedValue
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
|
||||||
|
varNames = make([]string, 0)
|
||||||
|
for name := range ctx.varStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||||
|
info, exists = ctx.funcStore[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
|
||||||
|
var info *funcInfo
|
||||||
|
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
|
||||||
|
ctx.funcStore[name] = info
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
|
funcNames = make([]string, 0)
|
||||||
|
for name := range ctx.funcStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
|
||||||
|
if info, exists := ctx.funcStore[name]; exists {
|
||||||
|
functor := info.functor
|
||||||
|
result, err = functor.Invoke(ctx, name, args)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// simple-var-store.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type SimpleVarStore struct {
|
|
||||||
varStore map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSimpleVarStore() *SimpleVarStore {
|
|
||||||
return &SimpleVarStore{
|
|
||||||
varStore: make(map[string]any),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
|
||||||
clone = &SimpleVarStore{
|
|
||||||
varStore: CloneMap(ctx.varStore),
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
|
||||||
v, exists = ctx.varStore[varName]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
|
||||||
ctx.varStore[varName] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
|
||||||
if allowedValue, ok := fromGenericAny(value); ok {
|
|
||||||
ctx.varStore[varName] = allowedValue
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
|
|
||||||
varNames = make([]string, 0)
|
|
||||||
for name := range ctx.varStore {
|
|
||||||
if acceptor != nil {
|
|
||||||
if acceptor(name) {
|
|
||||||
varNames = append(varNames, name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
varNames = append(varNames, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// simple-var-store.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleVarStore struct {
|
||||||
|
varStore map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleVarStore() *SimpleVarStore {
|
||||||
|
return &SimpleVarStore{
|
||||||
|
varStore: make(map[string]any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// fmt.Println("*** Cloning context ***")
|
||||||
|
clone = &SimpleVarStore{
|
||||||
|
varStore: ctx.cloneVars(),
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
||||||
|
v, exists = ctx.varStore[varName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
ctx.varStore[varName] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
if allowedValue, ok := fromGenericAny(value); ok {
|
||||||
|
ctx.varStore[varName] = allowedValue
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
|
||||||
|
varNames = make([]string, 0)
|
||||||
|
for name := range ctx.varStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
||||||
|
// }
|
||||||
|
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
}
|
||||||
|
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
|
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: '@'
|
||||||
@@ -61,13 +61,22 @@ const (
|
|||||||
SymDoubleColon // 50: '::'
|
SymDoubleColon // 50: '::'
|
||||||
SymInsert // 51: '>>'
|
SymInsert // 51: '>>'
|
||||||
SymAppend // 52: '<<'
|
SymAppend // 52: '<<'
|
||||||
|
SymCaret // 53: '^'
|
||||||
|
SymDollarRound // 54: '$('
|
||||||
|
SymOpenClosedRound // 55: '()'
|
||||||
|
SymDoubleDollar // 56: '$$'
|
||||||
|
SymDoubleDot // 57: '..'
|
||||||
|
SymTripleDot // 58: '...'
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
SymBool
|
SymBool
|
||||||
SymInteger
|
SymInteger
|
||||||
|
SymVariable
|
||||||
SymFloat
|
SymFloat
|
||||||
|
SymFraction
|
||||||
SymString
|
SymString
|
||||||
|
SymIterator
|
||||||
SymOr
|
SymOr
|
||||||
SymAnd
|
SymAnd
|
||||||
SymNot
|
SymNot
|
||||||
@@ -75,6 +84,8 @@ const (
|
|||||||
SymFuncCall
|
SymFuncCall
|
||||||
SymFuncDef
|
SymFuncDef
|
||||||
SymList
|
SymList
|
||||||
|
SymDict
|
||||||
|
SymIndex
|
||||||
SymExpression
|
SymExpression
|
||||||
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||||
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||||
@@ -90,6 +101,9 @@ const (
|
|||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
SymKwBuiltin
|
SymKwBuiltin
|
||||||
|
SymKwIn
|
||||||
|
SymKwInclude
|
||||||
|
SymKwNil
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords map[string]Symbol
|
var keywords map[string]Symbol
|
||||||
@@ -101,7 +115,10 @@ func init() {
|
|||||||
"BUILTIN": SymKwBuiltin,
|
"BUILTIN": SymKwBuiltin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
|
"IN": SymKwIn,
|
||||||
|
"INCLUDE": SymKwInclude,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
"OR": SymKwOr,
|
"OR": SymKwOr,
|
||||||
|
"NIL": SymKwNil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// ast_test.go
|
// t_ast_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -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() {
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_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"}[3]`, "three", nil},
|
||||||
|
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
|
||||||
|
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
|
||||||
|
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, 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 := NewSimpleStore()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDictToStringMultiLine(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-ML"
|
||||||
|
want := `{
|
||||||
|
"first": 1
|
||||||
|
}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(MultiLine)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(MultiLine): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDictToString(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-SL"
|
||||||
|
want := `{"first": 1}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(0)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(0): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// expr_test.go
|
// t_expr_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -18,29 +17,40 @@ func TestExpr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
/* 1 */ {`0?{}`, nil, nil},
|
||||||
|
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
||||||
|
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
||||||
|
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||||
|
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
||||||
|
/* 6 */ {`
|
||||||
|
ds={
|
||||||
|
"init":func(end){@end=end; @current=0 but true},
|
||||||
|
"current":func(){current},
|
||||||
|
"next":func(){
|
||||||
|
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
it=$(ds,3);
|
||||||
|
it++;
|
||||||
|
it++
|
||||||
|
`, int64(1), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
succeeded := 0
|
succeeded := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
inputs1 := []inputType{
|
for i, input := range inputs {
|
||||||
{`f=openFile("/tmp/test2.txt"); line=readFile(f); closeFile(f); line`, "ciao", nil},
|
|
||||||
//{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, input := range inputs1 {
|
|
||||||
var expr Expr
|
var expr Expr
|
||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
// ImportMathFuncs(ctx)
|
// ImportMathFuncs(ctx)
|
||||||
// ImportImportFunc(ctx)
|
// ImportImportFunc(ctx)
|
||||||
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())
|
||||||
@@ -68,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)
|
||||||
}
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_funcs_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncs(t *testing.T) {
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`isNil(nil)`, true, nil},
|
||||||
|
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||||
|
/* 3 */ {`v=5; isNil(v)`, false, nil},
|
||||||
|
/* 4 */ {`int(true)`, int64(1), nil},
|
||||||
|
/* 5 */ {`int(false)`, int64(0), nil},
|
||||||
|
/* 6 */ {`int(3.1)`, int64(3), nil},
|
||||||
|
/* 7 */ {`int(3.9)`, int64(3), nil},
|
||||||
|
/* 8 */ {`int("432")`, int64(432), nil},
|
||||||
|
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||||
|
/* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)},
|
||||||
|
/* 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 */ {`isInt(2+1)`, true, nil},
|
||||||
|
/* 46 */ {`isInt(3.1)`, false, nil},
|
||||||
|
/* 47 */ {`isFloat(3.1)`, true, nil},
|
||||||
|
/* 48 */ {`isString("3.1")`, true, nil},
|
||||||
|
/* 49 */ {`isString("3" + 1)`, true, nil},
|
||||||
|
/* 50 */ {`isList(["3", 1])`, true, nil},
|
||||||
|
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
|
||||||
|
/* 52 */ {`isFract(1|3)`, true, nil},
|
||||||
|
/* 53 */ {`isFract(3|1)`, false, nil},
|
||||||
|
/* 54 */ {`isRational(3|1)`, true, nil},
|
||||||
|
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
|
||||||
|
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
|
||||||
|
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
|
||||||
|
/* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
|
||||||
|
/* 59 */ {`fract(1.21)`, newFraction(121, 100), nil},
|
||||||
|
/* 60 */ {`dec(2)`, float64(2), nil},
|
||||||
|
/* 61 */ {`dec(2.0)`, float64(2), nil},
|
||||||
|
/* 62 */ {`dec("2.0")`, float64(2), nil},
|
||||||
|
/* 63 */ {`dec(true)`, float64(1), nil},
|
||||||
|
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
|
||||||
|
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
|
||||||
|
/* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
|
||||||
|
/* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
|
||||||
|
/* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
|
||||||
|
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
||||||
|
|
||||||
|
}
|
||||||
|
funcs: {
|
||||||
|
add(any=0 ...) -> number,
|
||||||
|
dec(any) -> decimal,
|
||||||
|
endsWithStr(source, suffix) -> boolean,
|
||||||
|
fract(any, denominator=1) -> fraction,
|
||||||
|
import( ...) -> any,
|
||||||
|
importAll( ...) -> any,
|
||||||
|
int(any) -> integer,
|
||||||
|
isBool(any) -> boolean,
|
||||||
|
isDec(any) -> boolean,
|
||||||
|
isDict(any) -> boolean,
|
||||||
|
isFloat(any) -> boolean,
|
||||||
|
isFract(any) -> boolean,
|
||||||
|
isInt(any) -> boolean,
|
||||||
|
isList(any) -> boolean,
|
||||||
|
isNil(any) -> boolean,
|
||||||
|
isString(any) -> boolean,
|
||||||
|
joinStr(separator, item="" ...) -> string,
|
||||||
|
mul(any=1 ...) -> number,
|
||||||
|
splitStr(source, separator="", count=-1) -> list of string,
|
||||||
|
startsWithStr(source, prefix) -> boolean,
|
||||||
|
subStr(source, start=0, count=-1) -> string,
|
||||||
|
trimStr(source) -> string
|
||||||
|
}
|
||||||
|
`, nil},*/
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, "Func", inputs, 69)
|
||||||
|
parserTest(t, "Func", inputs)
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// t_graph_test.go
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// graph_test.go
|
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// helpers_test.go
|
// t_helpers_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
|
|||||||
|
|
||||||
func TestEvalString(t *testing.T) {
|
func TestEvalString(t *testing.T) {
|
||||||
|
|
||||||
ctx := NewSimpleVarStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("a", uint8(1))
|
ctx.SetVar("a", uint8(1))
|
||||||
ctx.SetVar("b", int8(2))
|
ctx.SetVar("b", int8(2))
|
||||||
ctx.SetVar("f", 2.0)
|
ctx.SetVar("f", 2.0)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_index_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollections(t *testing.T) {
|
||||||
|
section := "Collection"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`"abcdef"[1:3]`, "bc", nil},
|
||||||
|
/* 2 */ {`"abcdef"[:3]`, "abc", nil},
|
||||||
|
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
|
||||||
|
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
|
||||||
|
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||||
|
/* 5 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 5)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_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)
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_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},
|
||||||
|
/* 17 */ {`list=["one","two","three"]; list[10]`, nil, errors.New(`[1:34] index 10 out of bounds`)},
|
||||||
|
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
|
||||||
|
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
|
||||||
|
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
|
||||||
|
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
|
||||||
|
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil},
|
||||||
|
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil},
|
||||||
|
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
|
||||||
|
/* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
|
||||||
|
/* 26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, 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 := NewSimpleStore()
|
||||||
|
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,241 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_parser_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inputType struct {
|
||||||
|
source string
|
||||||
|
wantResult any
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneralParser(t *testing.T) {
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
||||||
|
/* 2 */ {`3 == 4`, false, nil},
|
||||||
|
/* 3 */ {`3 != 4`, true, nil},
|
||||||
|
/* 4 */ {`4 == (3-1)*(10/5)`, true, nil},
|
||||||
|
/* 5 */ {`3 <= 4`, true, nil},
|
||||||
|
/* 6 */ {`3 < 4`, true, nil},
|
||||||
|
/* 7 */ {`4 < 3`, false, nil},
|
||||||
|
/* 8 */ {`1+5 < 4`, false, nil},
|
||||||
|
/* 9 */ {`3 > 4`, false, nil},
|
||||||
|
/* 10 */ {`4 >= 4`, true, nil},
|
||||||
|
/* 11 */ {`4 > 3`, true, nil},
|
||||||
|
/* 12 */ {`1+5 > 4`, true, nil},
|
||||||
|
/* 13 */ {`true`, true, nil},
|
||||||
|
/* 14 */ {`not true`, false, nil},
|
||||||
|
/* 15 */ {`true and false`, false, nil},
|
||||||
|
/* 16 */ {`true or false`, true, nil},
|
||||||
|
/* *17 */ {`"uno" + "due"`, `unodue`, nil},
|
||||||
|
/* *18 */ {`"uno" + 2`, `uno2`, nil},
|
||||||
|
/* *19 */ {`"uno" + (2+1)`, `uno3`, nil},
|
||||||
|
/* *20 */ {`"uno" * (2+1)`, `unounouno`, nil},
|
||||||
|
/* 21 */ {"1", int64(1), nil},
|
||||||
|
/* 22 */ {"1.5", float64(1.5), nil},
|
||||||
|
/* 23 */ {"1.5*2", float64(3.0), nil},
|
||||||
|
/* 24 */ {"+1", int64(1), nil},
|
||||||
|
/* 25 */ {"-1", int64(-1), nil},
|
||||||
|
/* 26 */ {"435 + 105 * 2 - 1", int64(644), nil},
|
||||||
|
/* 27 */ {"200 / 2 - 1", int64(99), nil},
|
||||||
|
/* 28 */ {"(1+1)", int64(2), nil},
|
||||||
|
/* 29 */ {"-(1+1)", int64(-2), nil},
|
||||||
|
/* 30 */ {"-(-2+1)", int64(1), nil},
|
||||||
|
/* 31 */ {"(1+1)*5", int64(10), nil},
|
||||||
|
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
|
||||||
|
/* 33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
||||||
|
/* 34 */ {`(((1)))`, int64(1), nil},
|
||||||
|
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
|
||||||
|
/* 36 */ {`0 || 0.0 && "hello"`, false, nil},
|
||||||
|
/* 37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
||||||
|
/* 38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||||
|
/* 39 */ {`false // very simple expression`, false, nil},
|
||||||
|
/* 40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||||
|
/* 41 */ {"", nil, nil},
|
||||||
|
/* 42 */ {"4!", int64(24), nil},
|
||||||
|
/* 43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||||
|
/* 44 */ {"-4!", int64(-24), nil},
|
||||||
|
/* 45 */ {"1.5 < 7", true, nil},
|
||||||
|
/* 46 */ {"1.5 > 7", false, nil},
|
||||||
|
/* 47 */ {"1.5 <= 7", true, nil},
|
||||||
|
/* 48 */ {"1.5 >= 7", false, nil},
|
||||||
|
/* 49 */ {"1.5 != 7", true, nil},
|
||||||
|
/* 50 */ {"1.5 == 7", false, nil},
|
||||||
|
/* 51 */ {`"1.5" < "7"`, true, nil},
|
||||||
|
/* 52 */ {`"1.5" > "7"`, false, nil},
|
||||||
|
/* 53 */ {`"1.5" == "7"`, false, nil},
|
||||||
|
/* 54 */ {`"1.5" != "7"`, true, nil},
|
||||||
|
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
||||||
|
/* 56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
||||||
|
/* 57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
||||||
|
/* 58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
||||||
|
/* 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`)},
|
||||||
|
/* 61 */ {`"1.5" < `, nil, errors.New(`[1:8] 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:8] 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", float64(1.5), nil},
|
||||||
|
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||||
|
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
||||||
|
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
||||||
|
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
|
||||||
|
/* 70 */ {`123`, int64(123), nil},
|
||||||
|
/* 71 */ {`1.`, float64(1.0), nil},
|
||||||
|
/* 72 */ {`1.E-2`, float64(0.01), nil},
|
||||||
|
/* 73 */ {`1E2`, float64(100), nil},
|
||||||
|
/* 74 */ {`1 / 2`, int64(0), nil},
|
||||||
|
/* 75 */ {`1.0 / 2`, float64(0.5), nil},
|
||||||
|
/* 76 */ {`1 ./ 2`, float64(0.5), nil},
|
||||||
|
/* 77 */ {`5 % 2`, int64(1), nil},
|
||||||
|
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
||||||
|
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
||||||
|
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
|
||||||
|
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||||
|
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||||
|
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
||||||
|
/* 84 */ {`~ 2 > 1`, false, nil},
|
||||||
|
/* 85 */ {`~ true && true`, false, nil},
|
||||||
|
/* 86 */ {`~ false || true`, true, nil},
|
||||||
|
/* 87 */ {`false but true`, true, nil},
|
||||||
|
/* 88 */ {`2+3 but 5*2`, int64(10), nil},
|
||||||
|
/* 89 */ {`x=2`, int64(2), nil},
|
||||||
|
/* 90 */ {`x=2 but x*10`, int64(20), nil},
|
||||||
|
/* 91 */ {`false and true`, false, nil},
|
||||||
|
/* 92 */ {`false and (x==2)`, false, nil},
|
||||||
|
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
|
/* 94 */ {`false or true`, true, nil},
|
||||||
|
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||||
|
/* 96 */ {`a=5; a`, int64(5), nil},
|
||||||
|
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||||
|
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
||||||
|
/* 99 */ {`2+(a=5)`, int64(7), nil},
|
||||||
|
/* 100 */ {`x ?? "default"`, "default", nil},
|
||||||
|
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||||
|
/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
||||||
|
/* 103 */ {`x ?= "default"; x`, "default", nil},
|
||||||
|
/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
||||||
|
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
||||||
|
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
||||||
|
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
||||||
|
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
||||||
|
/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
||||||
|
/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
||||||
|
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
||||||
|
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
||||||
|
/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||||
|
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
||||||
|
/* 115 */ {`nil`, nil, nil},
|
||||||
|
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||||
|
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||||
|
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||||
|
/* 119 */ {`{}`, &DictType{}, nil},
|
||||||
|
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
||||||
|
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||||
|
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||||
|
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||||
|
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||||
|
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||||
|
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
|
||||||
|
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
||||||
|
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
||||||
|
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
||||||
|
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
||||||
|
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||||
|
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
|
||||||
|
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||||
|
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||||
|
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||||
|
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||||
|
/* 137 */ {`builtin "os.file"`, int64(1), nil},
|
||||||
|
/* 138 */ {`v=10; v++; v`, int64(11), nil},
|
||||||
|
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
|
||||||
|
/* 140 */ {`1.2()`, newFraction(6, 5), nil},
|
||||||
|
/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
// parserTestSpec(t, "General", inputs, 102)
|
||||||
|
parserTest(t, "General", inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
for _, count := range spec {
|
||||||
|
good := doTest(t, section, &inputs[count-1], count)
|
||||||
|
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
good := doTest(t, section, &input, i+1)
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
|
||||||
|
var expr Expr
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
ctx := NewSimpleStore()
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
logTest(t, count, section, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||||
|
|
||||||
|
if !eq /*gotResult != input.wantResult*/ {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want = %v [%T]", count, 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 -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||||
|
if wantErr == nil {
|
||||||
|
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||||
|
} else {
|
||||||
|
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_template_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRelational(t *testing.T) {
|
||||||
|
section := "Relational"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`1 == 3-2`, true, nil},
|
||||||
|
/* 2 */ {`"a" == "a"`, true, nil},
|
||||||
|
/* 3 */ {`true == false`, false, nil},
|
||||||
|
/* 4 */ {`1.0 == 3.0-2`, true, nil},
|
||||||
|
/* 5 */ {`[1,2] == [2,1]`, false, nil},
|
||||||
|
/* 6 */ {`1 != 3-2`, false, nil},
|
||||||
|
/* 7 */ {`"a" != "a"`, false, nil},
|
||||||
|
/* 8 */ {`true != false`, true, nil},
|
||||||
|
/* 9 */ {`1.0 != 3.0-2`, false, nil},
|
||||||
|
/* 10 */ {`[1,2] != [2,1]`, true, nil},
|
||||||
|
/* 11 */ {`1|2 == 1|3`, false, nil},
|
||||||
|
/* 12 */ {`1|2 != 1|3`, true, nil},
|
||||||
|
/* 13 */ {`1|2 == 4|8`, true, nil},
|
||||||
|
/* 14 */ {`1 < 8`, true, nil},
|
||||||
|
/* 15 */ {`1 <= 8`, true, nil},
|
||||||
|
/* 16 */ {`"a" < "b"`, true, nil},
|
||||||
|
/* 17 */ {`"a" <= "b"`, true, nil},
|
||||||
|
/* 18 */ {`1.0 < 8`, true, nil},
|
||||||
|
/* 19 */ {`1.0 <= 8`, true, nil},
|
||||||
|
/* 20 */ {`1.0 <= 1.0`, true, nil},
|
||||||
|
/* 21 */ {`1.0 == 1`, true, nil},
|
||||||
|
/* 22 */ {`1|2 < 1|3`, false, nil},
|
||||||
|
/* 23 */ {`1|2 <= 1|3`, false, nil},
|
||||||
|
/* 24 */ {`1|2 > 1|3`, true, nil},
|
||||||
|
/* 25 */ {`1|2 >= 1|3`, true, nil},
|
||||||
|
/* 26 */ {`[1,2,3] > [2]`, true, nil},
|
||||||
|
/* 27 */ {`[1,2,3] > [9]`, false, nil},
|
||||||
|
/* 28 */ {`[1,2,3] >= [6]`, false, nil},
|
||||||
|
/* 29 */ {`[1,2,3] >= [2,6]`, false, nil},
|
||||||
|
/* 30 */ {`[1,[2,3],4] > [[3,2]]`, true, nil},
|
||||||
|
/* 31 */ {`[1,[2,[3,4]],5] > [[[4,3],2]]`, true, nil},
|
||||||
|
/* 32 */ {`[[4,3],2] IN [1,[2,[3,4]],5]`, true, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 31)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// scanner_test.go
|
// t_scanner_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -36,7 +37,7 @@ func TestScanner(t *testing.T) {
|
|||||||
/* 14 */ {`:`, SymColon, nil, nil},
|
/* 14 */ {`:`, SymColon, nil, nil},
|
||||||
/* 15 */ {`;`, SymSemiColon, nil, nil},
|
/* 15 */ {`;`, SymSemiColon, nil, nil},
|
||||||
/* 16 */ {`.`, SymDot, nil, nil},
|
/* 16 */ {`.`, SymDot, nil, nil},
|
||||||
/* 17 */ {`.5`, SymFloat, float64(0.5), nil},
|
/* 17 */ {`0.5`, SymFloat, float64(0.5), nil},
|
||||||
/* 18 */ {`\\`, SymBackSlash, nil, nil},
|
/* 18 */ {`\\`, SymBackSlash, nil, nil},
|
||||||
/* 19 */ {"`", SymBackTick, nil, nil},
|
/* 19 */ {"`", SymBackTick, nil, nil},
|
||||||
/* 20 */ {"?", SymQuestion, nil, nil},
|
/* 20 */ {"?", SymQuestion, nil, nil},
|
||||||
@@ -55,15 +56,18 @@ func TestScanner(t *testing.T) {
|
|||||||
/* 33 */ {`(`, SymOpenRound, nil, nil},
|
/* 33 */ {`(`, SymOpenRound, nil, nil},
|
||||||
/* 34 */ {`)`, SymClosedRound, nil, nil},
|
/* 34 */ {`)`, SymClosedRound, nil, nil},
|
||||||
/* 35 */ {`1E+2`, SymFloat, float64(100), nil},
|
/* 35 */ {`1E+2`, SymFloat, float64(100), nil},
|
||||||
/* 36 */ {`1E+x`, SymError, errors.New("expected integer exponent"), nil},
|
/* 36 */ {`1E+x`, SymError, errors.New("[1:5] expected integer exponent, got x"), nil},
|
||||||
/* 37 */ {`$`, SymDollar, nil, nil},
|
/* 37 */ {`$`, SymDollar, nil, nil},
|
||||||
/* 38 */ {`\`, SymError, errors.New("incomplete escape sequence"), nil},
|
/* 38 */ {`\`, SymError, errors.New("incomplete escape sequence"), nil},
|
||||||
/* 39 */ {`"string"`, SymString, "string", nil},
|
/* 39 */ {`"string"`, SymString, "string", nil},
|
||||||
/* 39 */ {`identifier`, SymIdentifier, "identifier", nil},
|
/* 40 */ {`identifier`, SymIdentifier, "identifier", nil},
|
||||||
|
/* 41 */ {`1.2(3)`, SymFraction, newFraction(37, 30), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
|
// if i != 40 {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
if input.wantErr == nil {
|
if input.wantErr == nil {
|
||||||
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
|
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
|
||||||
} else {
|
} else {
|
||||||
@@ -74,8 +78,9 @@ func TestScanner(t *testing.T) {
|
|||||||
scanner := NewScanner(r, nil)
|
scanner := NewScanner(r, nil)
|
||||||
|
|
||||||
if tk := scanner.Next(); tk == nil {
|
if tk := scanner.Next(); tk == nil {
|
||||||
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
|
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
|
||||||
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
|
// } else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
|
||||||
|
} else if tk.Sym != input.wantSym || !reflect.DeepEqual(tk.Value, input.wantValue) {
|
||||||
if tk.Sym == SymError && input.wantSym == tk.Sym {
|
if tk.Sym == SymError && input.wantSym == tk.Sym {
|
||||||
if tkErr, tkOk := tk.Value.(error); tkOk {
|
if tkErr, tkOk := tk.Value.(error); tkOk {
|
||||||
if inputErr, inputOk := input.wantValue.(error); inputOk {
|
if inputErr, inputOk := input.wantValue.(error); inputOk {
|
||||||
@@ -86,7 +91,7 @@ func TestScanner(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("%d: %q -> got = %v (value=%v [%T), want %v (value=%v [%T)", i+1,
|
t.Errorf("%d: %q -> got = %v (value=%v [%T]), want %v (value=%v [%T])", i+1,
|
||||||
input.source, tk.Sym, tk.Value, tk.Value, input.wantSym, input.wantValue, input.wantValue)
|
input.source, tk.Sym, tk.Value, tk.Value, input.wantSym, input.wantValue, input.wantValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_strings_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringsParser(t *testing.T) {
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
|
||||||
|
/* 2 */ {`"uno" + 2`, `uno2`, nil},
|
||||||
|
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
|
||||||
|
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
|
||||||
|
/* 5 */ {`"abc"[1]`, `b`, nil},
|
||||||
|
/* 6 */ {`#"abc"`, int64(3), nil},
|
||||||
|
}
|
||||||
|
parserTest(t, "String", inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_template_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSomething(t *testing.T) {
|
||||||
|
section := "Something"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`1`, int64(1), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 1)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// term_test.go
|
// t_term_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -31,6 +31,7 @@ func TestGetRoom(t *testing.T) {
|
|||||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetChildrenCount(t *testing.T) {
|
func TestGetChildrenCount(t *testing.T) {
|
||||||
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
|
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
|
||||||
|
|
||||||
@@ -1,10 +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.
|
||||||
|
|
||||||
// token_test.go
|
// t_token_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ func TestDevString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {"100", SymInteger, 100, `[55]"100"{100}`},
|
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)},
|
||||||
/* 2 */ {"+", SymPlus, nil, `[6]"+"{}`},
|
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_utils_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsString(t *testing.T) {
|
||||||
|
count := 0
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsBool(true) {
|
||||||
|
t.Errorf("%d: IsBool(true) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsString("abc") {
|
||||||
|
t.Errorf("%d: IsString(\"abc\") -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsInteger(int64(123)) {
|
||||||
|
t.Errorf("%d: IsInteger(123) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsFloat(1.23) {
|
||||||
|
t.Errorf("%d: IsFloat(1.23) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsFloat(numAsFloat(123)) {
|
||||||
|
t.Errorf("%d: IsFloat(numAsFloat(123)) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
b, ok := toBool(true)
|
||||||
|
if !(ok && b) {
|
||||||
|
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
b, ok = toBool("abc")
|
||||||
|
if !(ok && b) {
|
||||||
|
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToIntOk(t *testing.T) {
|
||||||
|
source := int64(64)
|
||||||
|
wantValue := int(64)
|
||||||
|
wantErr := error(nil)
|
||||||
|
|
||||||
|
gotValue, gotErr := toInt(source, "test")
|
||||||
|
|
||||||
|
if gotErr != nil || gotValue != wantValue {
|
||||||
|
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||||
|
source, gotValue, gotErr, wantValue, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToIntErr(t *testing.T) {
|
||||||
|
source := uint64(64)
|
||||||
|
wantValue := 0
|
||||||
|
wantErr := errors.New(`test expected integer, got uint64 (64)`)
|
||||||
|
|
||||||
|
gotValue, gotErr := toInt(source, "test")
|
||||||
|
|
||||||
|
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
|
||||||
|
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||||
|
source, gotValue, gotErr, wantValue, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnyInteger(t *testing.T) {
|
||||||
|
type inputType struct {
|
||||||
|
source any
|
||||||
|
wantValue int64
|
||||||
|
wantOk bool
|
||||||
|
}
|
||||||
|
section := "utils.anyInteger"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {int8(-8), int64(-8), true},
|
||||||
|
/* 2 */ {int16(-16), int64(-16), true},
|
||||||
|
/* 3 */ {int32(-32), int64(-32), true},
|
||||||
|
/* 4 */ {int64(-64), int64(-64), true},
|
||||||
|
/* 5 */ {uint8(8), int64(8), true},
|
||||||
|
/* 6 */ {uint16(16), int64(16), true},
|
||||||
|
/* 7 */ {uint32(32), int64(32), true},
|
||||||
|
/* 8 */ {uint64(64), int64(64), true},
|
||||||
|
/* 9 */ {int(-1), int64(-1), true},
|
||||||
|
/* 10 */ {true, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
gotValue, gotOk := anyInteger(input.source)
|
||||||
|
if gotOk != input.wantOk || gotValue != input.wantValue {
|
||||||
|
t.Errorf("%d: anyInteger(%v) -> gotOk = %t, wantOk = %t; gotValue = %v, wantValue = %v",
|
||||||
|
i+1, input.source, gotOk, input.wantOk, gotValue, input.wantValue)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
@@ -17,11 +17,10 @@ func registerTermConstructor(sym Symbol, constructor termContructor) {
|
|||||||
constructorRegistry[sym] = constructor
|
constructorRegistry[sym] = constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTerm(tk *Token, parent *term) (inst *term) {
|
func newTerm(tk *Token) (inst *term) {
|
||||||
if constructorRegistry != nil {
|
if constructorRegistry != nil {
|
||||||
if construct, exists := constructorRegistry[tk.Sym]; exists {
|
if construct, exists := constructorRegistry[tk.Sym]; exists {
|
||||||
inst = construct(tk)
|
inst = construct(tk)
|
||||||
inst.setParent(parent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type termPriority uint32
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
priNone termPriority = iota
|
priNone termPriority = iota
|
||||||
|
priRange
|
||||||
priBut
|
priBut
|
||||||
priAssign
|
priAssign
|
||||||
priOr
|
priOr
|
||||||
@@ -20,10 +21,13 @@ const (
|
|||||||
priRelational
|
priRelational
|
||||||
priSum
|
priSum
|
||||||
priProduct
|
priProduct
|
||||||
|
priFraction
|
||||||
priSelector
|
priSelector
|
||||||
priSign
|
priSign
|
||||||
priFact
|
priFact
|
||||||
|
priIterValue
|
||||||
priCoalesce
|
priCoalesce
|
||||||
|
priIncDec
|
||||||
priDot
|
priDot
|
||||||
priValue
|
priValue
|
||||||
)
|
)
|
||||||
@@ -127,6 +131,10 @@ func (self *term) setParent(parent *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *term) symbol() Symbol {
|
||||||
|
return self.tk.Sym
|
||||||
|
}
|
||||||
|
|
||||||
func (self *term) source() string {
|
func (self *term) source() string {
|
||||||
return self.tk.source
|
return self.tk.source
|
||||||
}
|
}
|
||||||
@@ -144,11 +152,24 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
|
||||||
|
if index64, ok := computedValue.(int64); ok {
|
||||||
|
i = int(index64)
|
||||||
|
} else {
|
||||||
|
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||||
|
leftType := getTypeName(leftValue)
|
||||||
|
leftText := getFormatted(leftValue, Truncate)
|
||||||
|
rightType := getTypeName(rightValue)
|
||||||
|
rightText := getFormatted(rightValue, Truncate)
|
||||||
return self.tk.Errorf(
|
return self.tk.Errorf(
|
||||||
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
|
||||||
leftValue, leftValue,
|
leftText, leftType,
|
||||||
rightValue, rightValue,
|
rightText, rightType,
|
||||||
self.source())
|
self.source())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,9 +223,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
uno
|
||||||
|
due
|
||||||
@@ -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,42 +4,85 @@
|
|||||||
// 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 IsBool(v any) (ok bool) {
|
||||||
_, ok = v.([]any)
|
_, ok = v.(bool)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNumber(v any) (ok bool) {
|
func IsList(v any) (ok bool) {
|
||||||
return isFloat(v) || isInteger(v)
|
_, ok = v.(*ListType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsDict(v any) (ok bool) {
|
||||||
|
_, ok = v.(*DictType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFract(v any) (ok bool) {
|
||||||
|
_, ok = v.(*FractionType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRational(v any) (ok bool) {
|
||||||
|
if _, ok = v.(*FractionType); !ok {
|
||||||
|
_, ok = v.(int64)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNumber(v any) (ok bool) {
|
||||||
|
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 {
|
||||||
|
if fract, ok := v.(*FractionType); ok {
|
||||||
|
f = fract.toFloat()
|
||||||
|
} else {
|
||||||
i, _ := v.(int64)
|
i, _ := v.(int64)
|
||||||
f = float64(i)
|
f = float64(i)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +147,12 @@ func fromGenericAny(v any) (exprAny any, ok bool) {
|
|||||||
if exprAny, ok = anyFloat(v); ok {
|
if exprAny, ok = anyFloat(v); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if exprAny, ok = v.(*DictType); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exprAny, ok = v.(*ListType); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,3 +180,40 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
||||||
|
result := make([]V, len(ts))
|
||||||
|
for i, t := range ts {
|
||||||
|
result[i] = fn(t)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user