Compare commits
29 Commits
v0.5.0
..
c0c2ab8b4e
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-errors.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- General errors
|
||||||
|
|
||||||
|
func errCantConvert(funcName string, value any, kind string) error {
|
||||||
|
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errExpectedGot(funcName string, kind string, value any) error {
|
||||||
|
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Parameter errors
|
||||||
|
|
||||||
|
func errOneParam(funcName string) error {
|
||||||
|
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errMissingRequiredParameter(funcName, paramName string) error {
|
||||||
|
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||||
|
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||||
|
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-params.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const (
|
||||||
|
paramParts = "parts"
|
||||||
|
paramSeparator = "separator"
|
||||||
|
paramSource = "source"
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-type-names.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeBoolean = "boolean"
|
||||||
|
typeFloat = "decimal"
|
||||||
|
typeInt = "integer"
|
||||||
|
typeNumber = "number"
|
||||||
|
typeString = "string"
|
||||||
|
)
|
||||||
@@ -27,8 +27,10 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
|||||||
|
|
||||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
exportAll := isEnabled(sourceCtx, control_export_all)
|
exportAll := isEnabled(sourceCtx, control_export_all)
|
||||||
|
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
||||||
// Export variables
|
// Export variables
|
||||||
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||||
|
// fmt.Printf("\tExporting %q\n", refName)
|
||||||
refValue, _ := sourceCtx.GetVar(refName)
|
refValue, _ := sourceCtx.GetVar(refName)
|
||||||
exportVar(destCtx, refName, refValue)
|
exportVar(destCtx, refName, refValue)
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-4
@@ -5,34 +5,122 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
initName = "init"
|
initName = "init"
|
||||||
|
cleanName = "clean"
|
||||||
|
resetName = "reset"
|
||||||
nextName = "next"
|
nextName = "next"
|
||||||
currentName = "current"
|
currentName = "current"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dataCursor struct {
|
type dataCursor struct {
|
||||||
ds map[any]*term
|
ds map[string]Functor
|
||||||
ctx ExprContext
|
ctx ExprContext
|
||||||
index int
|
index int
|
||||||
resource any
|
resource any
|
||||||
nextFunc Functor
|
nextFunc Functor
|
||||||
|
cleanFunc Functor
|
||||||
|
resetFunc Functor
|
||||||
currentFunc Functor
|
currentFunc Functor
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDataCursor(ctx ExprContext) (dc *dataCursor) {
|
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
|
||||||
dc = &dataCursor{
|
dc = &dataCursor{
|
||||||
|
ds: ds,
|
||||||
index: -1,
|
index: -1,
|
||||||
ctx: ctx.Clone(),
|
ctx: ctx.Clone(),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapToString(m map[string]Functor) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteByte('{')
|
||||||
|
for key, _ := range m {
|
||||||
|
if sb.Len() > 1 {
|
||||||
|
sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%q: func(){}", key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte('}')
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) String() string {
|
func (dc *dataCursor) String() string {
|
||||||
return "$(...)"
|
return "$()"
|
||||||
|
/* var sb strings.Builder
|
||||||
|
sb.WriteString(fmt.Sprintf(`$(
|
||||||
|
index: %d,
|
||||||
|
ds: %s,
|
||||||
|
ctx: `, dc.index, mapToString(dc.ds)))
|
||||||
|
CtxToBuilder(&sb, dc.ctx, 1)
|
||||||
|
sb.WriteByte(')')
|
||||||
|
return sb.String()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
||||||
|
f, ok := dc.ds[name]
|
||||||
|
exists = ok && isFunctor(f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
|
||||||
|
if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
|
||||||
|
if functor == dc.cleanFunc {
|
||||||
|
return nil, dc.Clean()
|
||||||
|
} else if functor == dc.resetFunc {
|
||||||
|
return nil, 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() (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)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Clean() (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")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||||
@@ -45,15 +133,22 @@ func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
|
if dc.resource != nil {
|
||||||
ctx := cloneContext(dc.ctx)
|
ctx := cloneContext(dc.ctx)
|
||||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
|
// 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 {
|
if item == nil {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
} else {
|
} else {
|
||||||
dc.index++
|
dc.index++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||||
exportObjects(dc.ctx, ctx)
|
exportObjects(dc.ctx, ctx)
|
||||||
|
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
|
||||||
|
} else {
|
||||||
|
err = errInvalidDataSource()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,5 +55,5 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("builtins", ImportBuiltinsFuncs)
|
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// func-common.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func errOneParam(funcName string) error {
|
|
||||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func errCantConvert(funcName string, value any, kind string) error {
|
|
||||||
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
|
||||||
}
|
|
||||||
+1
-1
@@ -142,5 +142,5 @@ func ImportImportFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("import", ImportImportFuncs)
|
registerImport("import", ImportImportFuncs, "Functions import() and include()")
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-3
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
||||||
if !(isNumber(paramValue) || isList(paramValue)) {
|
if !(isNumber(paramValue) || isList(paramValue) || isIterator(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 has wrong type %T, number expected", funcName, paramPos+1, paramValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -27,6 +27,11 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
if v, err = doAdd(ctx, name, subIter); err != nil {
|
if v, err = doAdd(ctx, name, subIter); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if subIter.HasOperation(cleanName) {
|
||||||
|
if _, err = subIter.CallOperation(cleanName, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
||||||
break
|
break
|
||||||
@@ -77,7 +82,7 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if array, ok := v.([]any); ok {
|
if array, ok := v.([]any); ok {
|
||||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
if v, err = doMul(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,5 +120,5 @@ func ImportMathFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("math.arith", ImportMathFuncs)
|
registerImport("math.arith", ImportMathFuncs, "Function add() and mul()")
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -126,7 +126,7 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
|
|||||||
|
|
||||||
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
var handle osHandle
|
var handle osHandle
|
||||||
|
result = nil
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
handle, _ = args[0].(osHandle)
|
handle, _ = args[0].(osHandle)
|
||||||
}
|
}
|
||||||
@@ -141,13 +141,16 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
limit = s[0]
|
limit = s[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
|
if v, err = r.reader.ReadString(limit); err == nil {
|
||||||
if len(v) > 0 && v[len(v)-1] == limit {
|
if len(v) > 0 && v[len(v)-1] == limit {
|
||||||
result = v[0 : len(v)-1]
|
result = v[0 : len(v)-1]
|
||||||
} else {
|
} else {
|
||||||
result = v
|
result = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,5 +167,5 @@ func ImportOsFuncs(ctx ExprContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("os", ImportOsFuncs)
|
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
|
||||||
}
|
}
|
||||||
|
|||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-string.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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].([]any); ok {
|
||||||
|
result, err = doJoinStr(name, sep, NewFlatArrayIterator(ls))
|
||||||
|
} 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, NewFlatArrayIterator(args[1:]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errWrongParamType(name, paramSeparator, typeString, args[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var start = 0
|
||||||
|
var count = -1
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
if start, err = toInt(args[1], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 2 {
|
||||||
|
if count, err = toInt(args[2], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < 0 {
|
||||||
|
start = len(source) + start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count < 0 {
|
||||||
|
count = len(source) - start
|
||||||
|
}
|
||||||
|
end := min(start+count, len(source))
|
||||||
|
result = source[start:end]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errMissingRequiredParameter(name, paramSource)
|
||||||
|
}
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
|
}
|
||||||
|
result = strings.TrimSpace(source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- End of function definitions
|
||||||
|
|
||||||
|
// Import above functions in the context
|
||||||
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the import function in the import-register.
|
||||||
|
// That will allow to import all function of this module by the "builtin" operator."
|
||||||
|
func init() {
|
||||||
|
registerImport("string", ImportStringFuncs, "string utilities")
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// function-register.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
var functionRegister map[string]func(ExprContext)
|
|
||||||
|
|
||||||
func registerImport(name string, importFunc func(ExprContext)) {
|
|
||||||
if functionRegister == nil {
|
|
||||||
functionRegister = make(map[string]func(ExprContext))
|
|
||||||
}
|
|
||||||
functionRegister[name] = importFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
|
||||||
var importFunc func(ExprContext)
|
|
||||||
if importFunc, exists = functionRegister[name]; exists {
|
|
||||||
importFunc(ctx)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
|
||||||
var matched bool
|
|
||||||
for name, importFunc := range functionRegister {
|
|
||||||
if matched, err = filepath.Match(pattern, name); err == nil {
|
|
||||||
if matched {
|
|
||||||
count++
|
|
||||||
importFunc(ctx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if functionRegister == nil {
|
|
||||||
functionRegister = make(map[string]func(ExprContext))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+23
@@ -6,6 +6,8 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,3 +61,24 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
|
||||||
|
var tree *ast
|
||||||
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
if tree, err = parser.Parse(scanner); err == nil {
|
||||||
|
result, err = tree.Eval(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalFile(ctx ExprContext, filePath string) (result any, err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
result, err = EvalStream(ctx, fh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ func NewFlatArrayIterator(array []any) *FlatArrayIterator {
|
|||||||
return &FlatArrayIterator{a: array, index: 0}
|
return &FlatArrayIterator{a: array, index: 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *FlatArrayIterator) HasOperation(name string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *FlatArrayIterator) CallOperation(name string, args []any) (any, error) {
|
||||||
|
return nil, errNoOperation(name)
|
||||||
|
}
|
||||||
|
|
||||||
func (it *FlatArrayIterator) Current() (item any, err error) {
|
func (it *FlatArrayIterator) Current() (item any, err error) {
|
||||||
if it.index >= 0 && it.index < len(it.a) {
|
if it.index >= 0 && it.index < len(it.a) {
|
||||||
item = it.a[it.index]
|
item = it.a[it.index]
|
||||||
|
|||||||
+15
@@ -4,8 +4,23 @@
|
|||||||
// iterator.go
|
// iterator.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type Iterator interface {
|
type Iterator interface {
|
||||||
Next() (item any, err error) // must return io.EOF after the last item
|
Next() (item any, err error) // must return io.EOF after the last item
|
||||||
Current() (item any, err error)
|
Current() (item any, err error)
|
||||||
Index() int
|
Index() int
|
||||||
|
HasOperation(name string) bool
|
||||||
|
CallOperation(name string, args []any) (value any, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errNoOperation(name string) error {
|
||||||
|
return fmt.Errorf("no %q function defined in the data-source", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidDataSource() error {
|
||||||
|
return errors.New("invalid data-source")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// module-register.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type module struct {
|
||||||
|
importFunc func(ExprContext)
|
||||||
|
description string
|
||||||
|
imported bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newModule(importFunc func(ExprContext), description string) *module {
|
||||||
|
return &module{importFunc, description, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
var moduleRegister map[string]*module
|
||||||
|
|
||||||
|
func registerImport(name string, importFunc func(ExprContext), description string) {
|
||||||
|
if moduleRegister == nil {
|
||||||
|
moduleRegister = make(map[string]*module)
|
||||||
|
}
|
||||||
|
if _, exists := moduleRegister[name]; exists {
|
||||||
|
panic(fmt.Errorf("module %q already registered", name))
|
||||||
|
}
|
||||||
|
moduleRegister[name] = newModule(importFunc, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||||
|
var mod *module
|
||||||
|
if mod, exists = moduleRegister[name]; exists {
|
||||||
|
mod.importFunc(ctx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||||
|
var matched bool
|
||||||
|
for name, mod := range moduleRegister {
|
||||||
|
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||||
|
if matched {
|
||||||
|
count++
|
||||||
|
mod.importFunc(ctx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterateModules(op func(name, description string, imported bool) bool) {
|
||||||
|
if op != nil {
|
||||||
|
for name, mod := range moduleRegister {
|
||||||
|
if !op(name, mod.description, mod.imported) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
func init() {
|
||||||
|
if moduleRegister == nil {
|
||||||
|
moduleRegister = make(map[string]*module)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
|||||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||||
ctx := cloneContext(parentCtx)
|
ctx := cloneContext(parentCtx)
|
||||||
name, _ := self.tk.Value.(string)
|
name, _ := self.tk.Value.(string)
|
||||||
|
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
||||||
params := make([]any, len(self.children))
|
params := make([]any, len(self.children))
|
||||||
for i, tree := range self.children {
|
for i, tree := range self.children {
|
||||||
var param any
|
var param any
|
||||||
|
|||||||
+65
-8
@@ -6,6 +6,8 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- iterator term
|
// -------- iterator term
|
||||||
@@ -41,6 +43,44 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
||||||
|
// var value any
|
||||||
|
// if len(self.children) < 1 || self.children[0] == nil {
|
||||||
|
// err = self.Errorf("missing the data-source parameter")
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if value, err = self.children[0].compute(ctx); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if dictAny, ok := value.(map[any]any); ok {
|
||||||
|
// ds = make(map[string]Functor)
|
||||||
|
// // required functions
|
||||||
|
// for _, k := range []string{currentName, nextName} {
|
||||||
|
// if item, exists := dictAny[k]; exists && item != nil {
|
||||||
|
// if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
|
// ds[k] = functor
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // Optional functions
|
||||||
|
// for _, k := range []string{initName, resetName, cleanName} {
|
||||||
|
// if item, exists := dictAny[k]; exists && item != nil {
|
||||||
|
// if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
|
// ds[k] = functor
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
||||||
var value any
|
var value any
|
||||||
if len(self.children) < 1 || self.children[0] == nil {
|
if len(self.children) < 1 || self.children[0] == nil {
|
||||||
@@ -52,18 +92,31 @@ func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requiredFields := []string{currentName, nextName}
|
||||||
|
fieldsMask := 0b11
|
||||||
|
foundFields := 0
|
||||||
if dictAny, ok := value.(map[any]any); ok {
|
if dictAny, ok := value.(map[any]any); ok {
|
||||||
ds = make(map[string]Functor)
|
ds = make(map[string]Functor)
|
||||||
for _, k := range []string{initName, currentName, nextName} {
|
for keyAny, item := range dictAny {
|
||||||
if item, exists := dictAny[k]; exists && item != nil {
|
if key, ok := keyAny.(string); ok {
|
||||||
if functor, ok := item.(*funcDefFunctor); ok {
|
if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
ds[k] = functor
|
ds[key] = functor
|
||||||
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
|
foundFields |= 1 << index
|
||||||
}
|
}
|
||||||
} else if k != initName {
|
|
||||||
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 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, ", "))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
||||||
}
|
}
|
||||||
@@ -77,8 +130,9 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dc := newDataCursor(ctx)
|
dc := newDataCursor(ctx, ds)
|
||||||
|
// var it Iterator = dc
|
||||||
|
// fmt.Println(it)
|
||||||
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
||||||
var args []any
|
var args []any
|
||||||
if len(self.children) > 1 {
|
if len(self.children) > 1 {
|
||||||
@@ -98,6 +152,9 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
|
|
||||||
dc.nextFunc, _ = ds[nextName]
|
dc.nextFunc, _ = ds[nextName]
|
||||||
dc.currentFunc, _ = ds[currentName]
|
dc.currentFunc, _ = ds[currentName]
|
||||||
|
dc.cleanFunc, _ = ds[cleanName]
|
||||||
|
dc.resetFunc, _ = ds[resetName]
|
||||||
|
|
||||||
v = dc
|
v = dc
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+1
-1
@@ -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.
|
||||||
|
|
||||||
// operator-length.go
|
// operator-builtin.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
//-------- builtin term
|
//-------- builtin term
|
||||||
|
|||||||
+45
-28
@@ -17,50 +17,67 @@ func newDotTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
|
||||||
|
var v int
|
||||||
|
var indexValue any
|
||||||
|
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||||
|
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
|
||||||
|
if v >= 0 && v < maxValue {
|
||||||
|
index = v
|
||||||
|
} else if index >= -maxValue {
|
||||||
|
index = maxValue + v
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if err = self.checkOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
indexTerm := self.children[1]
|
indexTerm := self.children[1]
|
||||||
|
|
||||||
if isList(leftValue) {
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case []any:
|
||||||
var index int
|
var index int
|
||||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||||
return
|
v = unboxedValue[index]
|
||||||
}
|
}
|
||||||
|
case string:
|
||||||
list, _ := leftValue.([]any)
|
|
||||||
if index >= 0 && index < len(list) {
|
|
||||||
v = list[index]
|
|
||||||
} else if index >= -len(list) {
|
|
||||||
v = list[len(list)+index]
|
|
||||||
} else {
|
|
||||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
|
||||||
}
|
|
||||||
} else if isString(leftValue) {
|
|
||||||
var index int
|
var index int
|
||||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||||
return
|
v = unboxedValue[index]
|
||||||
}
|
}
|
||||||
|
case map[any]any:
|
||||||
s, _ := leftValue.(string)
|
|
||||||
if index >= 0 && index < len(s) {
|
|
||||||
v = string(s[index])
|
|
||||||
} else if index >= -len(s) {
|
|
||||||
v = string(s[len(s)+index])
|
|
||||||
} else {
|
|
||||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
|
||||||
}
|
|
||||||
} else if isDict(leftValue) {
|
|
||||||
var ok bool
|
var ok bool
|
||||||
d, _ := leftValue.(map[any]any)
|
var indexValue any
|
||||||
if v, ok = d[rightValue]; !ok {
|
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||||
|
if v, ok = unboxedValue[indexValue]; !ok {
|
||||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
case *dataCursor:
|
||||||
|
if indexTerm.symbol() == SymIdentifier {
|
||||||
|
opName := indexTerm.source()
|
||||||
|
if opName == resetName {
|
||||||
|
err = unboxedValue.Reset()
|
||||||
|
} else if opName == cleanName {
|
||||||
|
err = unboxedValue.Clean()
|
||||||
} else {
|
} else {
|
||||||
|
err = indexTerm.Errorf("iterators do not support command %q", opName)
|
||||||
|
}
|
||||||
|
v = err == nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
+3
-3
@@ -25,12 +25,12 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
|
|
||||||
if isList(rightValue) {
|
if isList(rightValue) {
|
||||||
list, _ := rightValue.([]any)
|
list, _ := rightValue.([]any)
|
||||||
v = len(list)
|
v = int64(len(list))
|
||||||
} else if isString(rightValue) {
|
} else if isString(rightValue) {
|
||||||
s, _ := rightValue.(string)
|
s, _ := rightValue.(string)
|
||||||
v = len(s)
|
v = int64(len(s))
|
||||||
} else if it, ok := rightValue.(Iterator); ok {
|
} else if it, ok := rightValue.(Iterator); ok {
|
||||||
v = it.Index()
|
v = int64(it.Index())
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
err = self.errIncompatibleType(rightValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,11 @@ func NewSimpleFuncStore() *SimpleFuncStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||||
|
svs := ctx.SimpleVarStore
|
||||||
return &SimpleFuncStore{
|
return &SimpleFuncStore{
|
||||||
SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||||
funcStore: CloneMap(ctx.funcStore),
|
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||||
|
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+44
-2
@@ -4,7 +4,10 @@
|
|||||||
// simple-var-store.go
|
// simple-var-store.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type SimpleVarStore struct {
|
type SimpleVarStore struct {
|
||||||
varStore map[string]any
|
varStore map[string]any
|
||||||
@@ -16,9 +19,14 @@ func NewSimpleVarStore() *SimpleVarStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
|
||||||
|
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
||||||
|
// fmt.Println("*** Cloning context ***")
|
||||||
clone = &SimpleVarStore{
|
clone = &SimpleVarStore{
|
||||||
varStore: CloneMap(ctx.varStore),
|
varStore: ctx.cloneVars(),
|
||||||
}
|
}
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
@@ -29,10 +37,12 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
ctx.varStore[varName] = value
|
ctx.varStore[varName] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||||
if allowedValue, ok := fromGenericAny(value); ok {
|
if allowedValue, ok := fromGenericAny(value); ok {
|
||||||
ctx.varStore[varName] = allowedValue
|
ctx.varStore[varName] = allowedValue
|
||||||
} else {
|
} else {
|
||||||
@@ -68,3 +78,35 @@ func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, m
|
|||||||
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("{\n")
|
||||||
|
first := true
|
||||||
|
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
|
||||||
|
value, _ := ctx.GetVar(name)
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
sb.WriteString(name)
|
||||||
|
sb.WriteString("=")
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
}
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtxToString(ctx ExprContext, indent int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
CtxToBuilder(&sb, ctx, indent)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const (
|
|||||||
SymBackTick // 22: '`'
|
SymBackTick // 22: '`'
|
||||||
SymExclamation // 23: '!'
|
SymExclamation // 23: '!'
|
||||||
SymQuestion // 24: '?'
|
SymQuestion // 24: '?'
|
||||||
SymAmpersand // 25: '&&'
|
SymAmpersand // 25: '&'
|
||||||
SymDoubleAmpersand // 26: '&&'
|
SymDoubleAmpersand // 26: '&&'
|
||||||
SymPercent // 27: '%'
|
SymPercent // 27: '%'
|
||||||
SymAt // 28: '@'
|
SymAt // 28: '@'
|
||||||
@@ -64,7 +64,7 @@ const (
|
|||||||
SymCaret // 53: '^'
|
SymCaret // 53: '^'
|
||||||
SymDollarRound // 54: '$('
|
SymDollarRound // 54: '$('
|
||||||
SymOpenClosedRound // 55: '()'
|
SymOpenClosedRound // 55: '()'
|
||||||
SymDoubleDollar // 56: '$$
|
SymDoubleDollar // 56: '$$'
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
@@ -96,6 +96,7 @@ const (
|
|||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
SymKwBuiltin
|
SymKwBuiltin
|
||||||
|
SymKwInclude
|
||||||
SymKwNil
|
SymKwNil
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,6 +109,7 @@ func init() {
|
|||||||
"BUILTIN": SymKwBuiltin,
|
"BUILTIN": SymKwBuiltin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
|
"INCLUDE": SymKwInclude,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
"OR": SymKwOr,
|
"OR": SymKwOr,
|
||||||
"NIL": SymKwNil,
|
"NIL": SymKwNil,
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
|
|||||||
if index64, ok := computedValue.(int64); ok {
|
if index64, ok := computedValue.(int64); ok {
|
||||||
i = int(index64)
|
i = int(index64)
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("%s, got %T", valueDescription, computedValue)
|
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -217,9 +217,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
|
||||||
}
|
}
|
||||||
|
|||||||
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,7 +4,10 @@
|
|||||||
// 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)
|
||||||
@@ -39,6 +42,16 @@ 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 {
|
||||||
@@ -136,3 +149,32 @@ func CloneMap[K comparable, V any](source map[K]V) map[K]V {
|
|||||||
dest := make(map[K]V, len(source))
|
dest := make(map[K]V, len(source))
|
||||||
return CopyMap(dest, source)
|
return CopyMap(dest, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||||
|
// fmt.Printf("--- Clone with filter %p\n", filter)
|
||||||
|
if filter == nil {
|
||||||
|
return CopyMap(dest, source)
|
||||||
|
} else {
|
||||||
|
for k, v := range source {
|
||||||
|
if filter(k) {
|
||||||
|
// fmt.Printf("\tClone var %q\n", k)
|
||||||
|
dest[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||||
|
dest := make(map[K]V, len(source))
|
||||||
|
return CopyFilteredMap(dest, source, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt(value any, description string) (i int, err error) {
|
||||||
|
if valueInt64, ok := value.(int64); ok {
|
||||||
|
i = int(valueInt64)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user