Compare commits

..

3 Commits

Author SHA1 Message Date
camoroso ba1d887a05 commit 2024-04-20 04:02:51 +02:00
camoroso faff5a7e2c hook_test next commit 2024-04-19 15:02:53 +02:00
camoroso 36f6846a3f hook_test first commit 2024-04-19 15:00:20 +02:00
205 changed files with 3888 additions and 22683 deletions
+1 -1
View File
@@ -144,4 +144,4 @@ Variables and functions can be added to a context both programmatically and ad a
== Expressions syntax
See https://cdn.paas.portale-stac.it/howto/go-pkg/expr/doc/Expr.html[Expr documentation] for a complete reference of the expression syntax and available functions.
See #TODO link to doc/Expr.html#
+136
View File
@@ -0,0 +1,136 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// ast.go
package expr
import (
"errors"
"strings"
)
type Expr interface {
Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error)
String() string
}
//-------- ast
type ast struct {
forest []*term
root *term
}
func NewAst() *ast {
return &ast{}
}
func (self *ast) ToForest() {
if self.root != nil {
if self.forest == nil {
self.forest = make([]*term, 0)
}
self.forest = append(self.forest, self.root)
self.root = nil
}
}
func (self *ast) String() string {
var sb strings.Builder
if self.root == nil {
sb.WriteString("(nil)")
} else {
self.root.toString(&sb)
}
return sb.String()
}
func (self *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens {
if err = self.addToken(tk); err != nil {
break
}
}
return
}
func (self *ast) addToken(tk *Token) (err error) {
_, err = self.addToken2(tk)
return
}
func (self *ast) addToken2(tk *Token) (t *term, err error) {
if t = newTerm(tk, nil); t != nil {
err = self.addTerm(t)
} else {
err = tk.Errorf("No term constructor for token %q", tk.String())
}
return
}
func (self *ast) addTerm(node *term) (err error) {
if self.root == nil {
self.root = node
} else {
self.root, err = self.insert(self.root, node)
}
return
}
func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.getPriority() < node.getPriority() {
root = tree
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
subRoot, err = self.insert(last, node)
subRoot.setParent(tree)
} else {
node.setParent(tree)
}
} else if !node.isLeaf() {
root = node
tree.setParent(node)
} else {
err = node.Errorf("two adjacent operators: %q and %q", tree, node)
}
return
}
func (self *ast) Finish() {
if self.root == nil && self.forest != nil && len(self.forest) >= 1 {
self.root = self.forest[len(self.forest)-1]
self.forest = self.forest[0 : len(self.forest)-1]
}
}
func (self *ast) Eval(ctx ExprContext) (result any, err error) {
return self.eval(ctx, true)
}
func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
self.Finish()
if self.root != nil {
if preset {
initDefaultVars(ctx)
}
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil {
ctx.setVar(ControlLastResult, result)
} else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
break
}
}
}
if err == nil {
result, err = self.root.compute(ctx)
}
} else {
err = errors.New("empty expression")
}
return
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// ast_test.go
package expr
import (
"errors"
"testing"
)
func TestAstString(t *testing.T) {
tree := NewAst()
if gotResult := tree.String(); gotResult != "(nil)" {
t.Errorf(`result: got %q, want "(nil)"`, gotResult)
}
}
func TestAddTokensGood(t *testing.T) {
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "50", 500)
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr != nil {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestAddTokensBad(t *testing.T) {
tk0 := NewValueToken(0, 0, SymInteger, "200", 200)
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "50", 500)
wantErr := errors.New(`[0:0] two adjacent operators: "200" and "100"`)
tree := NewAst()
if gotErr := tree.addTokens(tk0, tk1, tk2, tk3); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestAddUnknownTokens(t *testing.T) {
tk0 := NewToken(0, 0, SymPercent, "%")
wantErr := errors.New(`No term constructor for token "%"`)
tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
}
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
)
// ---- Linking with Expr functions
type exprFunctor struct {
kern.BaseFunctor
params []kern.ExprFuncParam
expr kern.Expr
defCtx kern.ExprContext
}
func (functor *exprFunctor) GetParams() (params []kern.ExprFuncParam) {
return functor.params
}
func newExprFunctor(e kern.Expr, params []kern.ExprFuncParam, ctx kern.ExprContext) *exprFunctor {
var defCtx kern.ExprContext
if ctx != nil {
defCtx = ctx
}
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
func (functor *exprFunctor) TypeName() string {
return "ExprFunctor"
}
func (functor *exprFunctor) GetDefinitionContext() kern.ExprContext {
return functor.defCtx
}
func (functor *exprFunctor) InvokeNamed(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var missing []string
for _, p := range functor.params {
if arg, exists := args[p.Name()]; exists {
if funcArg, ok := arg.(kern.Functor); ok {
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, kern.TypeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
if missing == nil {
missing = make([]string, 0, 1)
}
missing = append(missing, p.Name())
// ctx.UnsafeSetVar(p.Name(), nil)
}
}
if missing != nil {
err = kern.ErrMissingParams(name, missing)
} else {
result, err = functor.expr.Eval(ctx)
}
return
}
-339
View File
@@ -1,339 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-base.go
package expr
import (
"fmt"
"math"
"strconv"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
const (
ParamDenominator = "denominator"
)
func isNilFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = args[kern.ParamValue] == nil
return
}
func isIntFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsInteger(args[kern.ParamValue])
return
}
func isFloatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFloat(args[kern.ParamValue])
return
}
func isBoolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsBool(args[kern.ParamValue])
return
}
func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsString(args[kern.ParamValue])
return
}
func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFraction(args[kern.ParamValue])
return
}
func isRationalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsRational(args[kern.ParamValue])
return
}
func isListFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsList(args[kern.ParamValue])
return
}
func isDictionaryFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsDict(args[kern.ParamValue])
return
}
func boolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
result = (v != 0)
case *kern.FractionType:
result = v.N() != 0
case float64:
result = v != 0.0
case bool:
result = v
case string:
result = len(v) > 0
case *kern.ListType:
result = len(*v) > 0
case *kern.DictType:
result = len(*v) > 0
default:
err = kern.ErrCantConvert(name, v, "bool")
}
return
}
func intFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(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)
}
case *kern.FractionType:
result = int64(v.N() / v.D())
default:
err = kern.ErrCantConvert(name, v, "int")
}
return
}
func decFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(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 *kern.FractionType:
result = v.ToFloat()
default:
err = kern.ErrCantConvert(name, v, "float")
}
return
}
func stringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
result = strconv.FormatInt(v, 10)
case float64:
result = strconv.FormatFloat(v, 'g', -1, 64)
case bool:
if v {
result = "true"
} else {
result = "false"
}
case string:
result = v
case *kern.FractionType:
result = v.ToString(0)
case kern.Formatter:
result = v.ToString(0)
case fmt.Stringer:
result = v.String()
default:
err = kern.ErrCantConvert(name, v, "string")
}
return
}
func fractFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
var den int64 = 1
var ok bool
if den, ok = args[ParamDenominator].(int64); !ok {
err = kern.ErrExpectedGot(name, "integer", args[ParamDenominator])
} else if den == 0 {
err = kern.ErrFuncDivisionByZero(name)
}
if err == nil {
result = kern.NewFraction(v, den)
}
case float64:
result, err = kern.Float64ToFraction(v)
case bool:
if v {
result = kern.NewFraction(1, 1)
} else {
result = kern.NewFraction(0, 1)
}
case string:
result, err = kern.MakeGeneratingFraction(v)
case *kern.FractionType:
result = v
default:
err = kern.ErrCantConvert(name, v, "float")
}
return
}
// func iteratorFunc(ctx expr.ExprContext, name string, args []any) (result any, err error) {
// return
// }
func evalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[kern.ParamSource].(string); ok {
var ast kern.Expr
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStoreWithoutGlobalContext()
}
r := strings.NewReader(source)
scanner := scan.NewScanner(r, scan.DefaultTranslations())
if ast, err = parser.Parse(scanner); err == nil {
CtrlEnable(ctx, kern.ControlExportAll)
result, err = ast.Eval(ctx)
}
} else {
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
return
}
func varFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
}
if result, ok = args[kern.ParamValue]; ok && result != nil {
ctx.GetParent().UnsafeSetVar(varName, result)
// } else {
// err = expr.ErrWrongParamType(name, expr.ParamSource, expr.TypeString, args[expr.ParamSource])
// }
} else if result, ok = ctx.GetVar(varName); !ok {
err = kern.ErrUnknownVar(name, varName)
}
return
}
func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
}
if result, ok = args[kern.ParamValue]; ok {
ctx.GetParent().UnsafeSetVar(varName, result)
} else {
err = kern.ErrWrongParamType(name, kern.ParamValue, kern.TypeAny, args[kern.ParamValue])
}
return
}
func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var ord int
if n, ok := args[kern.ParamValue].(byte); ok {
ord = int(n)
} else if n, ok := args[kern.ParamValue].(int64); ok {
ord = int(n)
} else if n, ok := args[kern.ParamValue].(int); ok {
ord = n
} else {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
}
if ord < 0 || ord > 255 {
err = kern.ErrFuncInvalidArg(name, fmt.Sprintf("character code must be in range 0-255, got %d", ord))
} else {
result = string(rune(ord))
}
return
}
func seqFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
list := kern.NewLinkedList()
items := args[kern.ParamValue].([]any)
for _, arg := range items {
list.PushBack(arg)
}
result = list
return
}
//// import
func ImportBuiltinsFuncs(ctx kern.ExprContext) {
anyParams := []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
}
ctx.RegisterFunc("isNil", kern.NewGolangFunctor(isNilFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isInt", kern.NewGolangFunctor(isIntFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isFloat", kern.NewGolangFunctor(isFloatFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isBool", kern.NewGolangFunctor(isBoolFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isString", kern.NewGolangFunctor(isStringFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isFract", kern.NewGolangFunctor(isFractionFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isRational", kern.NewGolangFunctor(isRationalFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isList", kern.NewGolangFunctor(isListFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isDict", kern.NewGolangFunctor(isDictionaryFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("bool", kern.NewGolangFunctor(boolFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("int", kern.NewGolangFunctor(intFunc), kern.TypeInt, anyParams)
ctx.RegisterFunc("dec", kern.NewGolangFunctor(decFunc), kern.TypeFloat, anyParams)
ctx.RegisterFunc("string", kern.NewGolangFunctor(stringFunc), kern.TypeString, anyParams)
ctx.RegisterFunc("fract", kern.NewGolangFunctor(fractFunc), kern.TypeFraction, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
kern.NewFuncParamFlagDef(ParamDenominator, kern.PfDefault, int64(1)),
})
ctx.RegisterFunc("eval", kern.NewGolangFunctor(evalFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
ctx.RegisterFunc("var", kern.NewGolangFunctor(varFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault, nil),
})
ctx.RegisterFunc("set", kern.NewGolangFunctor(setFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
kern.NewFuncParam(kern.ParamValue),
})
ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
})
ctx.RegisterFunc("seq", kern.NewGolangFunctor(seqFunc), kern.TypeLinkedList, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamValue, kern.PfRepeat),
})
}
func init() {
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}
-59
View File
@@ -1,59 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-fmt.go
package expr
import (
"fmt"
"io"
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
func getStdout(ctx kern.ExprContext) io.Writer {
var w io.Writer
if wany, exists := ctx.GetVar(kern.ControlStdout); exists && wany != nil {
w, _ = wany.(io.Writer)
}
if w == nil {
w = os.Stdout
}
return w
}
func printFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var n int = 0
if v, exists := args[kern.ParamItem]; exists && v != nil {
argv := v.([]any)
n, err = fmt.Fprint(getStdout(ctx), argv...)
}
result = int64(n)
return
}
func printLnFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var n int = 0
if v, exists := args[kern.ParamItem]; exists && v != nil {
argv := v.([]any)
n, err = fmt.Fprintln(getStdout(ctx), argv...)
} else {
n, err = fmt.Fprintln(getStdout(ctx))
}
result = int64(n)
return
}
func ImportFmtFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("print", kern.NewGolangFunctor(printFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
})
ctx.RegisterFunc("println", kern.NewGolangFunctor(printLnFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
})
}
func init() {
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
}
-81
View File
@@ -1,81 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-import.go
package expr
import (
"io"
"os"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
func importFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func importAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
CtrlEnable(ctx, kern.ControlExportAll)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
dirList := buildSearchDirList(ctx, "sources", ENV_EXPR_SOURCE_PATH)
if v, exists := args[kern.ParamFilepath]; exists && v != nil {
argv := v.([]any)
result, err = doImport(ctx, name, dirList, NewArrayIterator(argv))
}
return
}
func doImport(ctx kern.ExprContext, name string, dirList []string, it kern.Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, int(it.Index())); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *scan.Ast
scanner := scan.NewScanner(file, scan.DefaultTranslations())
parser := NewParser()
if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, scan.SymEos); err == nil {
result, err = expr.Eval(ctx)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("import", kern.NewGolangFunctor(importFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
})
ctx.RegisterFunc("importAll", kern.NewGolangFunctor(importAllFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
})
}
func init() {
RegisterBuiltinModule("import", ImportImportFuncs, "Functions import() and include()")
}
-105
View File
@@ -1,105 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-iterator.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
)
const (
iterParamOperator = "operator"
iterParamVars = "vars"
iterVarStatus = "status"
)
func parseRunArgs(localCtx kern.ExprContext, args map[string]any) (it kern.Iterator, op kern.Functor, err error) {
var ok bool
if it, ok = args[kern.ParamIterator].(kern.Iterator); !ok {
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", kern.ParamIterator, args[kern.ParamIterator], kern.TypeName(args[kern.ParamIterator]))
return
}
if args[iterParamOperator] != nil {
if op, ok = args[iterParamOperator].(kern.Functor); !ok || op == nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], kern.TypeName(args[iterParamOperator]))
return
}
}
var vars *kern.DictType
if vars, ok = args[iterParamVars].(*kern.DictType); !ok && args[iterParamVars] != nil {
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], kern.TypeName(args[iterParamVars]))
return
}
if vars != nil {
for key, value := range *vars {
var varName string
if varName, ok = key.(string); ok {
localCtx.UnsafeSetVar(varName, value)
}
}
}
return
}
func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var it kern.Iterator
var ok bool
var op kern.Functor
var v any
// var usingDefaultOp = false
var params map[string]any
var item any
localCtx := ctx.Clone()
localCtx.UnsafeSetVar(iterVarStatus, nil)
if it, op, err = parseRunArgs(localCtx, args); err != nil {
return
}
for item, err = it.Next(); err == nil; item, err = it.Next() {
if op != nil {
params = map[string]any{kern.ParamIndex: it.Index(), kern.ParamItem: item}
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = kern.ToBool(v); !success || !ok {
break
}
}
}
}
if err == io.EOF {
err = nil
}
if err == nil {
if op == nil {
ctx.UnsafeSetVar(iterVarStatus, it.Count())
}
result, _ = localCtx.GetVar(iterVarStatus)
}
return
}
func ImportIterFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("run", kern.NewGolangFunctor(runFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamIterator),
kern.NewFuncParamFlag(iterParamOperator, kern.PfOptional),
kern.NewFuncParamFlag(iterParamVars, kern.PfOptional),
})
}
func init() {
RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
}
-185
View File
@@ -1,185 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-math-arith.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
if !(kern.IsNumber(paramValue) || kern.IsFraction(paramValue)) /*|| isList(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
}
func doAdd(ctx kern.ExprContext, name string, it kern.Iterator, count, level int) (result any, err error) {
var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0
var intSum int64 = 0
var fractSum *kern.FractionType
var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*kern.ListType); ok {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(kern.Iterator); ok {
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(kern.ExtIterator); ok && extIter.HasOperation(kern.CleanName) {
if _, err = extIter.CallOperation(kern.CleanName, nil); err != nil {
return
}
}
} else if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
break
}
count++
if !sumAsFloat {
if kern.IsFloat(v) {
sumAsFloat = true
if sumAsFract {
floatSum = fractSum.ToFloat()
} else {
floatSum = float64(intSum)
}
} else if !sumAsFract && kern.IsFraction(v) {
fractSum = kern.NewFraction(intSum, 1)
sumAsFract = true
}
}
if sumAsFloat {
floatSum += kern.NumAsFloat(v)
} else if sumAsFract {
var item *kern.FractionType
var ok bool
if item, ok = v.(*kern.FractionType); !ok {
iv, _ := v.(int64)
item = kern.NewFraction(iv, 1)
}
fractSum = kern.SumFract(fractSum, item)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil || err == io.EOF {
err = nil
if sumAsFloat {
result = floatSum
} else if sumAsFract {
result = fractSum
} else {
result = intSum
}
}
return
}
func addFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
argv := args[kern.ParamValue].([]any)
result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1)
return
}
func doMul(ctx kern.ExprContext, name string, it kern.Iterator, count, level int) (result any, err error) {
var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0
var intProd int64 = 1
var fractProd *kern.FractionType
var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*kern.ListType); ok {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(kern.Iterator); ok {
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(kern.ExtIterator); ok && extIter.HasOperation(kern.CleanName) {
if _, err = extIter.CallOperation(kern.CleanName, nil); err != nil {
return
}
}
} else {
if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
break
}
}
count++
if !mulAsFloat {
if kern.IsFloat(v) {
mulAsFloat = true
if mulAsFract {
floatProd = fractProd.ToFloat()
} else {
floatProd = float64(intProd)
}
} else if !mulAsFract && kern.IsFraction(v) {
fractProd = kern.NewFraction(intProd, 1)
mulAsFract = true
}
}
if mulAsFloat {
floatProd *= kern.NumAsFloat(v)
} else if mulAsFract {
var item *kern.FractionType
var ok bool
if item, ok = v.(*kern.FractionType); !ok {
iv, _ := v.(int64)
item = kern.NewFraction(iv, 1)
}
fractProd = kern.MulFract(fractProd, item)
} else {
iv, _ := v.(int64)
intProd *= iv
}
}
if err == nil || err == io.EOF {
err = nil
if mulAsFloat {
result = floatProd
} else if mulAsFract {
result = fractProd
} else {
result = intProd
}
}
return
}
func mulFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
argv := args[kern.ParamValue].([]any)
result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1)
return
}
func ImportMathFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("add", kern.NewGolangFunctor(addFunc), kern.TypeNumber, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(0)),
})
ctx.RegisterFunc("mul", kern.NewGolangFunctor(mulFunc), kern.TypeNumber, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(1)),
})
}
func init() {
RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
}
-102
View File
@@ -1,102 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const fileByteIteratorType = "fileByteIterator"
type fileFileByteIterator struct {
fileIterBase
b byte
}
func newFileByteIterator(r *file.Reader, autoClose bool) *fileFileByteIterator {
return &fileFileByteIterator{
fileIterBase: fileIterBase{reader: r, index: -1, count: 0, autoClose: autoClose},
b: 0}
}
func (it *fileFileByteIterator) TypeName() string {
return fileByteIteratorType
}
func (it *fileFileByteIterator) String() string {
return it.repr(fileByteIteratorType)
}
func (it *fileFileByteIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.b, err = it.reader.ReadByte(); err == nil {
it.increment()
item = it.b
} else if it.autoClose {
it.Clean()
}
return
}
func (it *fileFileByteIterator) Current() (item any, err error) {
item = it.b
return
}
func (it *fileFileByteIterator) Reset() (err error) {
if err = it.reader.Reset(); err == nil {
it.reset()
it.b = 0
}
return
}
func (it *fileFileByteIterator) Clean() (err error) {
if it.reader.Valid() {
if err = it.reader.GetFile().Close(); err == nil {
it.reader = nil
}
}
it.reset()
it.b = 0
return
}
func (it *fileFileByteIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func fileByteIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle *file.Reader
var invalidFileHandle any
var autoClose bool
if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil {
if handle != nil {
result = newFileByteIterator(handle, autoClose)
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
-105
View File
@@ -1,105 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const fileLineIteratorType = "fileLineIterator"
type fileFileLineIterator struct {
fileIterBase
line string
}
func newFileLineIterator(r *file.Reader, autoClose bool) *fileFileLineIterator {
return &fileFileLineIterator{
fileIterBase: fileIterBase{reader: r, index: -1, count: 0, autoClose: autoClose},
line: "",
}
}
func (it *fileFileLineIterator) TypeName() string {
return fileLineIteratorType
}
func (it *fileFileLineIterator) String() string {
return it.repr(fileLineIteratorType)
}
func (it *fileFileLineIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.line, err = it.reader.ReadString('\n'); err == nil {
it.increment()
item = it.line[0 : len(it.line)-1]
} else if it.autoClose {
it.Clean()
}
return
}
func (it *fileFileLineIterator) Current() (item any, err error) {
if len(it.line) > 0 {
item = it.line[0 : len(it.line)-1]
}
return
}
func (it *fileFileLineIterator) Reset() (err error) {
if err = it.reader.Reset(); err == nil {
it.reset()
it.line = ""
}
return
}
func (it *fileFileLineIterator) Clean() (err error) {
if it.reader != nil {
if err = it.reader.Close(); err == nil {
it.reader = nil
}
}
it.reset()
it.line = ""
return
}
func (it *fileFileLineIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func fileLineIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle *file.Reader
var invalidFileHandle any
var autoClose bool
if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil {
if handle != nil {
result = newFileLineIterator(handle, autoClose)
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
-71
View File
@@ -1,71 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"fmt"
"slices"
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const paramHandleOrPath = "handle-or-path"
type fileIterBase struct {
reader *file.Reader
index int64
count int64
autoClose bool
}
func (it *fileIterBase) Count() int64 {
return it.count
}
func (it *fileIterBase) Index() int64 {
return it.index
}
func (it *fileIterBase) HasOperation(name string) bool {
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
}
func (it *fileIterBase) reset() {
it.index = -1
it.count = 0
}
func (it *fileIterBase) increment() {
it.index++
it.count++
}
func (it *fileIterBase) repr(typeName string) string {
if it.reader.Valid() {
return fmt.Sprintf("$(%s@%q)", typeName, it.reader.GetName())
}
return fmt.Sprintf("$(%s@<nil>)", typeName)
}
func initFileHandle(ctx kern.ExprContext, name string, args map[string]any) (handle *file.Reader, invalidFileHandle any, autoClose bool, err error) {
var ok bool
if handle, ok = args[paramHandleOrPath].(*file.Reader); !ok {
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
var handleAny any
if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil {
return
}
if handleAny != nil {
handle = handleAny.(*file.Reader)
autoClose = true
}
} else {
invalidFileHandle = args[paramHandleOrPath]
}
}
return
}
-233
View File
@@ -1,233 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const (
osLimitCh = "limitCh"
)
func errMissingFilePath(funcName string) error {
return fmt.Errorf("%s(): missing or invalid file path", funcName)
}
func errInvalidFileHandle(funcName string, v any) error {
if v != nil {
return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v)
} else {
return fmt.Errorf("%s(): invalid file handle", funcName)
}
}
func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
// var fh *os.File
// if fh, err = os.Open(filePath); err == nil {
// result = file.NewReader(fh)
// }
result, err = file.OpenReader(filePath)
} else {
err = errMissingFilePath(name)
}
return
}
func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
// var fh *os.File
// if fh, err = os.Create(filePath); err == nil {
// result = file.NewWriter(fh)
// }
result, err = file.CreateWriter(filePath)
} else {
err = errMissingFilePath(name)
}
return
}
func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
// var fh *os.File
// if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
// result = file.NewWriter(fh)
// }
result, err = file.AppendWriter(filePath)
} else {
err = errMissingFilePath(name)
}
return
}
func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle file.Handle
var invalidFileHandle any
var ok bool
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if fh := handle.GetFile(); fh != nil {
if w, ok := handle.(*file.Writer); ok {
err = w.Flush()
}
if err == nil {
err = fh.Close()
}
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, handle)
}
result = err == nil
return
}
func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle file.Handle
var invalidFileHandle any
var ok bool
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if w, ok := handle.(*file.Writer); ok {
if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any)
// result, err = fmt.Fprint(w.writer, argv...)
result, err = w.Write(argv...)
}
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle file.Handle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if r, ok := handle.(*file.Reader); ok {
var limit byte = '\n'
var v string
if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
limit = s[0]
}
v, err = r.ReadString(limit)
if err == io.EOF {
err = nil
}
if len(v) > 0 {
if v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle file.Handle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if r, ok := handle.(*file.Reader); ok {
var b []byte
b, err = r.ReadAll()
result = string(b)
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func ImportOsFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("fileOpen", kern.NewGolangFunctor(openFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamFilepath),
})
ctx.RegisterFunc("fileAppend", kern.NewGolangFunctor(appendFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamFilepath),
})
ctx.RegisterFunc("fileCreate", kern.NewGolangFunctor(createFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamFilepath),
})
ctx.RegisterFunc("fileClose", kern.NewGolangFunctor(closeFileFunc), kern.TypeBoolean, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
})
ctx.RegisterFunc("fileWriteText", kern.NewGolangFunctor(fileWriteTextFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
kern.NewFuncParamFlagDef(kern.ParamItem, kern.PfDefault|kern.PfRepeat, ""),
})
ctx.RegisterFunc("fileReadText", kern.NewGolangFunctor(fileReadTextFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
kern.NewFuncParamFlagDef(osLimitCh, kern.PfDefault, "\n"),
})
ctx.RegisterFunc("fileReadTextAll", kern.NewGolangFunctor(fileReadTextAllFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
})
ctx.RegisterFunc("fileLineIterator", kern.NewGolangFunctor(fileLineIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
kern.NewFuncParam(paramHandleOrPath),
})
ctx.RegisterFunc("fileByteIterator", kern.NewGolangFunctor(fileByteIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
kern.NewFuncParam(paramHandleOrPath),
})
}
func init() {
RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
}
-273
View File
@@ -1,273 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-string.go
package expr
import (
"fmt"
"io"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
)
const (
strParamOther = "other"
)
// --- Start of function definitions
func doJoinStr(funcName string, sep string, it kern.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 = kern.ErrExpectedGot(funcName, kern.TypeString, v)
return
}
}
if err == io.EOF {
err = nil
result = sb.String()
}
return
}
func joinStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if sep, ok := args[kern.ParamSeparator].(string); ok {
if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any)
if len(argv) == 1 {
if ls, ok := argv[0].(*kern.ListType); ok {
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
} else if it, ok := argv[0].(kern.Iterator); ok {
result, err = doJoinStr(name, sep, it)
} else if s, ok := argv[0].(string); ok {
result = s
} else {
err = kern.ErrInvalidParameterValue(name, kern.ParamItem, v)
}
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(argv))
}
}
} else {
err = kern.ErrWrongParamType(name, kern.ParamSeparator, kern.TypeString, args[kern.ParamSeparator])
}
return
}
func subStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var start = 0
var count = -1
var source string
var ok bool
if source, ok = args[kern.ParamSource].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if start, err = kern.ToGoInt(args[kern.ParamStart], name+"()"); err != nil {
return
}
if count, err = kern.ToGoInt(args[kern.ParamCount], 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 kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source string
var ok bool
if source, ok = args[kern.ParamSource].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
result = strings.TrimSpace(source)
return
}
func startsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source, prefix string
var ok bool
result = false
if source, ok = args[kern.ParamSource].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if prefix, ok = args[kern.ParamPrefix].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamPrefix, kern.TypeString, args[kern.ParamPrefix])
}
if strings.HasPrefix(source, prefix) {
result = true
} else if v, exists := args[strParamOther]; exists {
argv := v.([]any)
for i, targetSpec := range argv {
if target, ok := targetSpec.(string); ok {
if strings.HasPrefix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
break
}
}
}
return
}
func endsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source, suffix string
var ok bool
result = false
if source, ok = args[kern.ParamSource].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if suffix, ok = args[kern.ParamSuffix].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSuffix, kern.TypeString, args[kern.ParamSuffix])
}
if strings.HasPrefix(source, suffix) {
result = true
} else if v, exists := args[strParamOther]; exists {
argv := v.([]any)
for i, targetSpec := range argv {
if target, ok := targetSpec.(string); ok {
if strings.HasSuffix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
break
}
}
}
return
}
func splitStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source, sep string
var count int = -1
var parts []string
var ok bool
if source, ok = args[kern.ParamSource].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if sep, ok = args[kern.ParamSeparator].(string); !ok {
return nil, fmt.Errorf("separator param must be string, got %s (%v)", kern.TypeName(args[kern.ParamSeparator]), args[kern.ParamSeparator])
}
if count64, ok := args[kern.ParamCount].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %s (%v)", kern.TypeName(args[kern.ParamCount]), args[kern.ParamCount])
}
if count > 0 {
parts = strings.SplitN(source, sep, count)
} else if count < 0 {
parts = strings.Split(source, sep)
} else {
parts = []string{}
}
list := make(kern.ListType, len(parts))
for i, part := range parts {
list[i] = part
}
result = &list
return
}
func upperStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[kern.ParamSource].(string); ok {
result = strings.ToUpper(source)
} else {
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
return
}
func lowerStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[kern.ParamSource].(string); ok {
result = strings.ToLower(source)
} else {
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
return
}
// --- End of function definitions
// Import above functions in the context
func ImportStringFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSeparator),
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
})
ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParamFlagDef(kern.ParamStart, kern.PfDefault, int64(0)),
kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
})
ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParamFlagDef(kern.ParamSeparator, kern.PfDefault, ""),
kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
})
ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParam(kern.ParamPrefix),
kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
})
ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParam(kern.ParamSuffix),
kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
})
ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
RegisterBuiltinModule("string", ImportStringFuncs, "string utilities")
}
-50
View File
@@ -1,50 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtins-register.go
package expr
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
)
type builtinModule struct {
importFunc func(kern.ExprContext)
description string
imported bool
}
func newBuiltinModule(importFunc func(kern.ExprContext), description string) *builtinModule {
return &builtinModule{importFunc, description, false}
}
var builtinModuleRegister map[string]*builtinModule
func RegisterBuiltinModule(name string, importFunc func(kern.ExprContext), description string) {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
if _, exists := builtinModuleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
builtinModuleRegister[name] = newBuiltinModule(importFunc, description)
}
func IterateBuiltinModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range builtinModuleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// byte-slider.go
package expr
import "bytes"
type ByteSlider struct {
buf []byte
length int
}
func NewByteSlider(size int) *ByteSlider {
return &ByteSlider{
buf: make([]byte, max(1, size)),
length: 0,
}
}
func (self *ByteSlider) PushEnd(b byte) {
if self.length == cap(self.buf) {
self.length--
for i := 0; i < self.length; i++ {
self.buf[i] = self.buf[i+1]
}
}
self.buf[self.length] = b
self.length++
}
func (self *ByteSlider) Equal(target []byte) bool {
return target != nil && bytes.Equal(self.buf, target)
}
-14
View File
@@ -1,14 +0,0 @@
# Builder resource file
# Created on gio 21 mag 2026, 15:35:18, CEST
# Program name
PROGRAM_NAME="ecli"
# Program version
PROGRAM_VERSION="$(<version.txt)"
# Preset platform
platform=("linux/amd64" "darwin/arm64")
#--- end of file ---
-199
View File
@@ -1,199 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
# All rights reserved.
RESOURCE_FILE=".build.rc"
BUILD_REGISTER=".build_register"
PROGRAM_NAME=""
PROGRAM_VERSION=""
function usage() {
prog=$(basename "${0}")
msgln "USAGE:"
#msgln " ${prog} <program-name> <program-version> <os>/<platform>..."
msgln " ${prog} <os>/<platform>..."
msgln " ${prog} --local Build the local exec"
msgln " ${prog} --init Create the resource file in the current directory"
msgln
if [ -r "${RESOURCE_FILE}" ]; then
msgln "Resource file '${RESOURCE_FILE}' content:"
cat >&2 ${RESOURCE_FILE}
else
msgln "Resource file '${RESOURCE_FILE}' not found"
fi
}
function msgln() {
echo >&2 "${1}"
}
function exitUsage() {
echo >&2 "${1}"
usage
exit 1
}
function exitMsg() {
echo >&2 "${1}"
exit 1
}
# CMDLINE: help
if [ "${1}" == "help,," ] || [ "${1,,}" == "--help" ] || [ "${1,,}" == "-h" ]; then
usage
exit
fi
# CMDLINE: init
if [ "${1,,}" == "--init" ]; then
cat >"${RESOURCE_FILE}" <<eot
# Builder resource file
# Created on $(date)
# Program name
PROGRAM_NAME="name"
# Program version
PROGRAM_VERSION="version"
# Preset platform
platform=("linux/amd64" "darwin/arm64")
#--- end of file ---
eot
msgln "Resource file '${RESOURCE_FILE}' create. Edit it to set valid values."
#${EDITOR-vi} "${RESOURCE_FILE}"
exit
fi
if [ -r "${RESOURCE_FILE}" ]; then
if ! source "${RESOURCE_FILE}"; then
exitMsg "Can't load build resource file '${RESOURCE_FILE}'"
fi
fi
if [ -z "${PROGRAM_NAME}" ]; then
exitUsage "Missing program name"
fi
if [ -z "${PROGRAM_VERSION}" ]; then
exitUsage "Missing program version"
fi
function getBuildCount() {
local reg ver count
if [ -r "${BUILD_REGISTER}" ]; then
reg=$(<"${BUILD_REGISTER}")
else
reg="${PROGRAM_VERSION} 0"
fi
read ver count <<<"${reg}"
if [ "${ver}" != "${PROGRAM_VERSION}" ]; then
count=0
fi
count=$((count+1))
echo >"${BUILD_REGISTER}" "${PROGRAM_VERSION} ${count}"
echo ${count}
}
function build() {
local target=${1} ext cmd
IFS=/ read os cpu <<<"${p}"
#msgln "OS=${os}; CPU=${cpu}"
ext=""
if [ "${os}" == 'windows' ]; then
ext=".exe"
fi
cmd="GOOS='${os}' GOARCH='${cpu}' go build -o '${PROGRAM_NAME}_v${PROGRAM_VERSION}_${os}_${cpu}${ext}'"
eval "${cmd}"
}
function buildLocal() {
local ext cmd
ext=""
if [[ "${OSTYPE}" =~ win.* ]]; then
ext=".exe"
fi
cmd="go build -o '${PROGRAM_NAME}${ext}'"
eval "${cmd}"
}
function gitTag() {
local gopath gopkg mod
local tag
if ! tag=$(git tag -l --sort=-version:refname "v[0-9]*.[0-9]*.[0-9]*"|head -1) || [ -z "${tag}" ]; then
gopath=$(go env GOPATH)
gopkg="${gopath}/pkg/mod/git.portale-stac.it/go-pkg"
if cd "${gopkg}" 2>/dev/null; then
mod=$(ls -1v |grep expr@|tail -1)
tag=${mod##*@}
cd - >/dev/null
fi
fi
echo ${tag}
}
function gitTagDate() {
local tag_name=${1}
local tag_date
if ! tag_date=$(git show --no-patch --format=%ci "${tag_name}") || [ -z "${tag_date}" ]; then
tag_date="n/a"
fi
echo ${tag_date}
}
function createVersionSource() {
local tag tag_date
tag=$(gitTag)
if [ -z "${tag}" ]; then
tag="n/a"
else
tag_date=$(gitTagDate "${tag}")
fi
cat >version.go <<eot
// Copyright (c) 2024-$(date +%Y) Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// version.go
package main
const (
PROGNAME = "${PROGRAM_NAME}"
VERSION = "v${PROGRAM_VERSION}(build $(getBuildCount)),$(date +"%Y/%m/%d") (celestino.amoroso@portale-stac.it)"
EXPR_VERSION = "${tag}"
EXPR_DATE = "${tag_date}"
)
eot
}
## -- TEST -- ##
# echo "Tag: $(gitTag)"
# echo "Tag Date: $(gitTagDate $(gitTag))"
# exit 0
## -- MAIN -- ##
createVersionSource
if [ "${1}" == "--local" ]; then
buildLocal
exit
fi
if [ ${#} -gt 0 ]; then
for p; do
build "${p}"
done
else
for p in ${platform[@]}; do
build "${p}"
done
fi
-84
View File
@@ -1,84 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
# All rights reserved
RESOURCE_FILE=".build.rc"
BUILD_REGISTER=".build_register"
PASSWORD=
GITEA_USER="camoroso"
GITEA_PASSWORD_FILE="${HOME}/.gitea_password"
GITEA_OWNER="go-pkg"
GITEA_HOST="https://git.portale-stac.it"
GITEA_BASE_PATH="api/v1/packages"
GITEA_PKG_TYPE="generic"
function msgln() {
echo >&2 "${1}"
}
function exitMsg() {
echo >&2 "${1}"
exit 1
}
function readBuildCount() {
local reg ver count
if [ -r "${BUILD_REGISTER}" ]; then
reg=$(<"${BUILD_REGISTER}")
else
reg="${PROGRAM_VERSION} 0"
fi
read ver count <<<"${reg}"
if [ "${ver}" != "${PROGRAM_VERSION}" ]; then
count=0
fi
echo ${count}
}
if [ -r "${GITEA_PASSWORD_FILE}" ]; then
if ! PASSWORD=$(<"${GITEA_PASSWORD_FILE}"); then
exitMsg "Can're password file '${GITEA_PASSWORD_FILE}'"
fi
else
exitMsg "Password file '${GITEA_PASSWORD_FILE}' not found"
fi
if [ -z "${PASSWORD}" ]; then
exitMsg "Empty password. Please, check file '${GITEA_PASSWORD_FILE}'"
fi
if [ -r "${RESOURCE_FILE}" ]; then
source "${RESOURCE_FILE}"
else
exitMsg "resource file '${RESOURCE_FILE}' not found"
fi
if [ -r "${BUILD_REGISTER}" ]; then
BUILD_TAG=$(<"${BUILD_REGISTER}")
else
exitMsg "build register file '${BUILD_REGISTER}' not found"
fi
url="${GITEA_HOST}/${GITEA_BASE_PATH}/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/files"
#echo "URL: ${url}"
#echo $(curl --user "${GITEA_USER}:${PASSWORD}" -X GET ${url}|jq '.[]."name"')
declare -a files=(
$(curl --no-progress-meter --user "${GITEA_USER}:${PASSWORD}" -X GET ${url}|jq '.[]."name"')
)
for name in ${files[@]}; do
filename=${name:1:${#name}-2}
name_terminal=${filename##*_}
filever=${name_terminal%%.*}
if [ "${BUILD_TAG}" != "${PROGRAM_VERSION} ${filever}" ]; then
msgln "Deleting ${name}"
curl --no-progress-meter --user "${GITEA_USER}:${PASSWORD}" -X DELETE ${GITEA_HOST}/api/packages/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/${filename}
# else
# echo "most recent version"
fi
done
#curl --user "${GITEA_USER}:${PASSWORD}" -X GET https://git.portale-stac.it/api/v1/packages/go-pkg/generic/ecli/1.7.0/files
-236
View File
@@ -1,236 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// commands.go
package main
import (
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/utils"
)
type commandFunction func(opt *Options, ctx kern.ExprContext, args []string) (err error)
type command struct {
name string
description string
code commandFunction
}
func (cmd *command) exec(opt *Options, ctx kern.ExprContext, args []string) (err error) {
return cmd.code(opt, ctx, args)
}
type commandHandler struct {
cmdIndex []string
commands map[string]*command
}
func NewCommandHandler() *commandHandler {
return &commandHandler{
cmdIndex: make([]string, 0, 20),
commands: make(map[string]*command),
}
}
// func (h *commandHandler) setContext(ctx expr.ExprContext) {
// h.ctx = ctx
// }
func (h *commandHandler) add(name, description string, f commandFunction) {
h.cmdIndex = append(h.cmdIndex, name)
h.commands[name] = &command{name: name, description: description, code: f}
}
func (h *commandHandler) get(cmdLine string) (cmd *command, args []string) {
if len(cmdLine) > 0 {
tokens := strings.Split(cmdLine, " ")
name := tokens[0]
args = make([]string, 0, len(tokens)-1)
if cmd = h.commands[name]; cmd != nil && len(tokens) > 1 {
for _, tk := range tokens[1:] {
if tk != "" {
args = append(args, tk)
}
}
}
}
return
}
// ------
var cmdHandler *commandHandler
func (h *commandHandler) help() {
fmt.Fprintln(os.Stderr, `--- REPL commands:`)
for _, name := range h.cmdIndex {
cmd := h.commands[name]
fmt.Fprintf(os.Stderr, "%12s -- %s\n", cmd.name, cmd.description)
}
fmt.Fprint(os.Stderr, `
--- 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.
-B, --list-builtins List all builtin module names
-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
--noout Disable printing of expression results
-p Print prefix form
-t Print tree form
-v, --version Show program version
`)
}
// --------
func cmdExit(opt *Options, ctx kern.ExprContext, args []string) (err error) {
return io.EOF
}
func cmdHelp(opt *Options, ctx kern.ExprContext, args []string) (err error) {
cmdHandler.help()
return
}
func cmdMultiLine(opt *Options, ctx kern.ExprContext, args []string) (err error) {
if opt.formOpt&kern.MultiLine == 0 {
opt.formOpt |= kern.MultiLine
} else {
opt.formOpt &= ^kern.MultiLine
}
return
}
func cmdTty(opt *Options, ctx kern.ExprContext, args []string) (err error) {
if opt.formOpt&kern.TTY == 0 {
opt.formOpt |= kern.TTY
} else {
opt.formOpt &= ^kern.TTY
}
return
}
func execFile(opt *Options, ctx kern.ExprContext, fileName string) (err error) {
var fh *os.File
if fh, err = os.Open(fileName); err == nil {
goBatch(opt, ctx, fh, false)
fh.Close()
}
return
}
func cmdSource(opt *Options, ctx kern.ExprContext, args []string) (err error) {
var target string
for _, arg := range args {
if len(arg) == 0 {
continue
}
// TODO migliorare questa parte: eventualmente valutare un'espressione
if target, err = checkStringLiteral(arg); err != nil {
break
}
if target, err = utils.ExpandPath(target); err != nil {
break
}
if isPattern(target) {
var fileNames []string
if fileNames, err = matchPathPattern(target); err == nil {
for _, fileName := range fileNames {
if err = execFile(opt, ctx, fileName); err != nil {
break
}
}
}
} else {
err = execFile(opt, ctx, target)
}
if err != nil {
break
}
}
return
}
func cmdModules(opt *Options, ctx kern.ExprContext, args []string) (err error) {
expr.IterateBuiltinModules(func(name, description string, imported bool) bool {
var check rune = ' '
if imported {
check = '*'
}
fmt.Printf("%c %20q: %s\n", check, name, description)
return true
})
return
}
func cmdBase(opt *Options, ctx kern.ExprContext, args []string) (err error) {
if len(args) == 0 {
fmt.Println(opt.base)
} else if args[0] == "2" {
opt.baseVerb = "0b%b"
opt.base = 2
} else if args[0] == "8" {
opt.baseVerb = "0o%o"
opt.base = 8
} else if args[0] == "10" {
opt.baseVerb = "%d"
opt.base = 10
} else if args[0] == "16" {
opt.baseVerb = "0x%x"
opt.base = 16
} else {
err = fmt.Errorf("invalid number base %s", args[0])
}
return
}
func cmdOutput(opt *Options, ctx kern.ExprContext, args []string) (err error) {
var outputArg string
if len(args) == 0 {
outputArg = "status"
} else {
outputArg = strings.ToLower(args[0])
}
switch outputArg {
case "on":
opt.output = true
case "off":
opt.output = false
case "status":
if opt.output {
fmt.Println("on")
} else {
fmt.Println("off")
}
default:
err = fmt.Errorf("output: unknown option %q", outputArg)
}
return
}
//------------------
func setupCommands() {
cmdHandler = NewCommandHandler()
cmdHandler.add("base", "Set the integer output base: 2, 8, 10, or 16", cmdBase)
cmdHandler.add("exit", "Exit the program", cmdExit)
cmdHandler.add("help", "Show command list", cmdHelp)
cmdHandler.add("ml", "Enable/Disable multi-line output", cmdMultiLine)
cmdHandler.add("mods", "List builtin modules", cmdModules)
cmdHandler.add("output", "Enable/Disable printing expression results. Options 'on', 'off', 'status'", cmdOutput)
cmdHandler.add("source", "Load a file as input", cmdSource)
cmdHandler.add("tty", "Enable/Disable ansi output", cmdTty)
}
-314
View File
@@ -1,314 +0,0 @@
= Ecli
Expression Calculator Interactive Tool
:authors: Celestino Amoroso
:email: celestino.amoroso@gmail.com
:docinfo: shared
:encoding: utf-8
:toc: right
:toclevels: 4
:icons: font
:icon-set: fi
:numbered:
:data-uri:
:docinfo1:
:sectlinks:
:sectanchors:
:source-highlighter: rouge
:rouge-style: manni
:stylesdir: /home/share/s3-howto/styles
:stylesheet: adoc-colony.css
// Workaround to manage double-column in back-tick quotes
:2c: ::
// Workaround to manage double-plus in back-tick quotes
:plusplus: ++
// Workaround to manage asterisk in back-tick quotes
:star: *
#Generated by Copilot#
== Overview
`ecli` (Expression Calculator Interactive Tool) is an interactive REPL (Read-Eval-Print Loop) application for evaluating expressions using the Expr package. It provides a powerful command-line interface for expression evaluation with support for multiple builtin modules, file operations, and interactive scripting.
The tool combines the expression evaluation capabilities of the Expr package with an interactive shell environment, making it ideal for:
- Interactive expression testing and prototyping
- Batch expression evaluation from scripts
- Data processing and transformation
- Mathematical computations with fractions and complex operators
== Getting Started
=== Installation
To build and install `ecli`:
[source,bash]
----
cd cmd/ecli
./build.bash
----
The compiled binary will be available as `ecli` in the current directory.
=== Basic Usage
Start the interactive REPL:
[source,bash]
----
./ecli
----
You'll see the prompt `>>> ` where you can enter expressions to evaluate.
=== Command Line Options
[cols="1,4", options="header"]
|===
| Option | Description
| `-e <expression>` | Evaluate an expression directly without entering REPL mode
| `-i` | Force REPL operation after processing all `-e` options
| `-b <builtin>` | Import builtin modules (comma-separated list, glob patterns, or 'all')
| `-B, --list-builtins` | List all available builtin module names
| `-m, --modules` | List all builtin modules
| `-p` | Print expressions in prefix form
| `-t` | Print expressions in tree form
| `--noout` | Disable printing of expression results
| `-h, --help` | Show help message
| `-v, --version` | Show program version
|===
== Interactive Commands
Within the REPL, you can use the following commands:
[cols="2,5", options="header"]
|===
| Command | Description
| `help` | Display available commands and command-line options
| `exit` | Exit the REPL
| `multiline` | Toggle multi-line input mode for complex expressions
| `tty` | Toggle TTY mode
| `source <file>` | Execute expressions from a file
|===
== Features
=== Expression Evaluation
`ecli` supports the full expression language provided by the Expr package, including:
- **Arithmetic Operations**: Addition, subtraction, multiplication, division, modulo
- **Bitwise Operations**: AND, OR, XOR, NOT, shift operations
- **Boolean Logic**: AND, OR, NOT operations
- **Relational Operators**: Comparison and equality operators
- **String Operations**: Concatenation and string manipulation
- **Iterators**: Range, list, and custom iterators
- **Functions**: Builtin and user-defined functions
- **Collections**: Lists, dictionaries, and linked lists
- **Fractions**: Support for fractional arithmetic
=== Builtin Modules
`ecli` provides access to various builtin modules through the `-b` option:
[cols="1,4", options="header"]
|===
| Module | Functionality
| `base` | Core expression evaluation functions
| `fmt` | String formatting and output functions
| `string` | String manipulation functions
| `math-arith` | Mathematical and arithmetic operations
| `iterator` | Iterator-related functions
| `os-file` | File I/O operations
| `import` | Module import functionality
|===
Use `-B` or `--list-builtins` to see all available modules:
[source,bash]
----
./ecli --list-builtins
----
== Examples
=== Basic Arithmetic
[source,bash]
----
>>> 2 + 3 * 4
14
>>> (2 + 3) * 4
20
----
=== String Operations
[source,bash]
----
>>> "hello" + " " + "world"
hello world
----
=== Using Iterators
[source,bash]
----
>>> [1, 2, 3, 4, 5] | map(. * 2)
[2, 4, 6, 8, 10]
----
=== Evaluating from Command Line
[source,bash]
----
./ecli -e "2 + 2"
4
----
=== Loading Builtin Modules
[source,bash]
----
./ecli -b "math-arith,string"
----
=== Loading Expressions from Files
Inside the REPL:
[source]
----
>>> source "expressions.expr"
----
Or from command line:
[source,bash]
----
./ecli -e '@include "expressions.expr"'
----
== Configuration
=== Resource Files
`ecli` supports startup resource files:
- `.ecli.rc` - Main configuration file
- `.ecli.rc.d/` - Directory for modular configuration files
These files are automatically loaded at startup if they exist in the current directory or home directory.
== Building from Source
=== Prerequisites
- Go 1.18 or later
- Make or bash shell
=== Build Steps
[source,bash]
----
cd cmd/ecli
./build.bash
----
=== Build Artifacts
The build process generates:
- `ecli` - The main executable
- `version.txt` - Version information
- Platform-specific binaries (e.g., `ecli_v1.17.0_linux_amd64`, `ecli_v1.17.0_darwin_arm64`)
== Advanced Usage
=== Multi-line Input
For complex expressions, toggle multi-line mode:
[source]
----
>>> multiline
>>> result = [1, 2, 3, 4, 5]
... | filter(. > 2)
... | map(. * 2)
>>> result
[6, 8, 10]
----
=== Script Execution
Create a file `calculations.expr`:
[source]
----
x = 10
y = 20
result = x + y * 2
----
Execute it:
[source,bash]
----
./ecli -e '@source "calculations.expr"' -e 'result'
----
=== Chaining Operations
[source]
----
>>> data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
>>> data | map(.name)
[alice, bob]
----
== Troubleshooting
=== Expression Parsing Errors
If you encounter parsing errors, check:
- Bracket matching and quotation marks
- Operator precedence
- Variable and function names
=== Module Loading Issues
Verify available modules:
[source,bash]
----
./ecli --list-builtins
----
=== File Not Found Errors
Ensure file paths are:
- Properly quoted in expressions
- Relative to the current working directory or absolute paths
- Readable by the current user
== Related Documentation
- link:../../../README.adoc[Expr Package Documentation]
- Expr Expression Language Syntax
- Builtin Modules Reference
== Version History
For version information and changes, see the link:version.txt[version file].
== License
Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). All rights reserved.
-15
View File
@@ -1,15 +0,0 @@
module ecli
go 1.24.0
require (
git.portale-stac.it/go-pkg/expr v0.33.0
git.portale-stac.it/go-pkg/utils v0.3.0
github.com/ergochat/readline v0.1.3
)
require (
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
)
-36
View File
@@ -1,36 +0,0 @@
git.portale-stac.it/go-pkg/expr v0.1.0 h1:7xGEuUhdh6RRFaRbRnLVqVJBmHJWHfqjDBm2K0fIW2s=
git.portale-stac.it/go-pkg/expr v0.1.0/go.mod h1:kUFEQkUMCJ1IiUKkL0P5/vznaAIzFI26Xf5P0rTXqR0=
git.portale-stac.it/go-pkg/expr v0.2.0 h1:AAaVsV0uaC4EikKU91VuubIpbIN7wuya7t4avyFgg+0=
git.portale-stac.it/go-pkg/expr v0.2.0/go.mod h1:DZqqZ3A9h4qEOs7yMvG4VZq7B/xhFsYqC3IKd3M2VKc=
git.portale-stac.it/go-pkg/expr v0.17.0 h1:4ANGwJfwJO3AmnKka4Cf1AO9/ckGLMj8RIWeoDFKawQ=
git.portale-stac.it/go-pkg/expr v0.17.0/go.mod h1:DZqqZ3A9h4qEOs7yMvG4VZq7B/xhFsYqC3IKd3M2VKc=
git.portale-stac.it/go-pkg/expr v0.32.0 h1:ikXqHjJslIGkD79G1/51xe+c25TFi2CslJ6nu8mOuJY=
git.portale-stac.it/go-pkg/expr v0.32.0/go.mod h1:R2TYIahLtD8YDgNEHtgHCQdoEUZ7yCQsMHyJXhJijmw=
git.portale-stac.it/go-pkg/expr v0.33.0 h1:GJ7PPgA1689GSC/cUWGYm08jn7qMmkp0FMQf/As5sCw=
git.portale-stac.it/go-pkg/expr v0.33.0/go.mod h1:R2TYIahLtD8YDgNEHtgHCQdoEUZ7yCQsMHyJXhJijmw=
git.portale-stac.it/go-pkg/utils v0.2.0 h1:2l4IVUhElzjaIUJlahPG2DZTGb9x7OXuFTO4z1K6LmY=
git.portale-stac.it/go-pkg/utils v0.2.0/go.mod h1:PebQ45Qbe89aMTd3wcbcx1bkpNRW4/frNLnpuyZYovU=
git.portale-stac.it/go-pkg/utils v0.3.0 h1:kCJ3+XcekV7in/SieJjiswdtJKMBS0RTJMlG2fW5mK0=
git.portale-stac.it/go-pkg/utils v0.3.0/go.mod h1:PebQ45Qbe89aMTd3wcbcx1bkpNRW4/frNLnpuyZYovU=
github.com/ergochat/readline v0.1.0 h1:KEIiAnyH9qGZB4K8oq5mgDcExlEKwmZDcyyocgJiABc=
github.com/ergochat/readline v0.1.0/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
github.com/ergochat/readline v0.1.1 h1:C8Uuo3ybB23GWOt0uxmHbGzKM9owmtXary6Clrj84s0=
github.com/ergochat/readline v0.1.1/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
github.com/ergochat/readline v0.1.3 h1:/DytGTmwdUJcLAe3k3VJgowh5vNnsdifYT6uVaf4pSo=
github.com/ergochat/readline v0.1.3/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
-18
View File
@@ -1,18 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
//go:build graph
// graph.go
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func printGraph() {
r := expr.NewExprReticle(ast)
fmt.Println(r.String())
}
-415
View File
@@ -1,415 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// main.go
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/utils"
// https://pkg.go.dev/github.com/ergochat/readline#section-readme
"github.com/ergochat/readline"
)
const (
intro = PROGNAME + ` -- Expressions calculator ` + VERSION + `
Based on the Expr package ` + EXPR_VERSION + ` (` + EXPR_DATE + `)
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
`
mainPrompt = ">>> "
contPrompt = "... "
historyFile = "~/.expr_history"
)
// ------
func errOptValueRequired(opt string) error {
return fmt.Errorf("option %q requires a value", opt)
}
func about() string {
return PROGNAME + " -- " + VERSION + "; Expr package " + EXPR_VERSION
}
func importBuiltins(ctx kern.ExprContext, opt *Options) (err error) {
for _, spec := range opt.builtin {
if moduleSpec, ok := spec.(string); ok {
if moduleSpec == "all" {
moduleSpec = "*"
}
_, err = expr.ImportInContextByGlobPattern(ctx, moduleSpec)
} else if moduleSpec, ok := spec.([]string); ok {
notFoundList := make([]string, 0)
for _, name := range moduleSpec {
if !expr.ImportInContext(ctx, name) {
notFoundList = append(notFoundList, name)
}
}
if len(notFoundList) > 0 {
err = fmt.Errorf("not found modules: %s", strings.Join(notFoundList, ","))
}
}
}
return
}
func initReadlineConfig(cfg *readline.Config) {
if histfile, err := utils.ExpandPath(historyFile); err == nil {
cfg.HistoryFile = histfile
}
cfg.Undo = true
cfg.DisableAutoSaveHistory = true
}
func goInteractiveReadline(opt *Options, ctx kern.ExprContext, r io.Reader) {
var sb strings.Builder
var cfg readline.Config
initReadlineConfig(&cfg)
rl, err := readline.NewFromConfig(&cfg)
if err != nil {
goInteractive(opt, ctx, r)
return
}
defer rl.Close()
fmt.Print(intro)
rl.SetPrompt(mainPrompt)
for line, err := rl.ReadLine(); err == nil; line, err = rl.ReadLine() {
if continuation(&sb, line) {
rl.SetPrompt(contPrompt)
continue
}
rl.SetPrompt(mainPrompt)
sb.WriteString(line)
source := strings.TrimSpace(sb.String())
if source != "" && !strings.HasPrefix(source, "//") {
if cmd, args := cmdHandler.get(source); cmd != nil {
rl.SaveToHistory(source)
if err = cmd.exec(opt, ctx, args); err != nil {
if err == io.EOF {
err = nil
break
} else {
fmt.Fprintln(os.Stderr, "Eval Error:", err)
break
}
}
} else {
rl.SaveToHistory(source)
r := strings.NewReader(source)
compute(opt, ctx, r, true)
}
}
sb.Reset()
}
fmt.Println()
}
func goInteractive(opt *Options, ctx kern.ExprContext, r io.Reader) {
var sb strings.Builder
fmt.Print(intro)
fmt.Print(mainPrompt)
reader := bufio.NewReaderSize(r, 1024)
for line, err := reader.ReadString('\n'); err == nil && line != "exit\n"; line, err = reader.ReadString('\n') {
if continuation(&sb, line) {
continue
}
sb.WriteString(line)
source := strings.TrimSpace(sb.String())
// fmt.Printf("source=%q\n", source)
if source != "" && !strings.HasPrefix(source, "//") {
if cmd, args := cmdHandler.get(source); cmd != nil {
if err = cmd.exec(opt, ctx, args); err != nil {
break
}
} else {
r := strings.NewReader(source)
compute(opt, ctx, r, true)
}
}
sb.Reset()
fmt.Print(mainPrompt)
}
fmt.Println()
}
func continuation(sb *strings.Builder, line string) (cont bool) {
line = strings.TrimSpace(line)
if strings.HasSuffix(line, "\\") {
sb.WriteString(line[0 : len(line)-1])
cont = true
} else if strings.HasSuffix(line, ";") {
sb.WriteString(line)
cont = true
} else if len(line) > 0 {
if scan.StringEndsWithOperator(line) {
sb.WriteString(line)
cont = true
} else {
fullInput := sb.String() + line
if strings.Count(fullInput, "(") > strings.Count(fullInput, ")") ||
strings.Count(fullInput, "[") > strings.Count(fullInput, "]") ||
strings.Count(fullInput, "{") > strings.Count(fullInput, "}") {
sb.WriteString(line)
cont = true
}
}
}
return
}
func goBatch(opt *Options, ctx kern.ExprContext, r io.Reader, outputEnabled bool) {
var sb strings.Builder
reader := bufio.NewReaderSize(r, 1024)
for line, err := reader.ReadString('\n'); err == nil && line != "exit\n"; line, err = reader.ReadString('\n') {
if continuation(&sb, line) {
continue
}
sb.WriteString(line)
source := strings.TrimSpace(sb.String())
// fmt.Printf("source=%q\n", source)
if source != "" && !strings.HasPrefix(source, "//") {
if cmd, args := cmdHandler.get(source); cmd != nil {
if err = cmd.exec(opt, ctx, args); err != nil {
fmt.Fprintln(os.Stderr, "Eval Error:", err)
break
}
} else {
r := strings.NewReader(source)
compute(opt, ctx, r, outputEnabled)
}
}
sb.Reset()
}
}
func compute(opt *Options, ctx kern.ExprContext, r io.Reader, outputEnabled bool) {
scanner := scan.NewScanner(r, scan.DefaultTranslations())
parser := expr.NewParser()
if ast, err := parser.Parse(scanner); err == nil {
if opt.printPrefix {
fmt.Println(ast)
}
if opt.printTree {
printGraph()
}
if result, err := ast.Eval(ctx); err == nil {
if outputEnabled && opt.output {
printResult(opt, result)
}
} else {
fmt.Fprintln(os.Stderr, "Eval Error:", err)
}
} else {
fmt.Fprintln(os.Stderr, "Parse Error:", err)
}
}
func printResult(opt *Options, result any) {
if f, ok := result.(kern.Formatter); ok {
fmt.Println(f.ToString(opt.formOpt))
} else if kern.IsInteger(result) {
fmt.Printf(opt.baseVerb, result)
fmt.Println()
} else if kern.IsString(result) {
fmt.Printf("\"%s\"\n", result)
} else {
fmt.Println(result)
}
}
func isReaderTerminal(r io.Reader) bool {
if fh, ok := r.(*os.File); ok {
return utils.StreamIsTerminal(fh)
}
return false
}
func registerLocalFunctions(ctx kern.ExprContext) {
const (
devParamProp = "prop"
devParamDigits = "digits"
)
aboutFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = about()
return
}
ctrlListFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
vars := ctx.EnumVars(func(name string) bool {
return len(name) > 0 && name[0] == '_'
})
result = kern.ListFromStrings(vars)
return
}
ctrlFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
varName, _ := args[devParamProp].(string)
if len(args) == 1 {
result = expr.GlobalCtrlGet(ctx, varName)
} else {
result = expr.GlobalCtrlSet(ctx, varName, args[kern.ParamValue])
}
return
}
envSetFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName, value string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamName])
return
}
if value, ok = args[kern.ParamValue].(string); !ok {
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamValue])
return
}
if err = os.Setenv(varName, value); err == nil {
result = value
}
return
}
envGetFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamName])
return
}
if result, ok = os.LookupEnv(varName); !ok {
err = fmt.Errorf("environment variable %q does not exist", varName)
}
return
}
envListFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
env := os.Environ()
vars := make([]string, 0, len(env))
for _, e := range env {
name, _, _ := strings.Cut(e, "=")
vars = append(vars, name)
}
result = kern.ListFromStrings(vars)
return
}
binFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var value, digits int64
var ok bool
var sb strings.Builder
if value, ok = args[kern.ParamValue].(int64); !ok {
err = kern.ErrExpectedGot(name, kern.TypeInt, args[kern.ParamValue])
return
}
if digits, ok = args[devParamDigits].(int64); !ok {
err = kern.ErrExpectedGot(name, kern.TypeInt, args[devParamDigits])
return
}
if digits != 64 && digits != 32 && digits != 16 && digits != 8 {
err = fmt.Errorf("%s param allows 8, 16, 32, or 64 values only", devParamDigits)
return
}
mask := uint64(0)
for i := 0; i < int(digits); i++ {
mask |= (1 << i)
}
maskedValue := uint64(value) & mask
// if maskedValue != uint64(value) {
// err = fmt.Errorf("%s param (%d) is not compatible with the value (%d) of %s param", expr.ParamValue, value, digits, devParamDigits)
// return
// }
for i := int(digits) - 1; i >= 0; i-- {
if maskedValue&(1<<i) == 0 {
sb.WriteByte('0')
} else {
sb.WriteByte('1')
}
}
result = sb.String()
return
}
ctx.RegisterFunc("about", kern.NewGolangFunctor(aboutFunc), kern.TypeString, []kern.ExprFuncParam{})
ctx.RegisterFunc("ctrlList", kern.NewGolangFunctor(ctrlListFunc), kern.TypeListOfStrings, []kern.ExprFuncParam{})
ctx.RegisterFunc("ctrl", kern.NewGolangFunctor(ctrlFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(devParamProp),
kern.NewFuncParamFlag(kern.ParamValue, kern.PfOptional),
})
ctx.RegisterFunc("envSet", kern.NewGolangFunctor(envSetFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
kern.NewFuncParam(kern.ParamValue),
})
ctx.RegisterFunc("envGet", kern.NewGolangFunctor(envGetFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
})
ctx.RegisterFunc("envList", kern.NewGolangFunctor(envListFunc), kern.TypeListOfStrings, []kern.ExprFuncParam{})
ctx.RegisterFunc("bin", kern.NewGolangFunctor(binFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
kern.NewFuncParamFlagDef(devParamDigits, kern.PfOptional|kern.PfDefault, int64(8)),
})
}
func main() {
setupCommands()
opt := NewOptions()
opt.loadRc()
if err := opt.parseArgs(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx := expr.NewSimpleStore()
if err := importBuiltins(ctx, opt); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
registerLocalFunctions(ctx)
if len(opt.expressions) == opt.rcCount || opt.forceInteractive {
opt.expressions = append(opt.expressions, os.Stdin)
}
for _, input := range opt.expressions {
if isReaderTerminal(input) {
goInteractiveReadline(opt, ctx, input)
} else {
_, enableOutput := input.(*strings.Reader)
goBatch(opt, ctx, input, enableOutput)
if f, ok := input.(*os.File); ok {
f.Close()
}
}
}
// TODO: why did I added these lines?
// if opt.output {
// printResult(opt, ctx.GetLast())
// }
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// match.go
package main
import (
"os"
"path"
"path/filepath"
"strings"
)
func matchFilePattern(dirName string, pattern string, join bool) (fileList []string, err error) {
var entries []os.DirEntry
if entries, err = os.ReadDir(dirName); err == nil {
for _, entry := range entries {
if entry.Type().IsRegular() {
var match bool
if match, err =filepath.Match(pattern, entry.Name()); err != nil {
fileList = nil
break
}
if match {
if fileList == nil {
fileList = make([]string, 0, 1)
}
if join {
fileList = append(fileList, path.Join(dirName, entry.Name()))
} else {
fileList = append(fileList, entry.Name())
}
}
}
}
}
return
}
func matchPathPattern(pathPattern string) (fileList []string, err error) {
dirName := path.Dir(pathPattern)
pattern := path.Base(pathPattern)
return matchFilePattern(dirName, pattern, true)
}
func isPattern(name string) bool{
return strings.ContainsAny(name, "*?[]")
}
-36
View File
@@ -1,36 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// main.go
package main
import (
"path"
"slices"
"testing"
)
func TestIsPattern(t *testing.T) {
target := "non-pattern"
if isPattern(target) {
t.Errorf("%q recognized as a pattern", target)
}
target = "pattern/*.expr"
if !isPattern(target) {
t.Errorf("%q not recognized as a pattern", target)
}
}
func TestMatchFilePattern(t *testing.T) {
target := "./go.*sum"
dirName := path.Dir(target)
pattern := path.Base(target)
if matchedFiles, err := matchFilePattern(dirName, pattern, true); err == nil {
if slices.Compare(matchedFiles, []string{"go.sum", "go.work.sum"}) != 0 {
t.Errorf("Matched file list is not correct: %v", matchedFiles)
}
} else {
t.Errorf("Got error: %v", err)
}
}
-140
View File
@@ -1,140 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// options.go
package main
import (
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/utils"
)
type Options struct {
printTree bool
printPrefix bool
forceInteractive bool
builtin []any
expressions []io.Reader
formOpt kern.FmtOpt
baseVerb string
base int
output bool
rcCount int
}
func NewOptions() *Options {
return &Options{
expressions: make([]io.Reader, 0),
builtin: make([]any, 0),
formOpt: kern.Base10,
baseVerb: "%d",
base: 10,
output: true,
rcCount: 0,
}
}
func (opt *Options) loadRc() {
var rcPath string
var fh *os.File
var err error
rcList := []string{
".ecli.rc",
"~/.ecli.rc",
"~/.config/expr/ecli.rc",
"~/.dev-expr.rc", // OBSOLETE, to be removed in future releases
}
for _, rcFile := range rcList {
if rcPath, err = utils.ExpandPath(rcFile); err != nil {
return
}
if fh, err = os.Open(rcPath); err == nil {
opt.expressions = append(opt.expressions, fh) // rc should be the first source to be read
opt.rcCount++
break
}
}
}
func listBuiltins() {
expr.IterateBuiltinModules(func(name, description string, imported bool) bool {
if imported {
name = "*" + name
}
fmt.Printf("%20q: %s\n", name, description)
return true
})
}
func (opt *Options) parseArgs() (err error) {
for i := 1; i < len(os.Args) && err == nil; i++ {
arg := os.Args[i]
switch arg {
case "-i":
opt.forceInteractive = true
case "-t":
opt.printTree = true
case "-p":
opt.printPrefix = true
case "-e":
if i+1 < len(os.Args) {
i++
spec := os.Args[i]
if strings.HasPrefix(spec, "@") {
var f *os.File
if f, err = os.Open(spec[1:]); err == nil {
opt.expressions = append(opt.expressions, f)
} else {
return
}
} else {
if len(spec) > 0 && spec[len(spec)-1] != '\n' {
spec += "\n"
}
opt.expressions = append(opt.expressions, strings.NewReader(spec))
}
} else {
err = errOptValueRequired(arg)
}
case "-b":
if i+1 < len(os.Args) {
i++
specs := strings.Split(os.Args[i], ",")
if len(specs) == 1 {
opt.builtin = append(opt.builtin, specs[0])
} else {
opt.builtin = append(opt.builtin, specs)
}
} else {
err = errOptValueRequired(arg)
}
case "-B", "--list-builtins":
listBuiltins()
os.Exit(0)
case "-m", "--modules":
expr.IterateBuiltinModules(func(name, description string, _ bool) bool {
fmt.Printf("%20q: %s\n", name, description)
return true
})
os.Exit(0)
case "--noout":
opt.output = false
case "-h", "--help", "help":
cmdHandler.help()
os.Exit(0)
case "-v", "--version", "version", "about":
fmt.Println(about())
os.Exit(0)
default:
err = fmt.Errorf("invalid option nr %d %q", i+1, arg)
}
}
return
}
-79
View File
@@ -1,79 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
# All rights reserved.
RESOURCE_FILE=".build.rc"
BUILD_REGISTER=".build_register"
PASSWORD=
GITEA_USER="camoroso"
GITEA_PASSWORD_FILE="${HOME}/.gitea_password"
GITEA_OWNER="go-pkg"
GITEA_HOST="https://git.portale-stac.it"
GITEA_BASE_PATH="api/packages"
GITEA_PKG_TYPE="generic"
function exitMsg() {
echo >&2 "${1}"
exit 1
}
function readBuildCount() {
local reg ver count
if [ -r "${BUILD_REGISTER}" ]; then
reg=$(<"${BUILD_REGISTER}")
else
reg="${PROGRAM_VERSION} 0"
fi
read ver count <<<"${reg}"
if [ "${ver}" != "${PROGRAM_VERSION}" ]; then
count=0
fi
echo ${count}
}
if [ -r "${GITEA_PASSWORD_FILE}" ]; then
if ! PASSWORD=$(<"${GITEA_PASSWORD_FILE}"); then
exitMsg "Can're password file '${GITEA_PASSWORD_FILE}'"
fi
else
exitMsg "Password file '${GITEA_PASSWORD_FILE}' not found"
fi
if [ -z "${PASSWORD}" ]; then
exitMsg "Empty password. Please, check file '${GITEA_PASSWORD_FILE}'"
fi
if ! ./build.bash; then
exitMsg "Build program failed"
fi
if ! source "${RESOURCE_FILE}"; then
exitMsg "Loading resource file failed"
fi
if ! exeList=$(echo 2>/dev/null ${PROGRAM_NAME}_v${PROGRAM_VERSION}_*); then
exitMsg "No executable found"
fi
buildCount=$(readBuildCount)
fileCount=0
for exe in ${exeList}; do
if [ "${exe/tar.gz/}" != "${exe}" ]; then
continue
fi
((fileCount++))
dir="${exe}_${buildCount}"
dist="${dir}.tar.gz"
rm -f "${dist}"
printf "%2d: %-30s --> %s\n" "${fileCount}" "${exe}" "${dist}"
mkdir "${dir}"
cp "${exe}" "${dir}/${PROGRAM_NAME}"
tar czf "${dist}" "${dir}"
rm -fR "${dir}"
url="${GITEA_HOST}/${GITEA_BASE_PATH}/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/${dist}"
# echo "${url}"
curl --user "${USER}:${PASSWORD}" --upload-file "${dist}" "${url}"
rm -f "${dist}"
done
-23
View File
@@ -1,23 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// util-string.go
package main
import (
"fmt"
)
func checkStringLiteral(literal string) (value string, err error) {
length := len(literal)
if length >= 2 {
if (literal[0] == '"' && literal[length-1] == '"') || literal[0] == '\'' && literal[length-1] == '\'' {
value = literal[1 : length-1]
} else {
err = fmt.Errorf("unquoted or partially quoted string literal: `%s`", literal)
}
} else {
err = fmt.Errorf("invalid string literal: `%s`", literal)
}
return
}
-1
View File
@@ -1 +0,0 @@
1.17.0
+42
View File
@@ -0,0 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context.go
package expr
// ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
// ---- Functor interface
type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
}
type simpleFunctor struct {
f FuncTemplate
}
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
// ---- Function Info
type ExprFunc interface {
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
}
// ----Expression Context
type ExprContext interface {
Clone() ExprContext
GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any)
setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) ExprFunc
Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// control.go
package expr
import "strings"
// Preset control variables
const (
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlImportPath = "_import_path"
)
// Other control variables
const (
control_export_all = "_export_all"
)
// Initial values
const (
init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
)
func initDefaultVars(ctx ExprContext) {
ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlImportPath, init_import_path)
}
func enable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, true)
} else {
ctx.SetVar("_"+name, true)
}
}
func disable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, false)
} else {
ctx.SetVar("_"+name, false)
}
}
func isEnabled(ctx ExprContext, name string) (status bool) {
if v, exists := ctx.GetVar(name); exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
var v any
if v, exists = ctx.GetVar(name); exists {
s, exists = v.(string)
}
return
}
-307
View File
@@ -1,307 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// data-cursors.go
package expr
import (
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type dataCursor struct {
ds map[string]kern.Functor
ctx kern.ExprContext
initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
// cursorValid bool // true if resource is nil or if clean has not yet been called
index int64
count int64
current any
lastErr error
resource any
nextFunc kern.Functor
cleanFunc kern.Functor
resetFunc kern.Functor
}
func NewDataCursor(ctx kern.ExprContext, ds map[string]kern.Functor, resource any) (dc *dataCursor) {
dc = &dataCursor{
ds: ds,
initState: true,
// cursorValid: true,
index: -1,
count: 0,
current: nil,
lastErr: nil,
resource: resource,
ctx: ctx.Clone(),
nextFunc: ds[kern.NextName],
cleanFunc: ds[kern.CleanName],
resetFunc: ds[kern.ResetName],
}
return
}
func (dc *dataCursor) Context() kern.ExprContext {
return dc.ctx
}
func (dc *dataCursor) TypeName() string {
return "DataCursor"
}
// 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 = slices.Contains([]string{kern.CleanName, kern.ResetName, kern.CurrentName, kern.IndexName}, name)
if !exists {
f, ok := dc.ds[name]
exists = ok && kern.IsFunctor(f)
}
return
}
func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
if name == kern.IndexName {
value = int64(dc.Index())
} else if name == kern.CleanName {
err = dc.Clean()
} else if name == kern.ResetName {
err = dc.Reset()
} else if functor, ok := dc.ds[name]; ok && kern.IsFunctor(functor) {
ctx := kern.CloneContext(dc.ctx)
value, err = functor.InvokeNamed(ctx, name, args)
kern.ExportObjects(dc.ctx, ctx)
} else {
err = kern.ErrNoOperation(name)
}
return
}
// func (dc *dataCursor) Reset() (err error) {
// if dc.resetFunc != nil {
// if dc.resource != nil {
// ctx := cloneContext(dc.ctx)
// actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
// _, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
// exportObjects(dc.ctx, ctx)
// dc.index = -1
// dc.count = 0
// dc.initState = true
// dc.current = nil
// dc.lastErr = nil
// } else {
// err = errInvalidDataSource()
// }
// } else {
// err = errNoOperation(ResetName)
// }
// return
// }
func (dc *dataCursor) Reset() (err error) {
if dc.resetFunc != nil {
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(dc.resetFunc, []any{dc.resource})
_, err = dc.resetFunc.InvokeNamed(ctx, kern.ResetName, actualParams)
kern.ExportObjects(dc.ctx, ctx)
}
dc.index = -1
dc.count = 0
dc.initState = true
dc.current = nil
dc.lastErr = nil
return
}
func (dc *dataCursor) Clean() (err error) {
if dc.cleanFunc != nil {
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(dc.cleanFunc, []any{dc.resource})
_, err = dc.cleanFunc.InvokeNamed(ctx, kern.CleanName, actualParams)
kern.ExportObjects(dc.ctx, ctx)
}
dc.lastErr = io.EOF
return
}
// func (dc *dataCursor) Clean() (err error) {
// if dc.cleanFunc != nil {
// if dc.resource != nil {
// ctx := cloneContext(dc.ctx)
// actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
// _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
// exportObjects(dc.ctx, ctx)
// } else {
// err = errInvalidDataSource()
// }
// } else {
// err = errNoOperation(CleanName)
// }
// return
// }
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
dc.init()
if dc.current != nil {
item = dc.current
} else {
err = io.EOF
}
return
}
func (dc *dataCursor) checkFilter(filter kern.Functor, item any) (accepted bool, err error) {
var v any
var ok bool
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(filter, []any{item, dc.index})
if v, err = filter.InvokeNamed(ctx, kern.FilterName, actualParams); err == nil && v != nil {
if accepted, ok = v.(bool); !ok {
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
}
return
}
func (dc *dataCursor) mapItem(mapper kern.Functor, item any) (mappedItem any, err error) {
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(mapper, []any{item, dc.index})
mappedItem, err = mapper.InvokeNamed(ctx, kern.MapName, actualParams)
return
}
func (dc *dataCursor) init() {
if dc.initState {
dc.initState = false
dc.Next()
}
}
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
if dc.initState {
dc.init()
} else if err = dc.lastErr; err != nil {
return
}
current = dc.current
filter := dc.ds[kern.FilterName]
mapper := dc.ds[kern.MapName]
var item any
for item == nil && dc.lastErr == nil {
ctx := kern.CloneContext(dc.ctx)
dc.index++
actualParams := kern.BindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, kern.NextName, actualParams); dc.lastErr == nil {
if item == nil {
dc.lastErr = io.EOF
} else {
accepted := true
if filter != nil {
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
item = nil
}
}
if accepted {
dc.count++
}
if item != nil && mapper != nil {
item, dc.lastErr = dc.mapItem(mapper, item)
}
}
}
kern.ExportObjects(dc.ctx, ctx)
}
dc.current = item
if dc.lastErr != nil {
dc.index--
dc.Clean()
}
return
}
// func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
// if dc.initState {
// dc.init()
// } else if err = dc.lastErr; err != nil {
// return
// }
// current = dc.current
// if dc.resource != nil {
// filter := dc.ds[FilterName]
// mapper := dc.ds[MapName]
// var item any
// for item == nil && dc.lastErr == nil {
// ctx := cloneContext(dc.ctx)
// dc.index++
// actualParams := bindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
// if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
// if item == nil {
// dc.lastErr = io.EOF
// } else {
// accepted := true
// if filter != nil {
// if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
// item = nil
// }
// }
// if accepted {
// dc.count++
// }
// if item != nil && mapper != nil {
// item, dc.lastErr = dc.mapItem(mapper, item)
// }
// }
// }
// exportObjects(dc.ctx, ctx)
// }
// dc.current = item
// if dc.lastErr != nil {
// dc.index--
// dc.Clean()
// }
// } else {
// dc.lastErr = errInvalidDataSource()
// }
// return
// }
func (dc *dataCursor) Index() int64 {
return dc.index - 1
}
func (dc *dataCursor) Count() int64 {
return dc.count
}
-225
View File
@@ -1,225 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
)
type dictIterMode int
const (
dictIterModeKeys dictIterMode = iota
dictIterModeValues
dictIterModeItems
)
type DictIterator struct {
a *kern.DictType
count int64
index int64
keys []any
iterMode dictIterMode
}
type sortType int
const (
sortTypeNone sortType = iota
sortTypeAsc
sortTypeDesc
sortTypeDefault = sortTypeAsc
)
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
it.keys = make([]any, 0, len(m))
if sort == sortTypeNone {
for keyAny := range m {
it.keys = append(it.keys, keyAny)
}
} else {
scalarMap := make(map[string]any, len(m))
scalerKeys := make([]string, 0, len(m))
for keyAny := range m {
keyStr := fmt.Sprint(keyAny)
scalarMap[keyStr] = keyAny
scalerKeys = append(scalerKeys, keyStr)
}
switch sort {
case sortTypeAsc:
slices.Sort(scalerKeys)
case sortTypeDesc:
slices.Sort(scalerKeys)
slices.Reverse(scalerKeys)
}
for _, keyStr := range scalerKeys {
it.keys = append(it.keys, scalarMap[keyStr])
}
}
}
func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone
var s string
var argAny any
dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
if len(args) > 0 {
argAny = args[0]
} else {
argAny = "default"
}
if s, err = kern.ToGoString(argAny, "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
}
if err == nil {
if len(args) > 1 {
argAny = args[1]
} else {
argAny = "default"
}
if s, err = kern.ToGoString(argAny, "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
dictIt.iterMode = dictIterModeKeys
case "v", "value", "values":
dictIt.iterMode = dictIterModeValues
case "i", "item", "items":
dictIt.iterMode = dictIterModeItems
case "", "default":
dictIt.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
}
}
}
if err == nil {
dictIt.makeKeys(*dict, sortType)
it = dictIt
}
return
}
func NewMapIterator(m map[any]any) (it *DictIterator) {
it = &DictIterator{a: (*kern.DictType)(&m), count: 0, index: -1, keys: nil}
it.makeKeys(m, sortTypeNone)
return
}
func (it *DictIterator) String() string {
var l = int64(0)
if it.a != nil {
l = int64(len(*it.a))
}
return fmt.Sprintf("$({#%d})", l)
}
func (it *DictIterator) TypeName() string {
return "DictIterator"
}
func (it *DictIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName, kern.KeyName, kern.ValueName}, name)
return yes
}
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
case kern.KeyName:
if it.index >= 0 && it.index < int64(len(it.keys)) {
v = it.keys[it.index]
} else {
err = io.EOF
}
case kern.ValueName:
if it.index >= 0 && it.index < int64(len(it.keys)) {
a := *(it.a)
v = a[it.keys[it.index]]
} else {
err = io.EOF
}
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *DictIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < int64(len(it.keys)) {
switch it.iterMode {
case dictIterModeKeys:
item = it.keys[it.index]
case dictIterModeValues:
a := *(it.a)
item = a[it.keys[it.index]]
case dictIterModeItems:
a := *(it.a)
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
item = kern.NewList(pair)
}
} else {
err = io.EOF
}
return
}
func (it *DictIterator) Next() (item any, err error) {
it.index++
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *DictIterator) Index() int64 {
return it.index
}
func (it *DictIterator) Count() int64 {
return it.count
}
func (it *DictIterator) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *DictIterator) Clean() error {
return nil
}
+139 -1832
View File
File diff suppressed because it is too large Load Diff
-4047
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr_test.go
package expr
import (
"fmt"
"strings"
"testing"
)
func TestExpr(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
}
succeeded := 0
failed := 0
inputs1 := []inputType{
{`f=openFile("/tmp/test2.txt"); line=readFile(f); closeFile(f); line`, "ciao", nil},
//{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
}
for i, input := range inputs1 {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
ImportOsFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
-322
View File
@@ -1,322 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>file: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">git.portale-stac.it/go-pkg/expr/file/file.go (88.9%)</option>
<option value="file1">git.portale-stac.it/go-pkg/expr/file/reader.go (77.8%)</option>
<option value="file2">git.portale-stac.it/go-pkg/expr/file/writer.go (100.0%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// file.go
package file
import (
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
type Handle interface {
kern.Typer
GetFile() *os.File
GetName() string
Valid() bool
Close() error
}
type handleBase struct {
fh *os.File
}
func (h *handleBase) GetFile() *os.File <span class="cov0" title="0">{
return h.fh
}</span>
func (h *handleBase) GetName() (name string) <span class="cov8" title="1">{
if h.fh != nil </span><span class="cov8" title="1">{
name = h.fh.Name()
}</span>
<span class="cov8" title="1">return</span>
}
func (h *handleBase) Valid() bool <span class="cov8" title="1">{
return h.fh != nil
}</span>
func (h *handleBase) Close() (err error) <span class="cov8" title="1">{
if h.fh != nil </span><span class="cov8" title="1">{
err = h.fh.Close()
h.fh = nil
}</span>
<span class="cov8" title="1">return</span>
}
</pre>
<pre class="file" id="file1" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// reader.go
package file
import (
"bufio"
"io"
"os"
)
type Reader struct {
// fh *os.File
handleBase
reader *bufio.Reader
}
func NewReader(fh *os.File) *Reader <span class="cov8" title="1">{
return &amp;Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)}
}</span>
func OpenReader(filePath string) (r *Reader, err error) <span class="cov8" title="1">{
var fh *os.File
if fh, err = os.Open(filePath); err == nil </span><span class="cov8" title="1">{
r = NewReader(fh)
}</span>
<span class="cov8" title="1">return</span>
}
func (h *Reader) TypeName() string <span class="cov8" title="1">{
return "fileReader"
}</span>
func (h *Reader) String() string <span class="cov8" title="1">{
return "reader"
}</span>
func (h *Reader) Valid() bool <span class="cov8" title="1">{
return h.handleBase.Valid() &amp;&amp; h.reader != nil
}</span>
func (w *Reader) Close() (err error) <span class="cov8" title="1">{
w.reader = nil
err = w.handleBase.Close()
return
}</span>
func (h *Reader) ReadByte() (b byte, err error) <span class="cov8" title="1">{
if h.reader != nil </span><span class="cov8" title="1">{
b, err = h.reader.ReadByte()
}</span> else<span class="cov0" title="0"> {
err = io.ErrClosedPipe
}</span>
<span class="cov8" title="1">return</span>
}
func (h *Reader) ReadAll() (p []byte, err error) <span class="cov8" title="1">{
if h.reader != nil </span><span class="cov8" title="1">{
p, err = io.ReadAll(h.reader)
}</span> else<span class="cov0" title="0"> {
err = io.ErrClosedPipe
}</span>
<span class="cov8" title="1">return</span>
}
func (h *Reader) ReadString(delim byte) (s string, err error) <span class="cov0" title="0">{
if h.reader != nil </span><span class="cov0" title="0">{
s, err = h.reader.ReadString(delim)
}</span> else<span class="cov0" title="0"> {
err = io.ErrClosedPipe
}</span>
<span class="cov0" title="0">return</span>
}
func (h *Reader) Reset() (err error) <span class="cov8" title="1">{
if h.fh != nil </span><span class="cov8" title="1">{
if _, err = h.fh.Seek(0, 0); err == nil </span><span class="cov8" title="1">{
h.reader.Reset(h.fh)
}</span>
}
<span class="cov8" title="1">return</span>
}
</pre>
<pre class="file" id="file2" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// writer.go
package file
import (
"bufio"
"fmt"
"os"
)
type Writer struct {
// fh *os.File
handleBase
writer *bufio.Writer
}
func NewWriter(fh *os.File) *Writer <span class="cov8" title="1">{
return &amp;Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)}
}</span>
func CreateWriter(filePath string) (w *Writer, err error) <span class="cov8" title="1">{
var fh *os.File
if fh, err = os.Create(filePath); err == nil </span><span class="cov8" title="1">{
w = NewWriter(fh)
}</span>
<span class="cov8" title="1">return</span>
}
func AppendWriter(filePath string) (w *Writer, err error) <span class="cov8" title="1">{
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil </span><span class="cov8" title="1">{
w = NewWriter(fh)
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) TypeName() string <span class="cov8" title="1">{
return "fileWriter"
}</span>
func (w *Writer) String() string <span class="cov8" title="1">{
return "writer"
}</span>
func (w *Writer) Valid() bool <span class="cov8" title="1">{
return w.handleBase.Valid() &amp;&amp; w.writer != nil
}</span>
func (w *Writer) Close() (err error) <span class="cov8" title="1">{
var err1 error
if w.writer != nil </span><span class="cov8" title="1">{
err1 = w.Flush()
w.writer = nil
}</span>
<span class="cov8" title="1">if err = w.handleBase.Close(); err == nil </span><span class="cov8" title="1">{
err = err1
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) Flush() (err error) <span class="cov8" title="1">{
if w.writer != nil </span><span class="cov8" title="1">{
err = w.writer.Flush()
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) Write(args ...any) (n int, err error) <span class="cov8" title="1">{
if w.writer != nil </span><span class="cov8" title="1">{
n, err = fmt.Fprint(w.writer, args...)
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) Writef(format string, args ...any) (n int, err error) <span class="cov8" title="1">{
if w.writer != nil </span><span class="cov8" title="1">{
n, err = fmt.Fprintf(w.writer, format, args...)
}</span>
<span class="cov8" title="1">return</span>
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
-46
View File
@@ -1,46 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// file.go
package file
import (
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
type Handle interface {
kern.Typer
GetFile() *os.File
GetName() string
Valid() bool
Close() error
}
type handleBase struct {
fh *os.File
}
func (h *handleBase) GetFile() *os.File {
return h.fh
}
func (h *handleBase) GetName() (name string) {
if h.fh != nil {
name = h.fh.Name()
}
return
}
func (h *handleBase) Valid() bool {
return h.fh != nil
}
func (h *handleBase) Close() (err error) {
if h.fh != nil {
err = h.fh.Close()
h.fh = nil
}
return
}
-83
View File
@@ -1,83 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// reader.go
package file
import (
"bufio"
"io"
"os"
)
type Reader struct {
// fh *os.File
handleBase
reader *bufio.Reader
}
func NewReader(fh *os.File) *Reader {
return &Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)}
}
func OpenReader(filePath string) (r *Reader, err error) {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
r = NewReader(fh)
}
return
}
func (h *Reader) TypeName() string {
return "fileReader"
}
func (h *Reader) String() string {
return "reader"
}
func (h *Reader) Valid() bool {
return h.handleBase.Valid() && h.reader != nil
}
func (w *Reader) Close() (err error) {
w.reader = nil
err = w.handleBase.Close()
return
}
func (h *Reader) ReadByte() (b byte, err error) {
if h.reader != nil {
b, err = h.reader.ReadByte()
} else {
err = io.ErrClosedPipe
}
return
}
func (h *Reader) ReadAll() (p []byte, err error) {
if h.reader != nil {
p, err = io.ReadAll(h.reader)
} else {
err = io.ErrClosedPipe
}
return
}
func (h *Reader) ReadString(delim byte) (s string, err error) {
if h.reader != nil {
s, err = h.reader.ReadString(delim)
} else {
err = io.ErrClosedPipe
}
return
}
func (h *Reader) Reset() (err error) {
if h.fh != nil {
if _, err = h.fh.Seek(0, 0); err == nil {
h.reader.Reset(h.fh)
}
}
return
}
-62
View File
@@ -1,62 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// reader.go
package file
import "testing"
func TestOpenReader(t *testing.T) {
r, err := OpenReader("t_reader_test.go")
if err != nil {
t.Fatalf("OpenReader failed: %v", err)
}
defer r.Close()
if !r.Valid() {
t.Fatal("Reader should be valid after opening")
}
if r.TypeName() != "fileReader" {
t.Fatalf("Expected TypeName 'fileReader', got '%s'", r.TypeName())
}
if r.String() != "reader" {
t.Fatalf("Expected String 'reader', got '%s'", r.String())
}
// GetName may return either "t_reader_test.go" or "./t_reader_test.go" depending on the environment
name := r.GetName()
if (name != "t_reader_test.go") && (name != "./t_reader_test.go") {
t.Fatalf("Expected GetName 't_reader_test.go' or './t_reader_test.go', got '%s'", name)
}
// Test reading a byte
b, err := r.ReadByte()
if err != nil {
t.Fatalf("ReadByte failed: %v", err)
}
if b == 0 {
t.Fatal("ReadByte should not return zero byte")
}
err = r.Reset()
if err != nil {
t.Fatalf("Reset failed: %v", err)
}
if s, err := r.ReadString('\n'); err != nil {
t.Fatalf("ReadString failed: %v", err)
} else {
t.Logf("ReadString: %s", s)
}
// Test reading all content
content, err := r.ReadAll()
if err != nil {
t.Fatalf("ReadAll failed: %v", err)
}
if len(content) == 0 {
t.Fatal("ReadAll should return non-empty content")
}
}
-69
View File
@@ -1,69 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// writer.go
package file
import "testing"
func TestCreateWriter(t *testing.T) {
w, err := CreateWriter("/tmp/test_writer.txt")
if err != nil {
t.Fatalf("CreateWriter failed: %v", err)
}
defer w.Close()
if !w.Valid() {
t.Fatal("Writer should be valid after creation")
}
if w.TypeName() != "fileWriter" {
t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName())
}
if w.String() != "writer" {
t.Fatalf("Expected String 'writer', got '%s'", w.String())
}
name := w.GetName()
if name != "/tmp/test_writer.txt" {
t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name)
}
if n, err := w.Write("Hello, World!\n"); err != nil {
t.Fatalf("Write failed: %v", err)
} else if n != len("Hello, World!\n") {
t.Fatalf("Expected to write %d bytes, wrote %d", len("Hello, World!\n"), n)
}
if n, err := w.Writef("This is a %s.\n", "test"); err != nil {
t.Fatalf("Writef failed: %v", err)
} else if n != len("This is a test.\n") {
t.Fatalf("Expected to write %d bytes, wrote %d", len("This is a test.\n"), n)
}
}
func TestAppendWriter(t *testing.T) {
w, err := AppendWriter("/tmp/test_writer.txt")
if err != nil {
t.Fatalf("AppendWriter failed: %v", err)
}
defer w.Close()
if !w.Valid() {
t.Fatal("Writer should be valid after opening for append")
}
if w.TypeName() != "fileWriter" {
t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName())
}
if w.String() != "writer" {
t.Fatalf("Expected String 'writer', got '%s'", w.String())
}
name := w.GetName()
if name != "/tmp/test_writer.txt" {
t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name)
}
}
-82
View File
@@ -1,82 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// writer.go
package file
import (
"bufio"
"fmt"
"os"
)
type Writer struct {
// fh *os.File
handleBase
writer *bufio.Writer
}
func NewWriter(fh *os.File) *Writer {
return &Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)}
}
func CreateWriter(filePath string) (w *Writer, err error) {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
w = NewWriter(fh)
}
return
}
func AppendWriter(filePath string) (w *Writer, err error) {
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil {
w = NewWriter(fh)
}
return
}
func (w *Writer) TypeName() string {
return "fileWriter"
}
func (w *Writer) String() string {
return "writer"
}
func (w *Writer) Valid() bool {
return w.handleBase.Valid() && w.writer != nil
}
func (w *Writer) Close() (err error) {
var err1 error
if w.writer != nil {
err1 = w.Flush()
w.writer = nil
}
if err = w.handleBase.Close(); err == nil {
err = err1
}
return
}
func (w *Writer) Flush() (err error) {
if w.writer != nil {
err = w.writer.Flush()
}
return
}
func (w *Writer) Write(args ...any) (n int, err error) {
if w.writer != nil {
n, err = fmt.Fprint(w.writer, args...)
}
return
}
func (w *Writer) Writef(format string, args ...any) (n int, err error) {
if w.writer != nil {
n, err = fmt.Fprintf(w.writer, format, args...)
}
return
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-import.go
package expr
import (
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
)
const ENV_EXPR_PATH = "EXPR_PATH"
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func includeFunc(ctx ExprContext, name string, args []any) (result any, err error) {
enable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
var dirList []string
dirList = addEnvImportDirs(dirList)
dirList = addPresetImportDirs(ctx, dirList)
result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args))
return
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
}
func addEnvImportDirs(dirList []string) []string {
if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
for _, dir := range dirList {
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("source file %q not found", filename)
}
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser(ctx)
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.eval(ctx, false)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs-math.go
package expr
import (
"fmt"
"io"
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isNumber(paramValue) || isList(paramValue)) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
}
return
}
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var sumAsFloat = false
var floatSum float64 = 0.0
var intSum int64 = 0
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
if !sumAsFloat && isFloat(v) {
sumAsFloat = true
floatSum = float64(intSum)
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil || err == io.EOF {
err = nil
if sumAsFloat {
result = floatSum
} else {
result = intSum
}
}
return
}
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
return
}
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
var mulAsFloat = false
var floatProd float64 = 1.0
var intProd int64 = 1
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
if !mulAsFloat && isFloat(v) {
mulAsFloat = true
floatProd = float64(intProd)
}
if mulAsFloat {
floatProd *= numAsFloat(v)
} else {
iv, _ := v.(int64)
intProd *= iv
}
}
if err == nil || err == io.EOF {
err = nil
if mulAsFloat {
result = floatProd
} else {
result = intProd
}
}
return
}
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doMul(ctx, name, NewFlatArrayIterator(args))
return
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
}
func init() {
registerImport("math.arith", ImportMathFuncs)
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-os.go
package expr
import (
"bufio"
"fmt"
"io"
"os"
)
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush()
}
if err == nil {
err = fh.Close()
}
}
} else {
err = fmt.Errorf("%s(): invalid file handle", name)
}
result = err == nil
return
}
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
result, err = fmt.Fprint(w.writer, args[1:]...)
}
}
}
return
}
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if r, ok := handle.(*osReader); ok {
var limit byte = '\n'
var v string
if len(args) > 1 {
if s, ok := args[1].(string); ok && len(s) > 0 {
limit = s[0]
}
}
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
if len(v) > 0 && v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
}
}
}
return
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function-register.go
package expr
import (
"path/filepath"
)
var functionRegister map[string]func(ExprContext)
func registerImport(name string, importFunc func(ExprContext)) {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
functionRegister[name] = importFunc
}
func ImportInContext(ctx ExprContext, name string) (exists bool) {
var importFunc func(ExprContext)
if importFunc, exists = functionRegister[name]; exists {
importFunc(ctx)
}
return
}
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool
for name, importFunc := range functionRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
importFunc(ctx)
}
} else {
break
}
}
return
}
func init() {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
}
-124
View File
@@ -1,124 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// global-context.go
package expr
import (
"path/filepath"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
)
//var globalCtx *SimpleStore
func ImportInContext(ctx kern.ExprContext, name string) (exists bool) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
var mod *builtinModule
if mod, exists = builtinModuleRegister[name]; exists {
mod.importFunc(globalCtx)
mod.imported = true
}
}
return
}
func ImportInContextByGlobPattern(ctx kern.ExprContext, pattern string) (count int, err error) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
var matched bool
for name, mod := range builtinModuleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
mod.importFunc(globalCtx)
mod.imported = true
}
} else {
break
}
}
}
return
}
func fixCtrlVar(name string) string {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
return name
}
func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
name = fixCtrlVar(name)
currentValue, _ = globalCtx.GetVar(name)
globalCtx.SetVar(name, newValue)
}
return currentValue
}
func GlobalCtrlGet(ctx kern.ExprContext, name string) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
name = fixCtrlVar(name)
currentValue, _ = globalCtx.GetVar(name)
}
return currentValue
}
func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
name = fixCtrlVar(name)
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, true)
return currentStatus
}
func CtrlDisable(ctx kern.ExprContext, name string) (currentStatus bool) {
name = fixCtrlVar(name)
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, false)
return currentStatus
}
// func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
// var v any
// var exists bool
// if !strings.HasPrefix(name, "_") {
// name = "_" + name
// }
// if v, exists = ctx.GetVar(name); !exists {
// v, exists = globalCtx.GetVar(name)
// }
// if exists {
// if b, ok := v.(bool); ok {
// status = b
// }
// }
// return
// }
func getControlString(ctx kern.ExprContext, name string) (s string, exists bool) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
var v any
if v, exists = globalCtx.GetVar(name); exists {
s, exists = v.(string)
}
}
return
}
func InitGlobal() (ctx kern.ExprContext) {
ctx = NewSimpleStoreWithoutGlobalContext()
kern.InitDefaultVars(ctx)
ImportBuiltinsFuncs(ctx)
return
}
+1 -5
View File
@@ -1,7 +1,3 @@
module git.portale-stac.it/go-pkg/expr
go 1.22.0
toolchain go1.23.3
require golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
go 1.21.6
-2
View File
@@ -1,2 +0,0 @@
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+2 -4
View File
@@ -1,8 +1,6 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
//go:build graph
// graph.go
package expr
@@ -55,7 +53,7 @@ func (r *Reticle) computeCharWidth() {
if v := ref.node.value(); v != nil {
ref.label = fmt.Sprintf("%v", v)
} else {
ref.label = ref.node.Source()
ref.label = ref.node.source()
}
r.colsWidth[c] = max(r.colsWidth[c], len(ref.label)+2) // +2 to make room for brakets
}
+4 -3
View File
@@ -1,9 +1,10 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
//go:build graph
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_graph_test.go
// graph_test.go
package expr
import (
+13 -43
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers.go
@@ -6,24 +6,18 @@ package expr
import (
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/util"
)
func EvalString(ctx kern.ExprContext, source string) (result any, err error) {
var tree *scan.Ast
func EvalString(ctx ExprContext, source string) (result any, err error) {
var tree *ast
r := strings.NewReader(source)
scanner := scan.NewScanner(r, scan.DefaultTranslations())
parser := NewParser()
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx)
result, err = tree.eval(ctx, true)
}
return
}
@@ -38,21 +32,18 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
}
func EvalStringV(source string, args []Arg) (result any, err error) {
ctx := NewSimpleStoreWithoutGlobalContext()
ctx := NewSimpleFuncStore()
for _, arg := range args {
if util.IsFunc(arg.Value) {
if f, ok := arg.Value.(kern.FuncTemplate); ok {
functor := kern.NewGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, 0),
})
if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok {
functor := &simpleFunctor{f: f}
ctx.RegisterFunc(arg.Name, functor, 0, -1)
} else {
err = fmt.Errorf("invalid function specification: %q", arg.Name)
}
} else if integer, ok := kern.AnyInteger(arg.Value); ok {
} else if integer, ok := anyInteger(arg.Value); ok {
ctx.SetVar(arg.Name, integer)
} else if float, ok := kern.AnyFloat(arg.Value); ok {
} else if float, ok := anyFloat(arg.Value); ok {
ctx.SetVar(arg.Name, float)
} else if _, ok := arg.Value.(string); ok {
ctx.SetVar(arg.Name, arg.Value)
@@ -68,24 +59,3 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
}
return
}
func EvalStream(ctx kern.ExprContext, r io.Reader) (result any, err error) {
var tree *scan.Ast
scanner := scan.NewScanner(r, scan.DefaultTranslations())
parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx)
}
return
}
func EvalFile(ctx kern.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
}
+8 -13
View File
@@ -1,24 +1,21 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_helpers_test.go
// helpers_test.go
package expr
import (
"fmt"
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
)
func subtract(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
func subtract(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) != 2 {
err = fmt.Errorf("%s(): requires exactly two arguments", name)
return
}
x, xok := args["a"].(int64)
y, yok := args["b"].(int64)
x, xok := args[0].(int64)
y, yok := args[1].(int64)
if xok && yok {
result = x - y
} else {
@@ -33,7 +30,7 @@ func TestEvalStringA(t *testing.T) {
args := []Arg{
{"a", uint8(1)},
{"b", int8(2)},
{"subtract", kern.FuncTemplate(subtract)},
{"subtract", FuncTemplate(subtract)},
// force coverage
{"a16", uint16(1)},
{"b16", int16(2)},
@@ -43,8 +40,6 @@ func TestEvalStringA(t *testing.T) {
{"b64", int64(2)},
{"f32", float32(1.0)},
{"f64", float64(1.0)},
{"string", "text"},
{"bool", true},
}
wantResult := int64(5)
@@ -57,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
func TestEvalString(t *testing.T) {
ctx := NewSimpleStoreWithoutGlobalContext()
ctx := NewSimpleVarStore()
ctx.SetVar("a", uint8(1))
ctx.SetVar("b", int8(2))
ctx.SetVar("f", 2.0)
@@ -73,7 +68,7 @@ func TestEvalString(t *testing.T) {
// force coverage
ctx.GetFuncInfo("dummy")
ctx.Call("dummy", map[string]any{})
ctx.Call("dummy", []any{})
source := `a + b * f`
+3
View File
@@ -0,0 +1,3 @@
First test
next commit
nr 3
-128
View File
@@ -1,128 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// import-utils.go
package expr
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
)
const (
ENV_EXPR_SOURCE_PATH = "EXPR_PATH"
ENV_EXPR_PLUGIN_PATH = "EXPR_PLUGIN_PATH"
)
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(kern.IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, kern.TypeName(paramValue))
}
return
}
// func addSourceEnvImportDirs(varName string, dirList []string) []string {
// return addEnvImportDirs(ENV_EXPR_SOURCE_PATH, dirList)
// }
// func addPluginEnvImportDirs(varName string, dirList []string) []string {
// return addEnvImportDirs(ENV_EXPR_PLUGIN_PATH, dirList)
// }
func addEnvImportDirs(envVarName string, dirList []string) []string {
if dirSpec, exists := os.LookupEnv(envVarName); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addSearchDirs(ctx kern.ExprContext, endingPath string, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, kern.ControlSearchPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
if len(endingPath) > 0 {
for _, d := range dirs {
dirList = append(dirList, path.Join(d, endingPath))
}
} else {
dirList = append(dirList, dirs...)
}
}
}
return dirList
}
func buildSearchDirList(ctx kern.ExprContext, endingPath, envVarName string) (dirList []string) {
dirList = addEnvImportDirs(envVarName, dirList)
dirList = addSearchDirs(ctx, endingPath, dirList)
return
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
suffix := SHAREDLIBRARY_EXTENSION
if strings.HasSuffix(filename, ".debug") {
suffix += ".debug"
}
for _, dir := range dirList {
if dir, err = util.ExpandPath(dir); err != nil {
continue
}
fullPath := path.Join(dir, filename)
if isFile(fullPath) {
filePath = fullPath
break
}
// subdir := strings.TrimSuffix(filename, suffix)
// if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
// filePath = fullPath
// break
// }
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if filename, err = util.ExpandPath(filename); err != nil {
return
}
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("file %q not found", filename)
}
return
}
-141
View File
@@ -1,141 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// int-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type IntIterator struct {
count int64
index int64
start int64
stop int64
step int64
}
func NewIntIteratorA(args ...any) (it *IntIterator, err error) {
return NewIntIterator(args)
}
func NewIntIterator(args []any) (it *IntIterator, err error) {
var argc int = 0
if args != nil {
argc = len(args)
}
it = &IntIterator{count: 0, index: -1, start: 0, stop: 0, step: 1}
if argc >= 1 {
if it.stop, err = kern.ToGoInt64(args[0], "start index"); err != nil {
return
}
if argc >= 2 {
it.start = it.stop
if it.stop, err = kern.ToGoInt64(args[1], "stop index"); err != nil {
return
}
if argc >= 3 {
if it.step, err = kern.ToGoInt64(args[2], "step"); err != nil {
return
}
} else if it.start > it.stop {
it.step = -1
}
}
}
if it.step == 0 {
err = fmt.Errorf("step cannot be zero")
return
}
if it.start < it.stop && it.step < 0 {
err = fmt.Errorf("step cannot be negative when start < stop")
return
}
if it.start > it.stop && it.step > 0 {
err = fmt.Errorf("step cannot be positive when start > stop")
return
}
it.Reset()
return
}
func (it *IntIterator) String() string {
return fmt.Sprintf("$(%d..%d..%d)", it.start, it.stop, it.step)
}
func (it *IntIterator) TypeName() string {
return "IntIterator"
}
func (it *IntIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *IntIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *IntIterator) Current() (item any, err error) {
if it.start <= it.stop {
if it.index >= it.start && it.index < it.stop {
item = it.index
} else {
err = io.EOF
}
} else {
if it.index > it.stop && it.index <= it.start {
item = it.index
} else {
err = io.EOF
}
}
return
}
func (it *IntIterator) Next() (item any, err error) {
it.index += it.step
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *IntIterator) Index() int64 {
return it.index
}
func (it *IntIterator) Count() int64 {
return it.count
}
func (it *IntIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *IntIterator) Clean() error {
return nil
}
View File
-55
View File
@@ -1,55 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-factory.go
package expr
import (
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
func NewFormalIterator(value any) (it kern.Iterator) {
if value == nil {
it = NewArrayIterator([]any{})
} else {
it = NewArrayIterator([]any{value})
}
return
}
func NewIterator(ctx kern.ExprContext, value any, ops []*scan.Term) (it kern.Iterator, err error) {
if value == nil {
return NewArrayIterator([]any{}), nil
}
switch v := value.(type) {
case *kern.ListType:
it = NewListIterator(v, nil)
case *kern.LinkedList:
it = NewLinkedListIterator(v, nil)
case *kern.DictType:
it, err = NewDictIterator(v, nil)
case []any:
it = NewArrayIterator(v)
case kern.Iterator:
// var exprs []*scan.Term
it, err = NewIterIter(v, ctx, ops)
default:
it = NewArrayIterator([]any{value})
}
return
}
func HasIterStandardOperations(name string) bool {
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
}
// func HasIterOperations(name string, ops ...string) bool {
// return slices.Contains([]string{
// kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName,
// }, name) ||
// slices.Contains(ops, name)
// }
-135
View File
@@ -1,135 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-iter.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
const iterIterType = "IterIter"
type IterIter struct {
it kern.Iterator
count int64
index int64
ctx kern.ExprContext
exprList []*scan.Term
current any
}
func NewIterIter(it kern.Iterator, ctx kern.ExprContext, exprs []*scan.Term) (iter kern.Iterator, err error) {
if ctx == nil {
err = fmt.Errorf("context is required for %s", iterIterType)
} else if it == nil {
err = fmt.Errorf("source iterator is required for %s", iterIterType)
} else {
iter = &IterIter{it: it, count: 0, index: -1, ctx: ctx, exprList: exprs, current: nil}
}
return
}
func (it *IterIter) String() string {
return fmt.Sprintf("$(%s)", it.it)
}
func (it *IterIter) TypeName() string {
return iterIterType
}
func (it *IterIter) HasOperation(name string) bool {
return HasIterStandardOperations(name)
}
func (it *IterIter) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *IterIter) Current() (item any, err error) {
if it.current != nil {
item = it.current
} else if len(it.exprList) > 0 {
// Evaluate the expression list and use the result as the current item
var exprValue any
for _, expr := range it.exprList {
if exprValue, err = expr.Compute(it.ctx); err != nil {
break
}
it.ctx.UnsafeSetVar(kern.ControlLastResult, exprValue)
}
if err == nil {
item = exprValue
}
} else {
var exists bool
if it.current, exists = it.ctx.GetVar("_"); !exists {
err = fmt.Errorf("current item not available")
} else {
item = it.current
}
}
return
}
func (it *IterIter) Next() (item any, err error) {
var src any
it.current = nil
ctx := it.ctx
for src, err = it.it.Next(); src == nil && err == nil; src, err = it.it.Next() {
}
if err == nil {
if src == nil {
err = io.EOF
} else {
ctx.UnsafeSetVar("_", src)
ctx.UnsafeSetVar("__", it.it.Index())
ctx.UnsafeSetVar("_#", it.it.Count())
item, err = it.Current()
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
}
}
return
}
func (it *IterIter) Index() int64 {
return it.index
}
func (it *IterIter) Count() int64 {
return it.count
}
func (it *IterIter) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *IterIter) Clean() error {
return nil
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator.go
package expr
import "io"
type Iterator interface {
Reset()
Next() (item any, err error) // must return io.EOF after the last item
Index() int
}
type FlatArrayIterator struct {
a []any
index int
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}
func (it *FlatArrayIterator) Reset() {
it.index = 0
}
func (it *FlatArrayIterator) Next() (item any, err error) {
if it.index < len(it.a) {
item = it.a[it.index]
it.index++
} else {
err = io.EOF
}
return
}
func (it *FlatArrayIterator) Index() int {
return it.index - 1
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// bind-go-function.go
package kern
// ---- Linking with Go functions
type GolangFunctor struct {
BaseFunctor
f FuncTemplate
}
func NewGolangFunctor(f FuncTemplate) *GolangFunctor {
return &GolangFunctor{f: f}
}
func (functor *GolangFunctor) TypeName() string {
return "GoFunctor"
}
func (functor *GolangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return functor.f(ctx, name, args)
}
-27
View File
@@ -1,27 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// string.go
package kern
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func ToBool(v any) (b bool, ok bool) {
ok = true
switch x := v.(type) {
case string:
b = len(x) > 0
case float64:
b = x != 0.0
case int64:
b = x != 0
case bool:
b = x
default:
ok = false
}
return
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// clone-value.go
package kern
func Clone(v any) (c any) {
if v == nil {
return
}
switch unboxed := v.(type) {
case int64:
c = unboxed
case float64:
c = unboxed
case string:
c = unboxed
case bool:
c = unboxed
case *ListType:
c = unboxed.Clone()
case *DictType:
c = unboxed.Clone()
case *LinkedList:
c = unboxed.Clone()
default:
c = v
}
return
}
-95
View File
@@ -1,95 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-errors.go
package kern
import (
"fmt"
"strings"
)
func ErrMissingParams(funcName string, missing []string) (err error) {
return fmt.Errorf("%s(): missing params -- %s", funcName, strings.Join(missing, ", "))
}
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 {
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
} else {
err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
}
return
}
func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount)
return
}
// --- General errors
func ErrCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
}
func ErrExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s(): expected %s, got %s (%#v)", funcName, kind, TypeName(value), value)
}
func ErrFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s(): division by zero", funcName)
}
// func ErrDivisionByZero() error {
// return fmt.Errorf("division by zero")
// }
// --- Parameter errors
// 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 %s (%#v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
}
func undefArticle(s string) (article string) {
if len(s) > 0 && strings.Contains("aeiou", s[0:1]) {
article = "an"
} else {
article = "a"
}
return
}
func prependUndefArticle(s string) (result string) {
return undefArticle(s) + " " + s
}
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
var artWantType, artGotType string
gotType := TypeName(paramValue)
artGotType = prependUndefArticle(gotType)
artWantType = prependUndefArticle(paramType)
return fmt.Errorf("%s(): the %q parameter must be %s, got %s (%v)", funcName, paramName, artWantType, artGotType, paramValue)
}
func ErrUnknownParam(funcName, paramName string) error {
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
}
func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
}
func ErrFuncInvalidArg(funcName, details string) error {
return fmt.Errorf("%s(): invalid argument -- %s", funcName, details)
}
// --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm Term) error {
return leftTerm.Errorf("left operand of %q must be a variable", opTerm.Source())
}
-32
View File
@@ -1,32 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-params.go
package kern
const (
ParamArgs = "args"
ParamCount = "count"
ParamItem = "item"
ParamIndex = "index"
ParamParts = "parts"
ParamSeparator = "separator"
ParamSource = "source"
ParamSuffix = "suffix"
ParamPrefix = "prefix"
ParamStart = "start"
ParamEnd = "end"
ParamValue = "value"
ParamName = "name"
ParamEllipsis = "..."
ParamFilepath = "filepath"
ParamDirpath = "dirpath"
ParamHandle = "handle"
ParamResource = "resource"
ParamIterator = "iterator"
)
// to be moved in its own source file
const (
ConstLastIndex = 0xFFFF_FFFF
)
-24
View File
@@ -1,24 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-type-names.go
package kern
const (
TypeAny = "any"
TypeNil = "nil"
TypeBoolean = "boolean"
TypeFloat = "float"
TypeFraction = "fraction"
TypeFileHandle = "file-handle"
TypeInt = "integer"
TypeItem = "item"
TypeIterator = "iterator"
TypeNumber = "number"
TypePair = "pair"
TypeString = "string"
TypeDict = "dict"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
TypeLinkedList = "linked-list"
)
-41
View File
@@ -1,41 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
package kern
import "reflect"
func Equal(value1, value2 any) (equal bool) {
if value1 == nil && value2 == nil {
equal = true
} else if value1 == nil || value2 == nil {
equal = false
} else if IsBool(value1) && IsBool(value2) {
equal = value1.(bool) == value2.(bool)
} else if IsList(value1) && IsList(value2) {
ls1 := value1.(*ListType)
ls2 := value2.(*ListType)
equal = ls1.Equals(*ls2)
} else if IsDict(value1) && IsDict(value2) {
d1 := value1.(*DictType)
d2 := value2.(*DictType)
equal = d1.Equals(*d2)
} else if IsLinkedList(value1) && IsLinkedList(value2) {
ll1 := value1.(*LinkedList)
ll2 := value2.(*LinkedList)
equal = ll1.Equals(ll2)
} else if IsInteger(value1) && IsInteger(value2) {
equal = value1.(int64) == value2.(int64)
} else if IsString(value1) && IsString(value2) {
equal = value1.(string) == value2.(string)
} else if IsFloat(value1) && IsFloat(value2) {
equal = value1.(float64) == value2.(float64)
} else if IsNumOrFract(value1) && IsNumOrFract(value2) {
if eq, err := CmpAnyFract(value1, value2); err == nil {
equal = eq == 0
}
} else if !reflect.DeepEqual(value1, value2) {
equal = false
}
return
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context-helpers.go
package kern
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.ReturnType(), info.Params())
}
func ExportObjects(destCtx, sourceCtx ExprContext) {
exportAll := CtrlIsEnabled(sourceCtx, ControlExportAll)
// 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] == '@') && !(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)
}
}
}
func exportObjectsToParent(sourceCtx ExprContext) {
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
ExportObjects(parentCtx, sourceCtx)
}
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// control.go
package kern
import "strings"
// Preset control variables
const (
ControlPreset = "_preset"
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
ControlStdout = "_stdout"
)
// Other control variables
const (
ControlExportAll = "_export_all"
)
// Initial values
const (
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
)
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
var v any
var exists bool
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists = ctx.GetVar(name); !exists {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
v, exists = globalCtx.GetVar(name)
}
}
if exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
current, _ = ctx.GetVar(name)
ctx.UnsafeSetVar(name, value)
return
}
func InitDefaultVars(ctx ExprContext) {
if _, exists := ctx.GetVar(ControlPreset); exists {
return
}
ctx.UnsafeSetVar(ControlPreset, true)
ctx.UnsafeSetVar(ControlBoolShortcut, true)
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
}
-199
View File
@@ -1,199 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-type.go
package kern
import (
"fmt"
"reflect"
"strings"
)
const DictTypeName = "dict"
type DictType map[any]any
func IsDict(v any) (ok bool) {
_, ok = v.(*DictType)
return ok
}
func MakeDict() (dict *DictType) {
d := make(DictType)
dict = &d
return
}
func NewDict(dictAny map[any]any) (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 newDict(dictAny map[any]Term) (dict *DictType) {
// TODO Change with a call to NewDict()
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, opt FmtOpt) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
//sb.WriteString(strings.Repeat(" ", indent))
sb.WriteByte('{')
if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
sb.WriteByte('\n')
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(nest)
if key, ok := name.(string); ok {
sb.WriteByte('"')
sb.WriteString(key)
sb.WriteByte('"')
} else {
fmt.Fprintf(sb, "%v", name)
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(innerOpt))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
fmt.Fprintf(sb, "%v", value)
}
}
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
sb.WriteString("}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
flags := GetFormatFlags(opt)
if flags&MultiLine != 0 {
dict.toMultiLine(&sb, opt)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
fmt.Fprintf(&sb, "%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 {
fmt.Fprintf(&sb, "%#v", value)
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return DictTypeName
}
func (dict *DictType) HasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) SetItem(key any, value any) {
(*dict)[key] = value
}
func (dict *DictType) GetItem(key any) (value any, exists bool) {
value, exists = (*dict)[key]
return
}
func (dict *DictType) Clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = Clone(v)
}
return
}
func (dict *DictType) Merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
func (dict *DictType) Equals(dict2 DictType) (answer bool) {
if dict2 != nil && len(*dict) == len(dict2) {
answer = true
for key, value1 := range *dict {
if value2, exists := dict2.GetItem(key); exists {
if !Equal(value1, value2) {
answer = false
break
}
} else {
answer = false
break
}
}
}
return
}
////////////////
type DictFormat interface {
ToDict() *DictType
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr-context.go
package kern
// ----Expression Context
type ExprContext interface {
Clone() ExprContext
SetParent(ctx ExprContext)
GetParent() (ctx ExprContext)
GetGlobal() (ctx ExprContext)
GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any)
UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
VarCount() int
DeleteVar(varName string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
FuncCount() int
DeleteFunc(funcName string)
GetLocalFuncInfo(name string) (info ExprFunc, exists bool)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args map[string]any) (result any, err error)
RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error)
ToDict() (dict *DictType)
ToString(opt FmtOpt) string
}
func ContextToDict(ctx ExprContext) (dict *DictType) {
var keys []string
// Variables
keys = ctx.EnumVars(nil)
vars := MakeDict()
for _, key := range keys {
value, _ := ctx.GetVar(key)
vars.SetItem(key, value)
}
// Functions
keys = ctx.EnumFuncs(func(name string) bool { return true })
funcs := MakeDict()
for _, key := range keys {
funcInfo, _ := ctx.GetFuncInfo(key)
funcs.SetItem(key, funcInfo)
}
dict = MakeDict()
dict.SetItem("vars", vars)
dict.SetItem("funcs", funcs)
return
}
func ContextToString(ctx ExprContext, opt FmtOpt) string {
dict := ctx.ToDict()
return dict.ToString(opt)
}
-44
View File
@@ -1,44 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr-function.go
package kern
// ---- Functor interface
type Functor interface {
Typer
InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
GetParams() []ExprFuncParam
GetDefinitionContext() ExprContext
}
// ---- Function Param Info
type ExprFuncParam interface {
Name() string
Type() string
IsDefault() bool
IsOptional() bool
IsRepeat() bool
DefaultValue() any
}
// ---- Function Info
type ExprFunc interface {
Formatter
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
Params() []ExprFuncParam
ParamSpec(paramName string) ExprFuncParam
ReturnType() string
PrepareCall(name string, actualParams map[string]any) (err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext)
}
func IsFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
-12
View File
@@ -1,12 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr.go
package kern
// ----Expression interface
type Expr interface {
Typer
Eval(ctx ExprContext) (result any, err error)
String() string
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// float.go
package kern
func IsFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func AnyFloat(v any) (float float64, ok bool) {
ok = true
switch floatval := v.(type) {
case float32:
float = float64(floatval)
case float64:
float = floatval
default:
ok = false
}
return
}
-94
View File
@@ -1,94 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// formatter.go
package kern
import (
"fmt"
"strings"
)
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
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
}
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
return FmtOpt(indent<<16) | flags
}
func GetFormatFlags(opt FmtOpt) FmtOpt {
return opt & 0xFFFF
}
func GetFormatIndent(opt FmtOpt) int {
return int(opt >> 16)
}
type Formatter interface {
ToString(options FmtOpt) string
}
func Format(sb *strings.Builder, item any, opt FmtOpt) {
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else {
fmt.Fprintf(sb, "%v", item)
}
}
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 TypeName(v any) (name string) {
if v == nil {
name = "nil"
} else 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
}
-385
View File
@@ -1,385 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// fraction-type.go
package kern
//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:])
return MakeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
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
}
switch s[0] {
case '-':
sign = int64(-1)
s = s[1:]
case '+':
s = s[1:]
}
// if strings.HasSuffix(s, "()") {
// s = s[0 : len(s)-2]
// }
s = strings.TrimSuffix(s, "()")
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) N() int64 {
return f.num
}
func (f *FractionType) D() int64 {
return f.den
}
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 sign, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
sign = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sNum := fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, sign+num))
sb.WriteString(sNum)
} else {
if len(sign) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(sign) > 0 {
sb.WriteString(sign)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(sign) > 0 {
sb.WriteString(" ")
}
}
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
sb.WriteString(sDen)
}
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)
} else if dec, ok := v.(float64); ok {
f, err = Float64ToFraction(dec)
} else {
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) {
if f1 != nil && f2 != nil {
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
}
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
}
// 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
}
-164
View File
@@ -1,164 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-info.go
package kern
import (
"fmt"
"strings"
)
// --- Functions
// FuncInfo implements expr.ExprFunc
type FuncInfo struct {
name string
minArgs int
maxArgs int
functor Functor
formalParams []ExprFuncParam
returnType string
}
func NewFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *FuncInfo, err error) {
var minArgs = 0
var maxArgs = 0
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.IsDefault() || 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() {
minArgs--
maxArgs = -1
}
}
info = &FuncInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
}
functor.SetFunc(info)
return info, nil
}
func (info *FuncInfo) Params() []ExprFuncParam {
return info.formalParams
}
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.formalParams != nil {
for i, p := range info.formalParams {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.Name())
if p.IsDefault() {
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("{}")
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 (info *FuncInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
ctx = defCtx.Clone()
ctx.SetParent(defCtx)
} else {
ctx = parentCtx.Clone()
ctx.SetParent(parentCtx)
}
return
}
func (info *FuncInfo) ParamSpec(paramName string) ExprFuncParam {
for _, spec := range info.formalParams {
if spec.Name() == paramName {
return spec
}
}
return nil
}
func (info *FuncInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
passedCount := len(actualParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
return
}
if passedCount < len(info.formalParams) {
for _, p := range info.formalParams {
if _, exists := actualParams[p.Name()]; !exists {
if !p.IsDefault() {
break
}
if p.IsRepeat() {
varArgs := make([]any, 1)
varArgs[0] = p.DefaultValue()
actualParams[p.Name()] = varArgs
} else {
actualParams[p.Name()] = p.DefaultValue()
}
}
}
}
if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
}
return
}
-273
View File
@@ -1,273 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package kern
import (
"fmt"
"strconv"
)
// ---- Function templates
type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (result any, err error)
type DeepFuncTemplate func(a, b any) (eq bool, 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(){}"
}
return s
}
func (functor *BaseFunctor) GetParams() (params []ExprFuncParam) {
if functor.info != nil {
return functor.info.Params()
} else {
return []ExprFuncParam{}
}
}
func (functor *BaseFunctor) SetFunc(info ExprFunc) {
functor.info = info
}
func (functor *BaseFunctor) GetFunc() ExprFunc {
return functor.info
}
func (functor *BaseFunctor) GetDefinitionContext() ExprContext {
return nil
}
// ---- Function Parameters
type FuncParamFlags uint16
const (
PfDefault FuncParamFlags = 1 << iota
PfOptional
PfRepeat
)
type funcParamInfo struct {
name string
flags FuncParamFlags
defaultValue any
}
func NewFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name}
}
func NewFuncParamFlag(name string, flags FuncParamFlags) ExprFuncParam {
return &funcParamInfo{name: name, flags: flags}
}
func NewFuncParamFlagDef(name string, flags FuncParamFlags, 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 TypeAny
}
func (param *funcParamInfo) IsDefault() bool {
return (param.flags & PfDefault) != 0
}
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
}
func initActualParams(ctx ExprContext, info ExprFunc, callTerm Term) (actualParams map[string]any, err error) {
var varArgs []any
var varName string
namedParamsStarted := false
formalParams := info.Params()
actualParams = make(map[string]any, len(formalParams))
if callTerm == nil {
return
}
childCount := callTerm.GetChildCount()
for i := range childCount {
tree := callTerm.GetChild(i)
// for i, tree := range callTerm.Children() {
var paramValue any
paramCtx := ctx.Clone()
if paramValue, err = tree.Compute(paramCtx); err != nil {
break
}
if paramName, namedParam := GetAssignVarName(tree); namedParam {
if info.ParamSpec(paramName) == nil {
err = fmt.Errorf("%s(): unknown param %q", info.Name(), paramName)
break
}
actualParams[paramName] = paramValue
namedParamsStarted = true
} else if !namedParamsStarted {
if varArgs != nil {
varArgs = append(varArgs, paramValue)
} else if i < len(formalParams) {
spec := formalParams[i]
if spec.IsRepeat() {
varArgs = make([]any, 0, childCount-i)
varArgs = append(varArgs, paramValue)
varName = spec.Name()
} else {
actualParams[spec.Name()] = paramValue
}
} else {
err = ErrTooManyParams(info.Name(), len(formalParams), childCount)
break
}
} else {
err = fmt.Errorf("%s(): positional param nr %d not allowed after named params", info.Name(), i+1)
break
}
}
if err == nil {
if varArgs != nil {
actualParams[varName] = varArgs
}
}
return
}
// func (info *funcInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
// passedCount := len(actualParams)
// if info.MinArgs() > passedCount {
// err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
// return
// }
// if passedCount < len(info.formalParams) {
// for _, p := range info.formalParams {
// if _, exists := actualParams[p.Name()]; !exists {
// if !p.IsDefault() {
// break
// }
// if p.IsRepeat() {
// varArgs := make([]any, 1)
// varArgs[0] = p.DefaultValue()
// actualParams[p.Name()] = varArgs
// } else {
// actualParams[p.Name()] = p.DefaultValue()
// }
// }
// }
// }
// if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
// err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
// }
// return
// }
// ----- Call a function ---
// func getAssignVarName(t *term) (name string, ok bool) {
// if ok = t.symbol() == SymEqual; ok {
// name = t.children[0].source()
// }
// return
// }
func GetAssignVarName(t Term) (name string, ok bool) {
if ok = t.IsAssign(); ok {
name = t.GetChildSource(0)
}
return
}
func CallFunctionByTerm(parentCtx ExprContext, name string, callTerm Term) (result any, err error) {
var actualParams map[string]any
if info, exists := parentCtx.GetFuncInfo(name); exists {
if actualParams, err = initActualParams(parentCtx, info, callTerm); err == nil {
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
functor := info.Functor()
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunctionByArgs(parentCtx ExprContext, name string, args []any) (result any, err error) {
var actualParams map[string]any
if info, exists := parentCtx.GetFuncInfo(name); exists {
functor := info.Functor()
actualParams = BindActualParams(functor, args)
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunctionByParams(parentCtx ExprContext, name string, actualParams map[string]any) (result any, err error) {
//var actualParams map[string]any
if info, exists := parentCtx.GetFuncInfo(name); exists {
functor := info.Functor()
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func GetParam(args map[string]any, paramName string, paramNum int) (value any, exists bool) {
if value, exists = args[paramName]; !exists {
if paramNum > 0 && paramNum <= len(args) {
value, exists = args["arg"+strconv.Itoa(paramNum)]
}
}
return
}
func BindActualParams(functor Functor, args []any) (actualParams map[string]any) {
formalParams := functor.GetParams()
actualParams = make(map[string]any, len(args))
for i, arg := range args {
if i < len(formalParams) {
actualParams[formalParams[i].Name()] = arg
} else {
actualParams["arg"+strconv.Itoa(i+1)] = arg
}
}
return
}
-95
View File
@@ -1,95 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator.go
package kern
import (
// "errors"
"fmt"
"slices"
)
// Operator names
const (
InitName = "init"
CleanName = "clean"
ResetName = "reset"
NextName = "next"
CurrentName = "current"
IndexName = "index"
CountName = "count"
FilterName = "filter"
MapName = "map"
KeyName = "key"
ValueName = "value"
)
type Iterator interface {
Typer
fmt.Stringer
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int64
Count() int64
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
type ExtIterator interface {
Iterator
Reset() error
Clean() error
}
func ErrNoOperation(name string) error {
return fmt.Errorf("no %s() function defined in the data-source", name)
}
func IsIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
type IteratorBase struct {
ItemIndex int64
ItemCount int64
current any
}
func (it *IteratorBase) Current() (item any, err error) {
return it.current, nil
}
func (it *IteratorBase) Index() int64 {
return it.ItemIndex
}
func (it *IteratorBase) Count() int64 {
return it.ItemCount
}
func (it *IteratorBase) HasOperation(name string) bool {
return slices.Contains([]string{NextName, IndexName, CountName, CurrentName}, name)
}
func (it *IteratorBase) Clean() (err error) {
return
}
func (it *IteratorBase) Reset() (err error) {
it.ItemIndex = -1
it.ItemCount = 0
it.current = nil
return
}
func (it *IteratorBase) Increment() {
it.ItemIndex++
it.ItemCount++
}
func (it *IteratorBase) SetCurrent(v any) {
it.current = v
}
-130
View File
@@ -1,130 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// linked-list-type.go
package kern
import (
"strings"
)
const LinkedListTypeName = "lisked-list"
const MaxUint64Allowed = uint64(9_223_372_036_854_775_807)
func IsLinkedList(v any) (ok bool) {
_, ok = v.(*LinkedList)
return ok
}
func NewLinkedListA(listAny ...any) (list *LinkedList) {
if listAny == nil {
listAny = []any{}
}
list = NewLinkedList()
for _, item := range listAny {
list.PushBack(FixAnyNumber(item))
}
return
}
func LinkedListFromStrings(stringList []string) (list *LinkedList) {
list = NewLinkedList()
for _, s := range stringList {
list.PushBack(s)
}
return
}
func (ls *LinkedList) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteString("[<")
if ls.Len() > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
}
i := 0
for item := ls.FirstNode(); item != nil; item = item.Next() {
if i > 0 {
if flags&MultiLine != 0 {
sb.WriteString(",\n")
sb.WriteString(nest)
} else {
sb.WriteString(", ")
}
}
// data := item.Data()
// if s, ok := data.(string); ok {
// sb.WriteByte('"')
// sb.WriteString(s)
// sb.WriteByte('"')
// } else if formatter, ok := data.(Formatter); ok {
// sb.WriteString(formatter.ToString(innerOpt))
// } else {
// fmt.Fprintf(&sb, "%v", data)
// }
Format(&sb, item.Data(), innerOpt)
i++
}
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
}
sb.WriteString(">]")
s = sb.String()
if flags&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
return
}
func (ls *LinkedList) String() string {
return ls.ToString(0)
}
func (ls *LinkedList) TypeName() string {
return LinkedListTypeName
}
// func (ls *LinkedList) 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 (ls1 *LinkedList) Equals(ls2 *LinkedList) (answer bool) {
if ls2 != nil && ls1.Len() == ls2.Len() {
answer = true
i2 := ls2.FirstNode()
for i1 := ls1.FirstNode(); i1 != nil; i1 = i1.Next() {
if !Equal(i1.Data(), i2.Data()) {
answer = false
break
}
i2 = i2.Next()
}
}
return
}
func (ls1 *LinkedList) Clone() (ls2 *LinkedList) {
ls2 = NewLinkedListA()
for i1 := ls1.FirstNode(); i1 != nil; i1 = i1.Next() {
ls2.PushBack(Clone(i1.Data()))
}
return
}
-284
View File
@@ -1,284 +0,0 @@
// simple-list project slist.go
package kern
import (
"fmt"
)
type ListNode struct {
data any
next *ListNode
}
func (node *ListNode) Next() *ListNode {
return node.next
}
func (self *ListNode) Data() any {
return self.data
}
type LinkedList struct {
count int
first *ListNode
last *ListNode
}
func NewLinkedList() (list *LinkedList) {
list = &LinkedList{0, nil, nil}
return
}
func (ls *LinkedList) Len() int {
return ls.count
}
func (ls *LinkedList) Empty() bool {
return ls.count == 0
}
func (ls *LinkedList) PushFront(data any) *ListNode {
ls.first = &ListNode{data, ls.first}
if ls.last == nil {
ls.last = ls.first
}
ls.count++
return ls.first
}
func (ls *LinkedList) SeqPushFront(data any) (result *LinkedList) {
switch seq := data.(type) {
case Iterator:
result = ls.IterPushFront(seq)
default:
ls.PushFront(data)
result = ls
}
return
}
func (ls *LinkedList) IterPushFront(it Iterator) *LinkedList {
for item, err1 := it.Next(); err1 == nil; item, err1 = it.Next() {
ls.PushFront(item)
}
return ls
}
func (ls *LinkedList) PushBack(data any) (node *ListNode) {
node = &ListNode{data, nil}
if ls.last != nil {
ls.last.next = node
}
ls.last = node
if ls.first == nil {
ls.first = node
}
ls.count++
return
}
func (ls *LinkedList) SeqPushBack(data any) (result *LinkedList) {
switch seq := data.(type) {
case Iterator:
result = ls.IterPushBack(seq)
default:
ls.PushBack(data)
result = ls
}
return
}
func (ls *LinkedList) IterPushBack(it Iterator) *LinkedList {
for item, err1 := it.Next(); err1 == nil; item, err1 = it.Next() {
ls.PushBack(item)
}
return ls
}
func (ls *LinkedList) FirstNode() *ListNode {
return ls.first
}
func (ls *LinkedList) First() (data any, err error) {
if ls.first != nil {
data = ls.first.data
} else {
err = errorListEmpty()
}
return
}
func (ls *LinkedList) LastNode() *ListNode {
return ls.last
}
func (ls *LinkedList) Last() (data interface{}, err error) {
if ls.last != nil {
data = ls.last.data
} else {
err = errorListEmpty()
}
return
}
func (ls *LinkedList) NodeAt(index int) (node *ListNode, err error) {
if ls.count == 0 {
err = errorListEmpty()
} else if index > ls.count {
err = errorOutOfRange()
} else if index == ls.count-1 {
node = ls.last
} else {
current := ls.first
for range index {
current = current.next
}
node = current
}
return
}
func (ls *LinkedList) At(index int) (data interface{}, err error) {
node, err := ls.NodeAt(index)
if err == nil {
data = node.data
}
return
}
func (ls *LinkedList) Insert(index int, data any) *ListNode {
var prev *ListNode
prev = nil
current := ls.first
for pos := 0; current != nil && pos < index; pos++ {
prev = current
current = current.next
}
node := &ListNode{data, current}
if prev != nil {
prev.next = node
}
if ls.first == current {
ls.first = node
}
if node.next == nil {
ls.last = node
}
ls.count++
return node
}
func (ls *LinkedList) PushBackStringArray(items []string) {
for _, v := range items {
ls.PushBack(v)
}
}
func (ls *LinkedList) Sub(start, end int) (subList *LinkedList) {
subList = NewLinkedList()
if node, err := ls.NodeAt(start); err == nil {
for i := start; i < end && node != nil; i++ {
subList.PushBack(node.data)
node = node.next
}
}
return
}
// type TraverseOperator func(index int, elem interface{}, userData interface{}) (err error)
// func (self *LinkedList) Traverse(op TraverseOperator, user_data interface{}) (err error) {
// index := int(0)
// for current := self.first; current != nil; current = current.next {
// err = op(index, current.data, user_data)
// if err != nil {
// break
// }
// index++
// }
// return
// }
// func (self *LinkedList) Traverse2(observer Observer, abortOnError bool) (err error) {
// index := int(0)
// for current := self.first; current != nil; current = current.next {
// err = observer.Observe(current, index)
// if err != nil && abortOnError {
// break
// }
// index++
// }
// return
// }
type EqualFunc func(current, target any) bool
func (ls *LinkedList) FindFirst(eqFunc EqualFunc, targetData any) (targetNode *ListNode) {
for current := ls.first; current != nil && targetNode == nil; current = current.next {
if eqFunc(current.data, targetData) {
targetNode = current
}
}
return
}
func (ls *LinkedList) FindNext(eqFunc EqualFunc, startNode *ListNode) (targetNode *ListNode) {
if startNode != nil {
for current := startNode.next; current != nil && targetNode == nil; current = current.next {
if eqFunc(current.data, startNode.Data()) {
targetNode = current
}
}
}
return
}
// type DataFeeder func(user_data interface{}) interface{}
// type NodeObserver func(node *ListNode, index int, userData interface{})
// func (self *LinkedList) FeedTail(feeder DataFeeder, feederUserData interface{}, observer NodeObserver, observerUserData interface{}) (count int) {
// count = 0
// for item := feeder(feederUserData); item != nil; item = feeder(feederUserData) {
// // fmt.Println("Item", count, item)
// node := self.PushBack(item)
// if observer != nil {
// observer(node, count, observerUserData)
// }
// count++
// }
// return
// }
// func (self *LinkedList) FeedTail2(feeder Feeder, observer Observer, abortOnError bool) (count int, err error) {
// count = 0
// // item := feeder.Next()
// for item, err1 := feeder.Next(); item != nil; item, err1 = feeder.Next() {
// if err1 != nil {
// if err == nil {
// err = err1
// }
// if abortOnError {
// break
// }
// }
// // fmt.Println("Item", count, item)
// node := self.PushBack(item)
// if observer != nil {
// observer.Observe(node, count)
// }
// count++
// }
// return
// }
func errorListEmpty() error {
return fmt.Errorf("List is empty")
}
func errorOutOfRange() error {
return fmt.Errorf("Out of range")
}
-203
View File
@@ -1,203 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-type.go
package kern
import (
"fmt"
"reflect"
"strings"
)
type ListType []any
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
}
func NewListA(listAny ...any) (list *ListType) {
if listAny == nil {
listAny = []any{}
}
return NewList(listAny)
}
func NewList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
copy(ls, listAny)
list = &ls
}
return
}
func MakeList(length, capacity int) (list *ListType) {
if capacity < length {
capacity = length
}
ls := make(ListType, length, capacity)
list = &ls
return
}
func ListFromStrings(stringList []string) (list *ListType) {
list = MakeList(len(stringList), 0)
for i, s := range stringList {
(*list)[i] = s
}
return
}
func (ls *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
}
for i, item := range []any(*ls) {
if i > 0 {
if flags&MultiLine != 0 {
sb.WriteString(",\n")
sb.WriteString(nest)
} else {
sb.WriteString(", ")
}
}
Format(&sb, item, innerOpt)
}
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
}
sb.WriteByte(']')
s = sb.String()
if flags&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 (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 (ls *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls) == len(ls2) {
answer = true
for index, i1 := range *ls {
// if !reflect.DeepEqual(i1, ls2[index]) {
// answer = false
// break
// }
if !Equal(i1, ls2[index]) {
answer = false
break
}
}
}
return
}
func (ls1 *ListType) Clone() (ls2 *ListType) {
ls := make(ListType, len(*ls1))
for i, item := range *ls1 {
ls[i] = Clone(item)
}
ls2 = &ls
return
}
func (ls *ListType) IndexDeepSameCmp(target any) (index int) {
var eq bool
var err error
index = -1
for i, item := range *ls {
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
}
func (ls *ListType) SetItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*ls)) {
(*ls)[index] = value
} else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*ls)-1)
}
return
}
func (ls *ListType) AppendItem(value any) {
*ls = append(*ls, value)
}
-120
View File
@@ -1,120 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// number.go
package kern
import (
"fmt"
)
func IsInteger(v any) (ok bool) {
_, 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) {
return IsString(v) || IsNumber(v)
}
func NumAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
if fract, ok := v.(*FractionType); ok {
f = fract.ToFloat()
} else {
i, _ := v.(int64)
f = float64(i)
}
}
return
}
func AnyInteger(v any) (i int64, ok bool) {
ok = true
switch intval := v.(type) {
case int:
i = int64(intval)
case uint8:
i = int64(intval)
case uint16:
i = int64(intval)
case uint64:
i = int64(intval)
case uint32:
i = int64(intval)
case int8:
i = int64(intval)
case int16:
i = int64(intval)
case int32:
i = int64(intval)
case int64:
i = intval
default:
ok = false
}
return
}
func FixAnyNumber(v any) (fixed any) {
switch unboxed := v.(type) {
case int:
fixed = int64(unboxed)
case int8:
fixed = int64(unboxed)
case int16:
fixed = int64(unboxed)
case int32:
fixed = int64(unboxed)
case uint:
fixed = int64(unboxed)
case uint8:
fixed = int64(unboxed)
case uint16:
fixed = int64(unboxed)
case uint32:
fixed = int64(unboxed)
case uint64:
if unboxed <= MaxUint64Allowed {
fixed = int64(unboxed)
} else {
fixed = float64(unboxed)
}
case float32:
fixed = float64(unboxed)
default:
fixed = v
}
return
}
func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}
func ToGoInt64(value any, description string) (i int64, err error) {
if valueInt64, ok := value.(int64); ok {
i = valueInt64
} else if valueInt, ok := value.(int); ok {
i = int64(valueInt)
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// string.go
package kern
import (
"fmt"
)
func IsString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
func ToGoString(value any, description string) (s string, err error) {
if s, ok := value.(string); ok {
return s, nil
} else {
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
}
return
}
-21
View File
@@ -1,21 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term.go
package kern
import (
"fmt"
)
type Term interface {
fmt.Stringer
// Children() []Term
Source() string
GetChildCount() (count int)
GetChild(index int) Term
GetChildSource(index int) string
Compute(ctx ExprContext) (result any, err error)
IsAssign() bool
Errorf(template string, args ...any) (err error)
}
-9
View File
@@ -1,9 +0,0 @@
//go:build darwin
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-darwin.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dylib"
-9
View File
@@ -1,9 +0,0 @@
//go:build linux
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-linux.go
package expr
const SHAREDLIBRARY_EXTENSION = ".so"
-9
View File
@@ -1,9 +0,0 @@
//go:build windows
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-windows.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dll"
-102
View File
@@ -1,102 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type LinkedListIterator struct {
a *kern.LinkedList
count int64
index int64
current *kern.ListNode
}
func NewLinkedListIterator(list *kern.LinkedList, args []any) (it *LinkedListIterator) {
it = &LinkedListIterator{a: list, count: 0, index: -1, current: list.FirstNode()}
return
}
func (it *LinkedListIterator) String() string {
var l = int64(0)
if it.a != nil {
l = int64(it.a.Len())
}
return fmt.Sprintf("$([<#%d>])", l)
}
func (it *LinkedListIterator) TypeName() string {
return "LinkedListIterator"
}
func (it *LinkedListIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *LinkedListIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *LinkedListIterator) Current() (item any, err error) {
if it.current != nil {
item = it.current.Data()
} else {
err = io.EOF
}
return
}
func (it *LinkedListIterator) Next() (item any, err error) {
if it.current != nil {
item = it.current.Data()
it.current = it.current.Next()
it.index++
it.count++
} else {
err = io.EOF
}
return
}
func (it *LinkedListIterator) Index() int64 {
return it.index
}
func (it *LinkedListIterator) Count() int64 {
return it.count
}
func (it *LinkedListIterator) Reset() error {
it.current = it.a.FirstNode()
it.index = -1
it.count = 0
return nil
}
func (it *LinkedListIterator) Clean() error {
return nil
}
-149
View File
@@ -1,149 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type ListIterator struct {
a *kern.ListType
count int64
index int64
start int64
stop int64
step int64
}
func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
var argc int = 0
listLen := int64(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 := kern.ToGoInt64(args[0], "start index"); err == nil {
if i < 0 {
i = listLen + i
}
it.start = i
}
if argc >= 2 {
if i, err := kern.ToGoInt64(args[1], "stop index"); err == nil {
if i < 0 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := kern.ToGoInt64(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: (*kern.ListType)(&array), count: 0, index: -1, start: 0, stop: int64(len(array)) - 1, step: 1}
return
}
func (it *ListIterator) String() string {
var l = int64(0)
if it.a != nil {
l = int64(len(*it.a))
}
return fmt.Sprintf("$([#%d])", l)
}
func (it *ListIterator) TypeName() string {
return "ListIterator"
}
func (it *ListIterator) HasOperation(name string) bool {
//yes := name == expr.NextName || name == expr.ResetName || name == expr.IndexName || name == expr.CountName || name == expr.CurrentName
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *ListIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *ListIterator) Current() (item any, err error) {
a := *(it.a)
if it.start <= it.stop {
if it.stop < int64(len(a)) && it.index >= it.start && it.index <= it.stop {
item = a[it.index]
} else {
err = io.EOF
}
} else {
if it.start < int64(len(a)) && it.index >= it.stop && it.index <= it.start {
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() int64 {
return it.index
}
func (it *ListIterator) Count() int64 {
return it.count
}
func (it *ListIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *ListIterator) Clean() error {
return nil
}
+73
View File
@@ -0,0 +1,73 @@
// 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)
}
-46
View File
@@ -1,46 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-dict.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- dict term
func newDictTerm(args map[any]*scan.Term) *scan.Term {
return &scan.Term{
Tk: *scan.NewValueToken(0, 0, scan.SymDict, "{}", args),
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalDict,
}
}
// -------- dict func
func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
dict, _ := opTerm.Value().(map[any]*scan.Term)
items := make(kern.DictType, len(dict))
for key, tree := range dict {
var param any
if param, err = tree.Compute(ctx); err != nil {
break
}
var keyValue any
if keyValue, err = (key.(*scan.Term)).Compute(ctx); err == nil {
if kern.IsInteger(keyValue) || kern.IsString(keyValue) {
items[keyValue] = param
} else {
err = key.(*scan.Term).Errorf("dict key can be integer or string, got %s", kern.TypeName(keyValue))
}
}
}
if err == nil {
v = &items
}
return
}
+19 -20
View File
@@ -1,35 +1,34 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-expr.go
package expr
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
import "fmt"
// -------- expr term
func newExprTerm(root *scan.Term) *scan.Term {
tk := scan.NewValueToken(root.Tk.Row(), root.Tk.Col(), scan.SymExpression, root.Source(), root)
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalExpr,
func newExprTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalExpr,
}
}
// -------- eval expr
func evalExpr(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if ast, ok := opTerm.Value().(*scan.Term); ok {
v, err = ast.Compute(ctx)
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
if expr, ok := self.value().(Expr); ok {
v, err = expr.eval(ctx, false)
} else {
err = fmt.Errorf("expression expected, got %T", opTerm.Value())
err = fmt.Errorf("expression expected, got %T", self.value())
}
return
}
// init
// func init() {
// registerTermConstructor(SymExpression, newExprTerm)
// }
+98 -59
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-func.go
@@ -6,81 +6,120 @@ package expr
import (
"errors"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- function call term
func newFuncCallTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
var pos scan.TermPosition = scan.PosLeaf
if len(args) > 0 {
pos = scan.PosMultifix
}
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: args,
Position: pos,
Priority: scan.PriValue,
EvalFunc: evalFuncCall,
func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil,
children: args,
position: posLeaf,
priority: priValue,
evalFunc: evalFuncCall,
}
}
// -------- eval func call
// func _evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
// name, _ := opTerm.Tk.Value.(string)
// params := make([]any, len(opTerm.Children), len(opTerm.Children)+5)
// for i, tree := range opTerm.Children {
// var param any
// if param, err = tree.Compute(ctx); err != nil {
// break
// }
// params[i] = param
// }
// if err == nil {
// v, err = CallFunction(ctx, name, params)
// }
// return
// }
func evalFuncCall(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
name, _ := opTerm.Tk.Value.(string)
v, err = kern.CallFunctionByTerm(ctx, name, opTerm)
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := parentCtx.Clone()
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children))
for i, tree := range self.children {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
params[i] = param
}
if err == nil {
if v, err = ctx.Call(name, params); err == nil {
exportAll := isEnabled(ctx, control_export_all)
// Export variables
for _, refName := range ctx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
refValue, _ := ctx.GetVar(refName)
exportVar(parentCtx, refName, refValue)
}
// Export functions
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info := ctx.GetFuncInfo(refName); info != nil {
exportFunc(parentCtx, refName, info)
}
}
}
}
return
}
func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
// -------- function definition term
func newFuncDefTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
return &scan.Term{
Tk: *tk, // value is the expression body
Parent: nil,
Children: args, // function params
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalFuncDef,
func newFuncDefTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
parent: nil,
children: args, // arg[0]=formal-param-list, arg[1]=*ast
position: posLeaf,
priority: priValue,
evalFunc: evalFuncDef,
}
}
// -------- eval func def
func evalFuncDef(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
bodySpec := opTerm.Value()
if ast, ok := bodySpec.(*scan.Ast); ok {
paramList := make([]kern.ExprFuncParam, 0, len(opTerm.Children))
for _, param := range opTerm.Children {
var defValue any
flags := kern.FuncParamFlags(0)
if len(param.Children) > 0 {
flags |= kern.PfDefault
if defValue, err = param.Children[0].Compute(ctx); err != nil {
return
}
// TODO
type funcDefFunctor struct {
params []string
expr Expr
}
func (functor *funcDefFunctor) 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 functor, ok := arg.(Functor); ok {
ctx.RegisterFunc(p, functor, 0, -1)
} else {
ctx.setVar(p, arg)
}
info := kern.NewFuncParamFlagDef(param.Source(), flags, defValue)
paramList = append(paramList, info)
} else {
ctx.setVar(p, nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
}
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value()
if expr, ok := bodySpec.(*ast); ok {
paramList := make([]string, 0, len(self.children))
for _, param := range self.children {
paramList = append(paramList, param.source())
// if paramName, ok := param.value().(string); ok {
// paramList = append(paramList, paramName)
// } else {
// err = fmt.Errorf("invalid function definition: formal param nr %d must be an identifier", i+1)
// break
// }
}
v = &funcDefFunctor{
params: paramList,
expr: expr,
}
v = newExprFunctor(ast, paramList, ctx)
} else {
err = errors.New("invalid function definition: the body specification must be an expression")
}
-208
View File
@@ -1,208 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-iterator.go
package expr
import (
"slices"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- iterator term
func newIteratorTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
tk.Sym = scan.SymIterator
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: args,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalIterator,
}
}
// -------- eval iterator
func evalTermArray(ctx kern.ExprContext, terms []*scan.Term) (values []any, err error) {
values = make([]any, len(terms))
for i, t := range terms {
var value any
if value, err = t.Compute(ctx); err == nil {
values[i] = value
} else {
break
}
}
return
}
func evalFirstChild(ctx kern.ExprContext, iteratorTerm *scan.Term) (value any, err error) {
if len(iteratorTerm.Children) < 1 || iteratorTerm.Children[0] == nil {
err = iteratorTerm.Errorf("missing the data-source parameter")
return
}
value, err = iteratorTerm.Children[0].Compute(ctx)
return
}
func getDataSourceDict(iteratorTerm *scan.Term, firstChildValue any) (ds map[string]kern.Functor, err error) {
if dictAny, ok := firstChildValue.(*kern.DictType); ok {
requiredFields := []string{kern.NextName}
fieldsMask := 0b1
foundFields := 0
ds = make(map[string]kern.Functor)
for keyAny, item := range *dictAny {
if key, ok := keyAny.(string); ok {
if functor, ok := item.(kern.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 = iteratorTerm.Children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
}
}
return
}
func evalIterator(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var firstChildValue any
var ds map[string]kern.Functor
if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
return
}
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
return
}
err = nil
if ds != nil {
if len(ds) > 0 {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[kern.InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.Children) > 1 {
if args, err = evalTermArray(ctx, opTerm.Children[1:]); err != nil {
return
}
} else {
args = []any{}
}
actualParams := kern.BindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, kern.InitName, actualParams); err != nil {
return
}
kern.ExportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
} else {
dc = NewDataCursor(dcCtx, ds, nil)
}
v = dc
} else {
if dictIt, ok := firstChildValue.(*kern.DictType); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v, err = NewDictIterator(dictIt, args)
}
} else {
err = opTerm.Children[0].Errorf("the data-source must be a dictionary")
}
}
} else if list, ok := firstChildValue.(*kern.ListType); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v = NewListIterator(list, args)
}
} else if list, ok := firstChildValue.(*kern.LinkedList); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v = NewLinkedListIterator(list, args)
}
} else if intVal, ok := firstChildValue.(int64); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, intVal); err == nil {
v, err = NewIntIterator(args)
}
} else if it, ok := firstChildValue.(kern.Iterator); ok {
v, err = NewIterIter(it, ctx, opTerm.Children[1:])
} else {
var siblings []any
if siblings, err = evalSiblings(ctx, opTerm.Children, firstChildValue); err == nil {
v = NewArrayIterator(siblings)
}
}
return
}
// func evalIterIter(ctx kern.ExprContext, firstChildValue any, siblings []any) (v any, err error) {
// var op kern.Functor
// var args map[string]any
// if it, ok := firstChildValue.(kern.Iterator); ok {
// if len(siblings) > 1 {
// if op, ok = siblings[1].(kern.Functor); ok {
// args = make(map[string]any, len(siblings)-2)
// for i, arg := range siblings[2:] {
// switch a := arg.(type) {
// case *kern.DictType:
// for keyAny, item := range *a {
// if key, ok := keyAny.(string); ok {
// args[key] = item
// }
// }
// default:
// args["arg"+strconv.Itoa(i+1)] = arg
// }
// }
// } else if op == nil {
// return nil, fmt.Errorf("the first sibling parameter must be a functor to be used as operation for the iterator")
// }
// }
// v, err = NewIterIter(it, ctx, op, args)
// }
// return
// }
func evalSiblings(ctx kern.ExprContext, terms []*scan.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
}
-43
View File
@@ -1,43 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-list.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- list term
// func newLinkedListTermA(args ...*scan.Term) *scan.Term {
// return newLinkedListTerm(0, 0, args)
// }
func newLinkedListTerm(row, col int, args []*scan.Term) *scan.Term {
return &scan.Term{
Tk: *scan.NewValueToken(row, col, scan.SymLinkedList, "[<>]", args),
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalLinkedList,
}
}
// -------- list func
func evalLinkedList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
list, _ := opTerm.Value().([]*scan.Term)
items := kern.NewLinkedList()
for _, tree := range list {
var param any
if param, err = tree.Compute(ctx); err != nil {
break
}
items.PushBack(param)
}
if err == nil {
v = items
}
return
}

Some files were not shown because too many files have changed in this diff Show More