Compare commits

..

68 Commits

Author SHA1 Message Date
camoroso 382cb7aabd Expr.doc: more details about the run() function from builtin iterator 2026-07-31 08:01:13 +02:00
camoroso dd9aa5d52d builtin-iterator.go: added supporto to automatic variables $_ (iterator index) and $__ (iterator item) 2026-07-31 07:27:59 +02:00
camoroso 1d15b99740 int-iterator: fixed iterator's index value 2026-07-31 07:26:41 +02:00
camoroso 7430e9bdf7 Doc: many improvements 2026-07-30 16:15:31 +02:00
camoroso 8084ffbc19 refactored tests based on test-file.txt; this file is no more needed 2026-07-30 16:11:17 +02:00
camoroso e896e06a93 t_builtin-iterator_test.go: added setup and cleanup function that prepare data&files for test. 2026-07-30 15:39:44 +02:00
camoroso 7df7d1463f builtin-os-file-iter-line/byte.go: Clean() function does not reset data content 2026-07-30 15:37:37 +02:00
camoroso 948bad6f96 t_common_test.go: added function fileExists() 2026-07-30 15:35:25 +02:00
camoroso 1c4ba5ef0b builtin-iterator.go: Added function abort() ans fixed typos in error messages 2026-07-30 14:56:00 +02:00
camoroso 648533cbe3 int-iterator is now defined interval 2026-07-28 07:16:49 +02:00
camoroso 8e596d5979 several changes:
1. moved scan/symbol.go to new package sym and adapted all files that reference Symbol type and its values.
2. new type interval defined by begin, end and step values.
3. replaced operator-range.go with operator-interval.go.
4. replaced range operator begin:end with begin..end..step.
5. new implementation of sub-collection extraction based on new interval literal.
2026-07-27 16:01:52 +02:00
camoroso 07aecfc4e7 digest operator assigns the special variable 'last' for each iteration 2026-07-21 09:09:32 +02:00
camoroso 2f616f349b at operator return -1 when fails in lists and arrays; insert operators (<< and >>) changes che container if it is a variable 2026-07-16 07:25:15 +02:00
camoroso 8c55f4ac4b operator at for dict 2026-07-14 06:50:56 +02:00
camoroso bf2c7df0f0 forgot to add the array module. Separated tests on array and list from operators 2026-07-14 06:49:50 +02:00
camoroso 6b561fea45 array and linked-list: at operator 2026-07-13 15:10:51 +02:00
camoroso daca05e637 linked-list: sum and in operators 2026-07-13 14:27:18 +02:00
camoroso 4df222496d linked-list supports index assign and concatenation 2026-07-13 11:34:25 +02:00
camoroso 072fd9af39 completed list-type to array-type conversion and fixed common test module 2026-07-13 10:42:17 +02:00
camoroso 77f36642b4 The name of 'list' has been changed to 'array'; from now on, 'list' will refer to the linked list. 2026-07-12 07:35:28 +02:00
camoroso b6da9bcad4 refactored data-types to reduce plugin sizes 2026-06-08 07:02:56 +02:00
camoroso fec7cc546c Merge branch 'main' into all-in-one 2026-06-07 05:46:44 +02:00
camoroso d8dc2939b4 Expr.adoc: typos 2026-06-06 06:51:17 +02:00
camoroso dba8e01aa0 ecli.doc: correction and improvement 2026-06-06 06:14:58 +02:00
camoroso 6e868b568d Expr.adoc: $$() corrected a wrong sentences 2026-06-03 09:14:17 +02:00
camoroso 73e4ad7b87 Expr.adoc/groupby: group keys are strings 2026-06-03 09:11:51 +02:00
camoroso 6a6c897268 first ecli commit 2026-06-03 08:59:48 +02:00
camoroso 9eb3e2d072 Merge branch 'main' into all-in-one 2026-06-03 06:38:42 +02:00
camoroso b86ce6fe62 Expr.adoc: replaced dev-expr with ecli 2026-06-03 06:38:13 +02:00
camoroso 38316cec0b Expr.adoc: typo on $# var 2026-06-03 06:08:35 +02:00
camoroso ee7f488ebb Expr.adoc: automatic variables in the interator infix operators" 2026-06-02 11:35:36 +02:00
camoroso 02ea20cdb5 Expr.adoc: $$() for iterator and better explanation of infix iterator operator 2026-06-02 11:02:04 +02:00
camoroso 20cffbddfa Expr.doc: new Linked list type and review of iterators' operators 2026-06-02 02:36:02 +02:00
camoroso cd8f331a32 Merge branch 'main' into all-in-one 2026-05-22 06:47:50 +02:00
camoroso 4b2b573420 Linked-list: added support for operators '<<' and '>>'. Also fixed list insertion: ['x'] >> [1,2] must return [1,2,['x'], not [1,2,'x']; use $([]) to flat a list 2026-05-22 06:46:39 +02:00
camoroso 7ed5a806f9 changed import conventions for plugin names: expr-<scoped-name>; scoped-name = <scope>-<name> 2026-05-22 05:59:46 +02:00
camoroso ac5c97bfd3 t_helpers_test.go: enhanced tests for the helpers module 2026-05-21 03:54:13 +02:00
camoroso e1c24daac4 rationalized context convertion to dict and string types 2026-05-21 03:52:48 +02:00
camoroso a62f27b104 increased test coverage (83.9%) 2026-05-21 03:06:52 +02:00
camoroso 1055569dd6 Dict literal now accepts expressions as keys. Key values are computed at evalutetion time and still must be integer or string 2026-05-20 05:14:22 +02:00
camoroso 1aea1c14d2 LinkedList: added support for index and range extraction 2026-05-19 06:42:45 +02:00
camoroso 081395be5f linked-list-iterator and context operator $$ enhancement 2026-05-18 09:50:06 +02:00
camoroso 35a599b284 operator length # supports linked-list 2026-05-18 08:56:44 +02:00
camoroso eda3037855 operator $$() changed to return a linked-list in place of list 2026-05-18 08:55:28 +02:00
camoroso 47c181546a linked-list: added forgotten source files 2026-05-18 08:53:46 +02:00
camoroso a8a5d6aaa6 Linked-List: constructor NewLinkedListA() accepts any type of int and float 2026-05-18 06:24:59 +02:00
camoroso 84b255a51b new type LinkedList, preliminary implementation 2026-05-17 22:43:27 +02:00
camoroso 9efdeffcac test with mixed field name and index 2026-05-17 19:14:35 +02:00
camoroso d34b9d8a48 dict: implemented sub-field access, e.g D=["sub-dict"]["sub-field"] 2026-05-17 18:57:27 +02:00
camoroso 1bf8015da1 the assign operators, '=' and ':=', can set the value of dict items 2026-05-17 07:15:12 +02:00
camoroso 3ccbeb3978 util/utils.go: new function UnquoteString() 2026-05-17 07:02:35 +02:00
camoroso 0c719025cd new operator ':=', it assigns a value to a variable by deep copy 2026-05-17 05:02:07 +02:00
camoroso 08617378e0 doc: added some details 2026-05-16 17:33:45 +02:00
camoroso e6844ad1e8 alias operators: '<<' same as '<+', '>>' same as '+>'. Insert and append operation optimized with linked lists 2026-05-16 17:18:23 +02:00
camoroso 3a4e217891 kern/fraction-type.go: changed some syntax 2026-05-13 08:01:18 +02:00
camoroso 0f293fdbbc restructured some test files 2026-05-11 14:01:22 +02:00
camoroso 45faea7620 map, filter and cat return an iterator 2026-05-09 17:42:04 +02:00
camoroso e5a61b5638 iter-ops raised above assign; 'cat' operator returns an iterator; $$ operator supports iterators producing a list of items 2026-05-09 12:15:59 +02:00
camoroso 5285b61320 'join' operator renamed as 'cat' 2026-05-09 10:34:46 +02:00
camoroso 5950630cf7 Doc: added more details and new functions and operators (iterator operators) 2026-05-08 15:17:24 +02:00
camoroso 78e431f2b9 forgotten changes to builtin-os-file-iter.go 2026-05-08 11:07:53 +02:00
camoroso c7dce8288f file iterators refactored 2026-05-08 11:03:16 +02:00
camoroso c10053253c os file builtins refactored with the package 'file' 2026-05-08 10:16:03 +02:00
camoroso a3c7cf2efa iter-iter.go: check ctx against nil on creation 2026-05-08 10:14:07 +02:00
camoroso dfa1491093 kern/common-errors.go: new error function ErrFuncInvalidArg() 2026-05-08 10:10:28 +02:00
camoroso 99c1adc434 copyright header updated to 2026 2026-05-06 09:27:42 +02:00
camoroso 5585b496fb iter-iter: changed item operation from function and args to a list of expressions 2026-05-06 04:04:08 +02:00
camoroso acd4f8487d new iter-iter iterator and kern.func-info module 2026-05-05 20:38:30 +02:00
184 changed files with 8948 additions and 3230 deletions
+10 -8
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// list-iterator.go // list-iterator.go
@@ -10,10 +10,12 @@ import (
"slices" "slices"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
) )
type ListIterator struct { type ListIterator struct {
a *kern.ListType a *array.ArrayType
count int64 count int64
index int64 index int64
start int64 start int64
@@ -21,7 +23,7 @@ type ListIterator struct {
step int64 step int64
} }
func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) { func NewListIterator(list *array.ArrayType, args []any) (it *ListIterator) {
var argc int = 0 var argc int = 0
listLen := int64(len(([]any)(*list))) listLen := int64(len(([]any)(*list)))
if args != nil { if args != nil {
@@ -29,21 +31,21 @@ func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
} }
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1} it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 { if argc >= 1 {
if i, err := kern.ToGoInt64(args[0], "start index"); err == nil { if i, err := types.ToGoInt64(args[0], "start index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.start = i it.start = i
} }
if argc >= 2 { if argc >= 2 {
if i, err := kern.ToGoInt64(args[1], "stop index"); err == nil { if i, err := types.ToGoInt64(args[1], "stop index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.stop = i it.stop = i
} }
if argc >= 3 { if argc >= 3 {
if i, err := kern.ToGoInt64(args[2], "step"); err == nil { if i, err := types.ToGoInt64(args[2], "step"); err == nil {
if i < 0 { if i < 0 {
i = -i i = -i
} }
@@ -60,8 +62,8 @@ func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
return return
} }
func NewArrayIterator(array []any) (it *ListIterator) { func NewArrayIterator(a []any) (it *ListIterator) {
it = &ListIterator{a: (*kern.ListType)(&array), count: 0, index: -1, start: 0, stop: int64(len(array)) - 1, step: 1} it = &ListIterator{a: (*array.ArrayType)(&a), count: 0, index: -1, start: 0, stop: int64(len(a)) - 1, step: 1}
return return
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// function.go // function.go
+82 -48
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-base.go // builtin-base.go
@@ -12,6 +12,14 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/boolean"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/float"
"git.portale-stac.it/go-pkg/expr/types/fract"
"git.portale-stac.it/go-pkg/expr/types/list"
"git.portale-stac.it/go-pkg/expr/types/str"
) )
const ( const (
@@ -24,42 +32,47 @@ func isNilFunc(ctx kern.ExprContext, name string, args map[string]any) (result a
} }
func isIntFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isIntFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsInteger(args[kern.ParamValue]) result = types.IsInteger(args[kern.ParamValue])
return return
} }
func isFloatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isFloatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFloat(args[kern.ParamValue]) result = float.IsFloat(args[kern.ParamValue])
return return
} }
func isBoolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isBoolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsBool(args[kern.ParamValue]) result = boolean.IsBool(args[kern.ParamValue])
return return
} }
func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsString(args[kern.ParamValue]) result = str.IsString(args[kern.ParamValue])
return return
} }
func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFraction(args[kern.ParamValue]) result = fract.IsFraction(args[kern.ParamValue])
return return
} }
func isRationalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isRationalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsRational(args[kern.ParamValue]) result = fract.IsRational(args[kern.ParamValue])
return return
} }
func isListFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isArrayFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsList(args[kern.ParamValue]) result = array.IsArray(args[kern.ParamValue])
return return
} }
func isDictionaryFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func isDictionaryFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsDict(args[kern.ParamValue]) result = dict.IsDict(args[kern.ParamValue])
return
}
func isListFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = list.IsLinkedList(args[kern.ParamValue])
return return
} }
@@ -67,7 +80,7 @@ func boolFunc(ctx kern.ExprContext, name string, args map[string]any) (result an
switch v := args[kern.ParamValue].(type) { switch v := args[kern.ParamValue].(type) {
case int64: case int64:
result = (v != 0) result = (v != 0)
case *kern.FractionType: case *fract.FractionType:
result = v.N() != 0 result = v.N() != 0
case float64: case float64:
result = v != 0.0 result = v != 0.0
@@ -75,9 +88,9 @@ func boolFunc(ctx kern.ExprContext, name string, args map[string]any) (result an
result = v result = v
case string: case string:
result = len(v) > 0 result = len(v) > 0
case *kern.ListType: case *array.ArrayType:
result = len(*v) > 0 result = len(*v) > 0
case *kern.DictType: case *dict.DictType:
result = len(*v) > 0 result = len(*v) > 0
default: default:
err = kern.ErrCantConvert(name, v, "bool") err = kern.ErrCantConvert(name, v, "bool")
@@ -102,7 +115,7 @@ func intFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
if i, err = strconv.Atoi(v); err == nil { if i, err = strconv.Atoi(v); err == nil {
result = int64(i) result = int64(i)
} }
case *kern.FractionType: case *fract.FractionType:
result = int64(v.N() / v.D()) result = int64(v.N() / v.D())
default: default:
err = kern.ErrCantConvert(name, v, "int") err = kern.ErrCantConvert(name, v, "int")
@@ -110,7 +123,7 @@ func intFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
return return
} }
func decFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func floatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) { switch v := args[kern.ParamValue].(type) {
case int64: case int64:
result = float64(v) result = float64(v)
@@ -127,7 +140,7 @@ func decFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
if f, err = strconv.ParseFloat(v, 64); err == nil { if f, err = strconv.ParseFloat(v, 64); err == nil {
result = f result = f
} }
case *kern.FractionType: case *fract.FractionType:
result = v.ToFloat() result = v.ToFloat()
default: default:
err = kern.ErrCantConvert(name, v, "float") err = kern.ErrCantConvert(name, v, "float")
@@ -149,7 +162,7 @@ func stringFunc(ctx kern.ExprContext, name string, args map[string]any) (result
} }
case string: case string:
result = v result = v
case *kern.FractionType: case *fract.FractionType:
result = v.ToString(0) result = v.ToString(0)
case kern.Formatter: case kern.Formatter:
result = v.ToString(0) result = v.ToString(0)
@@ -174,19 +187,19 @@ func fractFunc(ctx kern.ExprContext, name string, args map[string]any) (result a
} }
if err == nil { if err == nil {
result = kern.NewFraction(v, den) result = fract.NewFraction(v, den)
} }
case float64: case float64:
result, err = kern.Float64ToFraction(v) result, err = fract.Float64ToFraction(v)
case bool: case bool:
if v { if v {
result = kern.NewFraction(1, 1) result = fract.NewFraction(1, 1)
} else { } else {
result = kern.NewFraction(0, 1) result = fract.NewFraction(0, 1)
} }
case string: case string:
result, err = kern.MakeGeneratingFraction(v) result, err = fract.MakeGeneratingFraction(v)
case *kern.FractionType: case *fract.FractionType:
result = v result = v
default: default:
err = kern.ErrCantConvert(name, v, "float") err = kern.ErrCantConvert(name, v, "float")
@@ -255,24 +268,41 @@ func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
return return
} }
// func unsetFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
// var varName string var ord int
// var ok bool
// if varName, ok = args[kern.ParamName].(string); !ok { if n, ok := args[kern.ParamValue].(byte); ok {
// return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName]) ord = int(n)
// } else { } else if n, ok := args[kern.ParamValue].(int64); ok {
// ctx.GetParent().DeleteVar(varName) ord = int(n)
// result = nil } else if n, ok := args[kern.ParamValue].(int); ok {
// } ord = n
// return } 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 := list.NewLinkedList()
items := args[kern.ParamValue].([]any)
for _, arg := range items {
list.PushBack(arg)
}
result = list
return
}
//// import //// import
func ImportBuiltinsFuncs(ctx kern.ExprContext) { func ImportBuiltinsFuncs(ctx kern.ExprContext) {
anyParams := []kern.ExprFuncParam{ anyParams := []kern.ExprFuncParam{
NewFuncParam(kern.ParamValue), kern.NewFuncParam(kern.ParamValue),
} }
ctx.RegisterFunc("isNil", kern.NewGolangFunctor(isNilFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("isNil", kern.NewGolangFunctor(isNilFunc), kern.TypeBoolean, anyParams)
@@ -282,36 +312,40 @@ func ImportBuiltinsFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("isString", kern.NewGolangFunctor(isStringFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("isString", kern.NewGolangFunctor(isStringFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isFract", kern.NewGolangFunctor(isFractionFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("isFract", kern.NewGolangFunctor(isFractionFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isRational", kern.NewGolangFunctor(isRationalFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("isRational", kern.NewGolangFunctor(isRationalFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isList", kern.NewGolangFunctor(isListFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("isArray", kern.NewGolangFunctor(isArrayFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isDict", kern.NewGolangFunctor(isDictionaryFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("isDict", kern.NewGolangFunctor(isDictionaryFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isList", kern.NewGolangFunctor(isListFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("bool", kern.NewGolangFunctor(boolFunc), kern.TypeBoolean, anyParams) ctx.RegisterFunc("bool", kern.NewGolangFunctor(boolFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("int", kern.NewGolangFunctor(intFunc), kern.TypeInt, anyParams) ctx.RegisterFunc("int", kern.NewGolangFunctor(intFunc), kern.TypeInt, anyParams)
ctx.RegisterFunc("dec", kern.NewGolangFunctor(decFunc), kern.TypeFloat, anyParams) ctx.RegisterFunc("float", kern.NewGolangFunctor(floatFunc), kern.TypeFloat, anyParams)
ctx.RegisterFunc("string", kern.NewGolangFunctor(stringFunc), kern.TypeString, anyParams) ctx.RegisterFunc("string", kern.NewGolangFunctor(stringFunc), kern.TypeString, anyParams)
ctx.RegisterFunc("fract", kern.NewGolangFunctor(fractFunc), kern.TypeFraction, []kern.ExprFuncParam{ ctx.RegisterFunc("fract", kern.NewGolangFunctor(fractFunc), kern.TypeFraction, []kern.ExprFuncParam{
NewFuncParam(kern.ParamValue), kern.NewFuncParam(kern.ParamValue),
NewFuncParamFlagDef(ParamDenominator, PfDefault, int64(1)), kern.NewFuncParamFlagDef(ParamDenominator, kern.PfDefault, int64(1)),
}) })
ctx.RegisterFunc("eval", kern.NewGolangFunctor(evalFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("eval", kern.NewGolangFunctor(evalFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
}) })
ctx.RegisterFunc("var", kern.NewGolangFunctor(varFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("var", kern.NewGolangFunctor(varFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParam(kern.ParamName), kern.NewFuncParam(kern.ParamName),
NewFuncParamFlagDef(kern.ParamValue, PfDefault, nil), kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault, nil),
}) })
ctx.RegisterFunc("set", kern.NewGolangFunctor(setFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("set", kern.NewGolangFunctor(setFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParam(kern.ParamName), kern.NewFuncParam(kern.ParamName),
NewFuncParam(kern.ParamValue), kern.NewFuncParam(kern.ParamValue),
}) })
// ctx.RegisterFunc("unset", kern.NewGolangFunctor(unsetFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{
// NewFuncParam(kern.ParamName), kern.NewFuncParam(kern.ParamValue),
// NewFuncParam(kern.ParamValue), })
// })
ctx.RegisterFunc("seq", kern.NewGolangFunctor(seqFunc), kern.TypeLinkedList, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamValue, kern.PfRepeat),
})
} }
func init() { func init() {
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-fmt.go // builtin-fmt.go
@@ -47,10 +47,10 @@ func printLnFunc(ctx kern.ExprContext, name string, args map[string]any) (result
func ImportFmtFuncs(ctx kern.ExprContext) { func ImportFmtFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("print", kern.NewGolangFunctor(printFunc), kern.TypeInt, []kern.ExprFuncParam{ ctx.RegisterFunc("print", kern.NewGolangFunctor(printFunc), kern.TypeInt, []kern.ExprFuncParam{
NewFuncParamFlag(kern.ParamItem, PfRepeat), kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
}) })
ctx.RegisterFunc("println", kern.NewGolangFunctor(printLnFunc), kern.TypeInt, []kern.ExprFuncParam{ ctx.RegisterFunc("println", kern.NewGolangFunctor(printLnFunc), kern.TypeInt, []kern.ExprFuncParam{
NewFuncParamFlag(kern.ParamItem, PfRepeat), kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
}) })
} }
+5 -4
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-import.go // builtin-import.go
@@ -10,6 +10,7 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
func importFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func importFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
@@ -47,7 +48,7 @@ func doImport(ctx kern.ExprContext, name string, dirList []string, it kern.Itera
var expr *scan.Ast var expr *scan.Ast
scanner := scan.NewScanner(file, scan.DefaultTranslations()) scanner := scan.NewScanner(file, scan.DefaultTranslations())
parser := NewParser() parser := NewParser()
if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, scan.SymEos); err == nil { if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, sym.SymEos); err == nil {
result, err = expr.Eval(ctx) result, err = expr.Eval(ctx)
} }
if err != nil { if err != nil {
@@ -69,10 +70,10 @@ func doImport(ctx kern.ExprContext, name string, dirList []string, it kern.Itera
func ImportImportFuncs(ctx kern.ExprContext) { func ImportImportFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("import", kern.NewGolangFunctor(importFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("import", kern.NewGolangFunctor(importFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParamFlag(kern.ParamFilepath, PfRepeat), kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
}) })
ctx.RegisterFunc("importAll", kern.NewGolangFunctor(importAllFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("importAll", kern.NewGolangFunctor(importAllFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParamFlag(kern.ParamFilepath, PfRepeat), kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
}) })
} }
+52 -20
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-iterator.go // builtin-iterator.go
@@ -9,32 +9,39 @@ import (
"io" "io"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types/boolean"
"git.portale-stac.it/go-pkg/expr/types/dict"
) )
const ( const (
iterParamOperator = "operator" iterParamOperator = "operator"
iterParamVars = "vars" iterParamVars = "vars"
iterVarStatus = "status" iterVarStatus = "status"
iterParamStatus = "status"
iterVarAbort = "__abort"
) )
func parseRunArgs(localCtx kern.ExprContext, args map[string]any) (it kern.Iterator, op kern.Functor, err error) { func parseRunArgs(localCtx kern.ExprContext, args map[string]any) (it kern.Iterator, op kern.Functor, err error) {
var ok bool var ok bool
if it, ok = args[kern.ParamIterator].(kern.Iterator); !ok { 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])) // err = fmt.Errorf("parameter %q must be an iterator, passed %v [%s]", kern.ParamIterator, args[kern.ParamIterator], kern.TypeName(args[kern.ParamIterator]))
err = errBadParamType(kern.ParamIterator, kern.TypeIterator, args[kern.ParamIterator])
return return
} }
if args[iterParamOperator] != nil { if args[iterParamOperator] != nil {
if op, ok = args[iterParamOperator].(kern.Functor); !ok || op == 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])) // err = fmt.Errorf("parameter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], kern.TypeName(args[iterParamOperator]))
err = errBadParamType(iterParamOperator, "function", args[iterParamOperator])
return return
} }
} }
var vars *kern.DictType var vars *dict.DictType
if vars, ok = args[iterParamVars].(*kern.DictType); !ok && args[iterParamVars] != nil { if vars, ok = args[iterParamVars].(*dict.DictType); !ok && args[iterParamVars] != nil {
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], kern.TypeName(args[iterParamVars])) // err = fmt.Errorf("parameter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], kern.TypeName(args[iterParamVars]))
err = errBadParamType(iterParamVars, kern.TypeDict, args[iterParamVars])
return return
} }
@@ -50,12 +57,21 @@ func parseRunArgs(localCtx kern.ExprContext, args map[string]any) (it kern.Itera
return return
} }
func errBadParamType(paramName, wantedType string, wantedValue any) error {
return fmt.Errorf("type of %q must be %s, passed %v [%s]", paramName, wantedType, wantedValue, kern.TypeName(wantedValue))
}
func abortFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if status, provided := args[iterParamStatus]; provided {
ctx.UnsafeSetVar("@"+iterVarStatus, status)
}
ctx.UnsafeSetVar("@"+iterVarAbort, true)
return
}
func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var it kern.Iterator var it kern.Iterator
var ok bool
var op kern.Functor var op kern.Functor
var v any
// var usingDefaultOp = false
var params map[string]any var params map[string]any
var item any var item any
@@ -66,17 +82,33 @@ func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
return return
} }
localCtx.RegisterFunc("abort", kern.NewGolangFunctor(abortFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(iterParamStatus, kern.PfOptional, "aborted"),
})
params = map[string]any{kern.ParamIndex: 0, kern.ParamItem: nil}
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
if op != nil { if op != nil {
params = map[string]any{kern.ParamIndex: it.Index(), kern.ParamItem: item} // params = map[string]any{kern.ParamIndex: it.Index(), kern.ParamItem: item}
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil { params[kern.ParamIndex] = it.Index()
break params[kern.ParamItem] = item
} else {
var success bool localCtx.UnsafeSetVar("_", it.Index())
if success, ok = kern.ToBool(v); !success || !ok { localCtx.UnsafeSetVar("__", item)
break
abort := false
if _, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
abort = true
} else if abortAny, exists := localCtx.GetVar(iterVarAbort); exists {
if abort, ok := boolean.ToBool(abortAny); ok && abort {
abort = true
} }
} }
localCtx.DeleteVar("__")
localCtx.DeleteVar("_")
if abort {
break
}
} }
} }
@@ -85,7 +117,7 @@ func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
} }
if err == nil { if err == nil {
if op == nil { if op == nil {
ctx.UnsafeSetVar(iterVarStatus, it.Count()) localCtx.UnsafeSetVar(iterVarStatus, it.Count())
} }
result, _ = localCtx.GetVar(iterVarStatus) result, _ = localCtx.GetVar(iterVarStatus)
} }
@@ -94,9 +126,9 @@ func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
func ImportIterFuncs(ctx kern.ExprContext) { func ImportIterFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("run", kern.NewGolangFunctor(runFunc), kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc("run", kern.NewGolangFunctor(runFunc), kern.TypeAny, []kern.ExprFuncParam{
NewFuncParam(kern.ParamIterator), kern.NewFuncParam(kern.ParamIterator),
NewFuncParamFlag(iterParamOperator, PfOptional), kern.NewFuncParamFlag(iterParamOperator, kern.PfOptional),
NewFuncParamFlag(iterParamVars, PfOptional), kern.NewFuncParamFlag(iterParamVars, kern.PfOptional),
}) })
} }
+28 -24
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-math-arith.go // builtin-math-arith.go
@@ -9,10 +9,14 @@ import (
"io" "io"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/float"
"git.portale-stac.it/go-pkg/expr/types/fract"
) )
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) { func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
if !(kern.IsNumber(paramValue) || kern.IsFraction(paramValue)) /*|| isList(paramValue)*/ { if !(types.IsNumber(paramValue) || fract.IsFraction(paramValue)) /*|| isArray(paramValue)*/ {
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected", err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
funcName, paramPos+1, subPos+1, level, paramValue) funcName, paramPos+1, subPos+1, level, paramValue)
} }
@@ -23,13 +27,13 @@ func doAdd(ctx kern.ExprContext, name string, it kern.Iterator, count, level int
var sumAsFloat, sumAsFract bool var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0 var floatSum float64 = 0.0
var intSum int64 = 0 var intSum int64 = 0
var fractSum *kern.FractionType var fractSum *fract.FractionType
var v any var v any
level++ level++
for v, err = it.Next(); err == nil; v, err = it.Next() { for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*kern.ListType); ok { if list, ok := v.(*array.ArrayType); ok {
v = NewListIterator(list, nil) v = NewListIterator(list, nil)
} }
if subIter, ok := v.(kern.Iterator); ok { if subIter, ok := v.(kern.Iterator); ok {
@@ -47,29 +51,29 @@ func doAdd(ctx kern.ExprContext, name string, it kern.Iterator, count, level int
count++ count++
if !sumAsFloat { if !sumAsFloat {
if kern.IsFloat(v) { if float.IsFloat(v) {
sumAsFloat = true sumAsFloat = true
if sumAsFract { if sumAsFract {
floatSum = fractSum.ToFloat() floatSum = fractSum.ToFloat()
} else { } else {
floatSum = float64(intSum) floatSum = float64(intSum)
} }
} else if !sumAsFract && kern.IsFraction(v) { } else if !sumAsFract && fract.IsFraction(v) {
fractSum = kern.NewFraction(intSum, 1) fractSum = fract.NewFraction(intSum, 1)
sumAsFract = true sumAsFract = true
} }
} }
if sumAsFloat { if sumAsFloat {
floatSum += kern.NumAsFloat(v) floatSum += types.NumAsFloat(v)
} else if sumAsFract { } else if sumAsFract {
var item *kern.FractionType var item *fract.FractionType
var ok bool var ok bool
if item, ok = v.(*kern.FractionType); !ok { if item, ok = v.(*fract.FractionType); !ok {
iv, _ := v.(int64) iv, _ := v.(int64)
item = kern.NewFraction(iv, 1) item = fract.NewFraction(iv, 1)
} }
fractSum = kern.SumFract(fractSum, item) fractSum = fract.SumFract(fractSum, item)
} else { } else {
iv, _ := v.(int64) iv, _ := v.(int64)
intSum += iv intSum += iv
@@ -98,12 +102,12 @@ func doMul(ctx kern.ExprContext, name string, it kern.Iterator, count, level int
var mulAsFloat, mulAsFract bool var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0 var floatProd float64 = 1.0
var intProd int64 = 1 var intProd int64 = 1
var fractProd *kern.FractionType var fractProd *fract.FractionType
var v any var v any
level++ level++
for v, err = it.Next(); err == nil; v, err = it.Next() { for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*kern.ListType); ok { if list, ok := v.(*array.ArrayType); ok {
v = NewListIterator(list, nil) v = NewListIterator(list, nil)
} }
if subIter, ok := v.(kern.Iterator); ok { if subIter, ok := v.(kern.Iterator); ok {
@@ -123,29 +127,29 @@ func doMul(ctx kern.ExprContext, name string, it kern.Iterator, count, level int
count++ count++
if !mulAsFloat { if !mulAsFloat {
if kern.IsFloat(v) { if float.IsFloat(v) {
mulAsFloat = true mulAsFloat = true
if mulAsFract { if mulAsFract {
floatProd = fractProd.ToFloat() floatProd = fractProd.ToFloat()
} else { } else {
floatProd = float64(intProd) floatProd = float64(intProd)
} }
} else if !mulAsFract && kern.IsFraction(v) { } else if !mulAsFract && fract.IsFraction(v) {
fractProd = kern.NewFraction(intProd, 1) fractProd = fract.NewFraction(intProd, 1)
mulAsFract = true mulAsFract = true
} }
} }
if mulAsFloat { if mulAsFloat {
floatProd *= kern.NumAsFloat(v) floatProd *= types.NumAsFloat(v)
} else if mulAsFract { } else if mulAsFract {
var item *kern.FractionType var item *fract.FractionType
var ok bool var ok bool
if item, ok = v.(*kern.FractionType); !ok { if item, ok = v.(*fract.FractionType); !ok {
iv, _ := v.(int64) iv, _ := v.(int64)
item = kern.NewFraction(iv, 1) item = fract.NewFraction(iv, 1)
} }
fractProd = kern.MulFract(fractProd, item) fractProd = fract.MulFract(fractProd, item)
} else { } else {
iv, _ := v.(int64) iv, _ := v.(int64)
intProd *= iv intProd *= iv
@@ -172,11 +176,11 @@ func mulFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
func ImportMathFuncs(ctx kern.ExprContext) { func ImportMathFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("add", kern.NewGolangFunctor(addFunc), kern.TypeNumber, []kern.ExprFuncParam{ ctx.RegisterFunc("add", kern.NewGolangFunctor(addFunc), kern.TypeNumber, []kern.ExprFuncParam{
NewFuncParamFlagDef(kern.ParamValue, PfDefault|PfRepeat, int64(0)), kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(0)),
}) })
ctx.RegisterFunc("mul", kern.NewGolangFunctor(mulFunc), kern.TypeNumber, []kern.ExprFuncParam{ ctx.RegisterFunc("mul", kern.NewGolangFunctor(mulFunc), kern.TypeNumber, []kern.ExprFuncParam{
NewFuncParamFlagDef(kern.ParamValue, PfDefault|PfRepeat, int64(1)), kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(1)),
}) })
} }
+102
View File
@@ -0,0 +1,102 @@
// 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
@@ -0,0 +1,105 @@
// 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
}
+24 -104
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-os-file.go // builtin-os-file.go
@@ -6,146 +6,66 @@ package expr
import ( import (
"fmt" "fmt"
"io"
"slices" "slices"
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
) )
const paramHandleOrPath = "handle-or-path" const paramHandleOrPath = "handle-or-path"
const fileReadTextIteratorType = "fileReadTextIterator"
type fileReadTextIterator struct { type fileIterBase struct {
osReader *osReader reader *file.Reader
index int64 index int64
count int64 count int64
line string
autoClose bool autoClose bool
} }
func newReadTextIterator(r *osReader, autoClose bool) *fileReadTextIterator { func (it *fileIterBase) Count() int64 {
return &fileReadTextIterator{osReader: r, index: -1, autoClose: autoClose}
}
func (it *fileReadTextIterator) TypeName() string {
return fileReadTextIteratorType
}
func (it *fileReadTextIterator) String() string {
if it.osReader != nil && it.osReader.fh != nil {
return fmt.Sprintf("$(%s@%q)", fileReadTextIteratorType, it.osReader.fh.Name())
}
return fmt.Sprintf("$(%s@<nil>)", fileReadTextIteratorType)
}
func (it *fileReadTextIterator) Count() int64 {
return it.count return it.count
} }
func (it *fileReadTextIterator) Next() (item any, err error) { // must return io.EOF after the last item func (it *fileIterBase) Index() int64 {
if it.osReader.fh != nil {
if it.line, err = it.osReader.reader.ReadString('\n'); err == nil {
it.index++
it.count++
item = it.line[0 : len(it.line)-1]
} else if it.autoClose {
it.Clean()
}
}
return
}
func (it *fileReadTextIterator) Current() (item any, err error) {
if len(it.line) > 0 {
item = it.line[0 : len(it.line)-1]
}
return
}
func (it *fileReadTextIterator) Index() int64 {
return it.index return it.index
} }
func (it *fileReadTextIterator) Reset() (err error) { func (it *fileIterBase) HasOperation(name string) bool {
if _, err = it.osReader.fh.Seek(0, io.SeekStart); err == nil {
it.index = -1
it.count = 0
it.line = ""
}
return
}
func (it *fileReadTextIterator) HasOperation(name string) bool {
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name) return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
} }
func (it *fileReadTextIterator) Clean() (err error) { func (it *fileIterBase) reset() {
if it.osReader.fh != nil { it.index = -1
if err = it.osReader.fh.Close(); err == nil { it.count = 0
it.osReader = nil
}
}
return nil
} }
func (it *fileReadTextIterator) CallOperation(name string, args map[string]any) (v any, err error) { func (it *fileIterBase) increment() {
switch name { it.index++
case kern.NextName: it.count++
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 fileReadIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func (it *fileIterBase) repr(typeName string) string {
var handle *osReader if it.reader.Valid() {
var invalidFileHandle any return fmt.Sprintf("$(%s@%q)", typeName, it.reader.GetName())
var ok, autoClose bool }
return fmt.Sprintf("$(%s@<nil>)", typeName)
}
result = nil func initFileHandle(ctx kern.ExprContext, name string, args map[string]any) (handle *file.Reader, invalidFileHandle any, autoClose bool, err error) {
if handle, ok = args[paramHandleOrPath].(*osReader); !ok { var ok bool
if handle, ok = args[paramHandleOrPath].(*file.Reader); !ok {
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 { if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
var handleAny any var handleAny any
if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil { if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil {
return return
} }
if handleAny != nil { if handleAny != nil {
handle = handleAny.(*osReader) handle = handleAny.(*file.Reader)
autoClose = true autoClose = true
} }
} else { } else {
invalidFileHandle = args[paramHandleOrPath] invalidFileHandle = args[paramHandleOrPath]
} }
} }
if handle != nil {
result = newReadTextIterator(handle, autoClose)
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return return
} }
// func ImportOsIterFuncs(ctx ExprContext) {
// ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
// NewFuncParam(paramHandleOrPath),
// })
// }
// func init() {
// RegisterBuiltinModule("os.file", ImportOsIterFuncs, "Operating system file iterator functions")
// }
+53 -83
View File
@@ -1,15 +1,14 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-os-file.go // builtin-os-file.go
package expr package expr
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"os"
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
) )
@@ -17,44 +16,6 @@ const (
osLimitCh = "limitCh" osLimitCh = "limitCh"
) )
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) TypeName() string {
return "osWriter"
}
func (h *osWriter) String() string {
return "writer"
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) TypeName() string {
return "osReader"
}
func (h *osReader) String() string {
return "reader"
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func errMissingFilePath(funcName string) error { func errMissingFilePath(funcName string) error {
return fmt.Errorf("%s(): missing or invalid file path", funcName) return fmt.Errorf("%s(): missing or invalid file path", funcName)
} }
@@ -67,24 +28,26 @@ func errInvalidFileHandle(funcName string, v any) error {
} }
} }
func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { 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 { if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File // var fh *os.File
if fh, err = os.Create(filePath); err == nil { // if fh, err = os.Open(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} // result = file.NewReader(fh)
} // }
result, err = file.OpenReader(filePath)
} else { } else {
err = errMissingFilePath(name) err = errMissingFilePath(name)
} }
return return
} }
func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { 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 { if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File // var fh *os.File
if fh, err = os.Open(filePath); err == nil { // if fh, err = os.Create(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)} // result = file.NewWriter(fh)
} // }
result, err = file.CreateWriter(filePath)
} else { } else {
err = errMissingFilePath(name) err = errMissingFilePath(name)
} }
@@ -93,10 +56,11 @@ func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (resul
func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { 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 { if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File // var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil { // 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)} // result = file.NewWriter(fh)
} // }
result, err = file.AppendWriter(filePath)
} else { } else {
err = errMissingFilePath(name) err = errMissingFilePath(name)
} }
@@ -104,18 +68,18 @@ func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (res
} }
func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle file.Handle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
if handle, ok = args[kern.ParamHandle].(osHandle); !ok { if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
invalidFileHandle = args[kern.ParamHandle] invalidFileHandle = args[kern.ParamHandle]
} }
if handle != nil { if handle != nil {
if fh := handle.getFile(); fh != nil { if fh := handle.GetFile(); fh != nil {
if w, ok := handle.(*osWriter); ok { if w, ok := handle.(*file.Writer); ok {
err = w.writer.Flush() err = w.Flush()
} }
if err == nil { if err == nil {
@@ -131,19 +95,20 @@ func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (resu
} }
func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle file.Handle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
if handle, ok = args[kern.ParamHandle].(osHandle); !ok { if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
invalidFileHandle = args[kern.ParamHandle] invalidFileHandle = args[kern.ParamHandle]
} }
if handle != nil { if handle != nil {
if w, ok := handle.(*osWriter); ok { if w, ok := handle.(*file.Writer); ok {
if v, exists := args[kern.ParamItem]; exists { if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any) argv := v.([]any)
result, err = fmt.Fprint(w.writer, argv...) // result, err = fmt.Fprint(w.writer, argv...)
result, err = w.Write(argv...)
} }
} else { } else {
invalidFileHandle = handle invalidFileHandle = handle
@@ -157,24 +122,24 @@ func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (
} }
func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle file.Handle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
result = nil result = nil
if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil { if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle] invalidFileHandle = args[kern.ParamHandle]
} }
if handle != nil { if handle != nil {
if r, ok := handle.(*osReader); ok { if r, ok := handle.(*file.Reader); ok {
var limit byte = '\n' var limit byte = '\n'
var v string var v string
if s, ok := args[osLimitCh].(string); ok && len(s) > 0 { if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
limit = s[0] limit = s[0]
} }
v, err = r.reader.ReadString(limit) v, err = r.ReadString(limit)
if err == io.EOF { if err == io.EOF {
err = nil err = nil
} }
@@ -197,19 +162,19 @@ func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (r
} }
func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle file.Handle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
result = nil result = nil
if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil { if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle] invalidFileHandle = args[kern.ParamHandle]
} }
if handle != nil { if handle != nil {
if r, ok := handle.(*osReader); ok { if r, ok := handle.(*file.Reader); ok {
var b []byte var b []byte
b, err = io.ReadAll(r.reader) b, err = r.ReadAll()
result = string(b) result = string(b)
} else { } else {
invalidFileHandle = handle invalidFileHandle = handle
@@ -224,38 +189,43 @@ func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any)
func ImportOsFuncs(ctx kern.ExprContext) { func ImportOsFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("fileOpen", kern.NewGolangFunctor(openFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{ ctx.RegisterFunc("fileOpen", kern.NewGolangFunctor(openFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
NewFuncParam(kern.ParamFilepath), kern.NewFuncParam(kern.ParamFilepath),
}) })
ctx.RegisterFunc("fileAppend", kern.NewGolangFunctor(appendFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{ ctx.RegisterFunc("fileAppend", kern.NewGolangFunctor(appendFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
NewFuncParam(kern.ParamFilepath), kern.NewFuncParam(kern.ParamFilepath),
}) })
ctx.RegisterFunc("fileCreate", kern.NewGolangFunctor(createFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{ ctx.RegisterFunc("fileCreate", kern.NewGolangFunctor(createFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
NewFuncParam(kern.ParamFilepath), kern.NewFuncParam(kern.ParamFilepath),
}) })
ctx.RegisterFunc("fileClose", kern.NewGolangFunctor(closeFileFunc), kern.TypeBoolean, []kern.ExprFuncParam{ ctx.RegisterFunc("fileClose", kern.NewGolangFunctor(closeFileFunc), kern.TypeBoolean, []kern.ExprFuncParam{
NewFuncParam(kern.ParamHandle), kern.NewFuncParam(kern.ParamHandle),
}) })
ctx.RegisterFunc("fileWriteText", kern.NewGolangFunctor(fileWriteTextFunc), kern.TypeInt, []kern.ExprFuncParam{ ctx.RegisterFunc("fileWriteText", kern.NewGolangFunctor(fileWriteTextFunc), kern.TypeInt, []kern.ExprFuncParam{
NewFuncParam(kern.ParamHandle), kern.NewFuncParam(kern.ParamHandle),
NewFuncParamFlagDef(kern.ParamItem, PfDefault|PfRepeat, ""), kern.NewFuncParamFlagDef(kern.ParamItem, kern.PfDefault|kern.PfRepeat, ""),
}) })
ctx.RegisterFunc("fileReadText", kern.NewGolangFunctor(fileReadTextFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("fileReadText", kern.NewGolangFunctor(fileReadTextFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamHandle), kern.NewFuncParam(kern.ParamHandle),
NewFuncParamFlagDef(osLimitCh, PfDefault, "\n"), kern.NewFuncParamFlagDef(osLimitCh, kern.PfDefault, "\n"),
}) })
ctx.RegisterFunc("fileReadTextAll", kern.NewGolangFunctor(fileReadTextAllFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("fileReadTextAll", kern.NewGolangFunctor(fileReadTextAllFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamHandle), kern.NewFuncParam(kern.ParamHandle),
}) })
ctx.RegisterFunc("fileReadIterator", kern.NewGolangFunctor(fileReadIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{ ctx.RegisterFunc("fileLineIterator", kern.NewGolangFunctor(fileLineIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
NewFuncParam(paramHandleOrPath), kern.NewFuncParam(paramHandleOrPath),
}) })
ctx.RegisterFunc("fileByteIterator", kern.NewGolangFunctor(fileByteIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
kern.NewFuncParam(paramHandleOrPath),
})
} }
func init() { func init() {
+27 -25
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtin-string.go // builtin-string.go
@@ -10,6 +10,8 @@ import (
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
) )
const ( const (
@@ -43,8 +45,8 @@ func joinStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result
if v, exists := args[kern.ParamItem]; exists { if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any) argv := v.([]any)
if len(argv) == 1 { if len(argv) == 1 {
if ls, ok := argv[0].(*kern.ListType); ok { if a, ok := argv[0].(*array.ArrayType); ok {
result, err = doJoinStr(name, sep, NewListIterator(ls, nil)) result, err = doJoinStr(name, sep, NewListIterator(a, nil))
} else if it, ok := argv[0].(kern.Iterator); ok { } else if it, ok := argv[0].(kern.Iterator); ok {
result, err = doJoinStr(name, sep, it) result, err = doJoinStr(name, sep, it)
} else if s, ok := argv[0].(string); ok { } else if s, ok := argv[0].(string); ok {
@@ -72,11 +74,11 @@ func subStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
} }
if start, err = kern.ToGoInt(args[kern.ParamStart], name+"()"); err != nil { if start, err = types.ToGoInt(args[kern.ParamStart], name+"()"); err != nil {
return return
} }
if count, err = kern.ToGoInt(args[kern.ParamCount], name+"()"); err != nil { if count, err = types.ToGoInt(args[kern.ParamCount], name+"()"); err != nil {
return return
} }
@@ -194,11 +196,11 @@ func splitStrFunc(ctx kern.ExprContext, name string, args map[string]any) (resul
} else { } else {
parts = []string{} parts = []string{}
} }
list := make(kern.ListType, len(parts)) a := make(array.ArrayType, len(parts))
for i, part := range parts { for i, part := range parts {
list[i] = part a[i] = part
} }
result = &list result = &a
return return
} }
@@ -225,44 +227,44 @@ func lowerStrFunc(ctx kern.ExprContext, name string, args map[string]any) (resul
// Import above functions in the context // Import above functions in the context
func ImportStringFuncs(ctx kern.ExprContext) { func ImportStringFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSeparator), kern.NewFuncParam(kern.ParamSeparator),
NewFuncParamFlag(kern.ParamItem, PfRepeat), kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
}) })
ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
NewFuncParamFlagDef(kern.ParamStart, PfDefault, int64(0)), kern.NewFuncParamFlagDef(kern.ParamStart, kern.PfDefault, int64(0)),
NewFuncParamFlagDef(kern.ParamCount, PfDefault, int64(-1)), kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
}) })
ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
NewFuncParamFlagDef(kern.ParamSeparator, PfDefault, ""), kern.NewFuncParamFlagDef(kern.ParamSeparator, kern.PfDefault, ""),
NewFuncParamFlagDef(kern.ParamCount, PfDefault, int64(-1)), kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
}) })
ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
}) })
ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{ ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
NewFuncParam(kern.ParamPrefix), kern.NewFuncParam(kern.ParamPrefix),
NewFuncParamFlag(strParamOther, PfRepeat), kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
}) })
ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{ ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
NewFuncParam(kern.ParamSuffix), kern.NewFuncParam(kern.ParamSuffix),
NewFuncParamFlag(strParamOther, PfRepeat), kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
}) })
ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
}) })
ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{ ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{
NewFuncParam(kern.ParamSource), kern.NewFuncParam(kern.ParamSource),
}) })
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// builtins-register.go // builtins-register.go
+14
View File
@@ -0,0 +1,14 @@
# 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
@@ -0,0 +1,199 @@
#!/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
@@ -0,0 +1,84 @@
#!/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
@@ -0,0 +1,236 @@
// 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)
}
+339
View File
@@ -0,0 +1,339 @@
= 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
- Command history survival across sessions
- 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
| `base [2\|8\|10\|16]` | Set the integer output base. Use without arguments to show current base
| `exit` | Exit the REPL
| `help` | Display available commands and command-line options
| `ml` | Enable/Disable multi-line input mode for complex expressions
| `mods` | List all builtin modules (with `*` marking imported ones)
| `output [on\|off\|status]` | Enable/Disable printing of expression results
| `source <file>` | Load and execute expressions from a file
| `tty` | Enable/Disable ANSI terminal output
|===
== 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]
----
>>> it = [1, 2, 3, 4, 5] map $_ * 2
$($([#5]))
>>> $$(it)
[<
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 "@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, split operations across multiple lines terminating with a backslash (`\`):
[source]
----
>>> ml
>>> 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 '@calculations.expr' -e 'result'
50
----
=== Other Iterator Examples
[source]
----
>>> data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
// Extract names using map iterator
>>> $$(data map ($_).name)
[<
"alice",
"bob"
>]
// Filter ages greater than 28
>>> $$(data filter ($_).age > 28 map ($_).name)
[<
"alice"
>]
----
== 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
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,36 @@
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=
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
//go:build graph
// NOTE: Enable this build tag to print the graph of the expression instead of evaluating it.
// go run -tags graph cmd/ecli/main.go
// graph.go
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func printGraph() {
r := expr.NewExprReticle(ast)
fmt.Println(r.String())
}
+418
View File
@@ -0,0 +1,418 @@
// 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/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/str"
"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 types.IsInteger(result) {
fmt.Printf(opt.baseVerb, result)
fmt.Println()
} else if str.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 = array.ArrayFromStrings(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 = array.ArrayFromStrings(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
@@ -0,0 +1,49 @@
// 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
@@ -0,0 +1,36 @@
// 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
@@ -0,0 +1,140 @@
// 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
@@ -0,0 +1,79 @@
#!/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
@@ -0,0 +1,23 @@
// 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
@@ -0,0 +1 @@
1.17.0
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// data-cursors.go // data-cursors.go
+15 -10
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// dict-iterator.go // dict-iterator.go
@@ -11,6 +11,9 @@ import (
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/str"
) )
type dictIterMode int type dictIterMode int
@@ -22,7 +25,7 @@ const (
) )
type DictIterator struct { type DictIterator struct {
a *kern.DictType a *dict.DictType
count int64 count int64
index int64 index int64
keys []any keys []any
@@ -67,7 +70,7 @@ func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
} }
} }
func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err error) { func NewDictIterator(dict *dict.DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone var sortType = sortTypeNone
var s string var s string
var argAny any var argAny any
@@ -78,7 +81,7 @@ func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err err
} else { } else {
argAny = "default" argAny = "default"
} }
if s, err = kern.ToGoString(argAny, "sort type"); err == nil { if s, err = str.ToGoString(argAny, "sort type"); err == nil {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "a", "asc": case "a", "asc":
sortType = sortTypeAsc sortType = sortTypeAsc
@@ -99,7 +102,7 @@ func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err err
argAny = "default" argAny = "default"
} }
if s, err = kern.ToGoString(argAny, "iteration mode"); err == nil { if s, err = str.ToGoString(argAny, "iteration mode"); err == nil {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "k", "key", "keys": case "k", "key", "keys":
dictIt.iterMode = dictIterModeKeys dictIt.iterMode = dictIterModeKeys
@@ -116,12 +119,15 @@ func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err err
} }
} }
dictIt.makeKeys(*dict, sortType) if err == nil {
return dictIt, err dictIt.makeKeys(*dict, sortType)
it = dictIt
}
return
} }
func NewMapIterator(m map[any]any) (it *DictIterator) { func NewMapIterator(m map[any]any) (it *DictIterator) {
it = &DictIterator{a: (*kern.DictType)(&m), count: 0, index: -1, keys: nil} it = &DictIterator{a: (*dict.DictType)(&m), count: 0, index: -1, keys: nil}
it.makeKeys(m, sortTypeNone) it.makeKeys(m, sortTypeNone)
return return
} }
@@ -139,7 +145,6 @@ func (it *DictIterator) TypeName() string {
} }
func (it *DictIterator) HasOperation(name string) bool { func (it *DictIterator) HasOperation(name string) bool {
// yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName, kern.KeyName, kern.ValueName}, name) yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName, kern.KeyName, kern.ValueName}, name)
return yes return yes
} }
@@ -188,7 +193,7 @@ func (it *DictIterator) Current() (item any, err error) {
case dictIterModeItems: case dictIterModeItems:
a := *(it.a) a := *(it.a)
pair := []any{it.keys[it.index], a[it.keys[it.index]]} pair := []any{it.keys[it.index], a[it.keys[it.index]]}
item = kern.NewList(pair) item = array.NewArray(pair)
} }
} else { } else {
err = io.EOF err = io.EOF
+631 -190
View File
File diff suppressed because it is too large Load Diff
+460 -30
View File
@@ -581,7 +581,11 @@ pre.rouge .ss {
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li> <li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
<li><a href="#_but_operator">4.2. <code class="blue">but</code> operator</a></li> <li><a href="#_but_operator">4.2. <code class="blue">but</code> operator</a></li>
<li><a href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></li> <li><a href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></li>
<li><a href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></li> <li><a href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a>
<ul class="sectlevel3">
<li><a href="#_triple_special_case_of_the_selector_operator">4.4.1. Triple special case of the selector operator</a></li>
</ul>
</li>
<li><a href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code>, <code class="blue">?=</code>, and <code class="blue">?!</code></a></li> <li><a href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code>, <code class="blue">?=</code>, and <code class="blue">?!</code></a></li>
</ul> </ul>
</li> </li>
@@ -613,8 +617,10 @@ pre.rouge .ss {
<li><a href="#_dec">dec()</a></li> <li><a href="#_dec">dec()</a></li>
<li><a href="#_string">string()</a></li> <li><a href="#_string">string()</a></li>
<li><a href="#_fract">fract()</a></li> <li><a href="#_fract">fract()</a></li>
<li><a href="#_char">char()</a></li>
<li><a href="#_eval">eval()</a></li> <li><a href="#_eval">eval()</a></li>
<li><a href="#_var">var()</a></li> <li><a href="#_var">var()</a></li>
<li><a href="#_set">set</a></li>
</ul> </ul>
</li> </li>
<li><a href="#_module_fmt">7.1.2. Module "fmt"</a> <li><a href="#_module_fmt">7.1.2. Module "fmt"</a>
@@ -649,6 +655,8 @@ pre.rouge .ss {
<li><a href="#_filewritetext">fileWriteText()</a></li> <li><a href="#_filewritetext">fileWriteText()</a></li>
<li><a href="#_filereadtext">fileReadText()</a></li> <li><a href="#_filereadtext">fileReadText()</a></li>
<li><a href="#_filereadtextall">fileReadTextAll()</a></li> <li><a href="#_filereadtextall">fileReadTextAll()</a></li>
<li><a href="#_filebyteiterator">fileByteIterator()</a></li>
<li><a href="#_filelineiterator">fileLineIterator()</a></li>
</ul> </ul>
</li> </li>
<li><a href="#_module_string">7.1.7. Module "string"</a> <li><a href="#_module_string">7.1.7. Module "string"</a>
@@ -674,7 +682,15 @@ pre.rouge .ss {
<li><a href="#_named_operators">8.1.1. Named operators</a></li> <li><a href="#_named_operators">8.1.1. Named operators</a></li>
</ul> </ul>
</li> </li>
<li><a href="#_iterator_over_custom_data_sources">8.2. Iterator over custom data-sources</a></li> <li><a href="#_infixed_operators_on_iterators">8.2. Infixed operators on iterators</a>
<ul class="sectlevel3">
<li><a href="#_cat_operator">8.2.1. <code class="blue">cat</code> operator</a></li>
<li><a href="#_filter_operator">8.2.2. <code class="blue">filter</code> operator</a></li>
<li><a href="#_groupby_operator">8.2.3. <code class="blue">groupby</code> operator</a></li>
<li><a href="#_map_operator">8.2.4. <code class="blue">map</code> operator</a></li>
</ul>
</li>
<li><a href="#_iterator_over_custom_data_sources">8.3. Iterator over custom data-sources</a></li>
</ul> </ul>
</li> </li>
<li><a href="#_plugins">9. Plugins</a></li> <li><a href="#_plugins">9. Plugins</a></li>
@@ -686,7 +702,7 @@ pre.rouge .ss {
<div class="sectionbody"> <div class="sectionbody">
<!-- toc disabled --> <!-- toc disabled -->
<div class="paragraph"> <div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2026/04/15, 6:02 p.m.)</mark></p> <p><mark>TODO: Work in progress (last update on 2026/05/08)</mark></p>
</div> </div>
</div> </div>
</div> </div>
@@ -788,7 +804,26 @@ pre.rouge .ss {
<code class="green">}</code></p> <code class="green">}</code></p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p>In order to inspect the global context issue the <code class="blue">$$global</code> operator.</p> <p>In order to inspect the global context issue the <code class="blue">$$ global</code> operation.</p>
</div>
<div class="paragraph">
<div class="title">Example: list all functions whose name starts with "str"</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">builtin "string</code> <em class="gray">// most function in the builtin module <strong>string</strong> have names starting with "str".</em><br>
<code class="green">1</code></p>
</div>
<div class="paragraph">
<p><code>&gt;&gt;&gt;</code> <code class="blue">($$$global).functions) filter strStartsWith($_, "str"</code><br>
<code class="green">[</code><br>
<code class="green">&#160;&#160;"strEndsWith",</code><br>
<code class="green">&#160;&#160;"strJoin",</code><br>
<code class="green">&#160;&#160;"strLower",</code><br>
<code class="green">&#160;&#160;"strSplit",</code><br>
<code class="green">&#160;&#160;"strStartsWith",</code><br>
<code class="green">&#160;&#160;"strSub",</code><br>
<code class="green">&#160;&#160;"strTrim",</code><br>
<code class="green">&#160;&#160;"strUpper",</code><br>
<code class="green">&#160;&#160;"string"</code><br>
<code class="green">]</code></p>
</div> </div>
</div> </div>
</div> </div>
@@ -1880,10 +1915,10 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
<p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p> <p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p>
</div> </div>
<div class="exampleblock"> <div class="exampleblock">
<div class="title">Example 13. Selector literal Syntax</div> <div class="title">Example 13. Selector literal syntax</div>
<div class="content"> <div class="content">
<div class="paragraph"> <div class="paragraph">
<p><em>selector-operator</em> = <em>select-expression</em> "<strong>?</strong>" <em>selector-case</em> { "<strong>:</strong>" <em>selector-case</em> } ["<strong>::</strong>" <em>default-multi-expression</em>]<br> <p><strong><em>selector-operator</em></strong> = <em>select-expression</em> "<strong>?</strong>" <em>selector-case</em> { "<strong>:</strong>" <em>selector-case</em> } ["<strong>::</strong>" <em>default-multi-expression</em>]<br>
<em>selector-case</em> = [<em>match-list</em>] <em>case-value</em><br> <em>selector-case</em> = [<em>match-list</em>] <em>case-value</em><br>
<em>match-list</em> = "<strong>[</strong>" <em>item</em> {"<strong>,</strong>" <em>items</em>} "<strong>]</strong>"<br> <em>match-list</em> = "<strong>[</strong>" <em>item</em> {"<strong>,</strong>" <em>items</em>} "<strong>]</strong>"<br>
<em>item</em> = <em>expression</em><br> <em>item</em> = <em>expression</em><br>
@@ -1931,11 +1966,45 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
<p><code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} : {"b"}</code><br> <p><code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} : {"b"}</code><br>
<code class="red">Eval Error: [1:3] no case catches the value (10) of the selection expression</code></p> <code class="red">Eval Error: [1:3] no case catches the value (10) of the selection expression</code></p>
</div> </div>
<div class="sect3">
<h4 id="_triple_special_case_of_the_selector_operator"><a class="anchor" href="#_triple_special_case_of_the_selector_operator"></a><a class="link" href="#_triple_special_case_of_the_selector_operator">4.4.1. Triple special case of the selector operator</a></h4>
<div class="paragraph">
<p>If the <em>select-expression</em> is a boolean expression, the selector operator can be used as a sort of <em>if-then-else</em> statement. In this case, the first case is evaluated if the <em>select-expression</em> is true, and the second case is evaluated if the <em>select-expression</em> is false. In this special case, the <em>match-list</em> of both cases must be empty.</p>
</div>
<div class="paragraph">
<div class="title">Example</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">(true) ? {"T"}: {"F"}</code><br>
<code class="green">T</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">(2 &gt; 1) ? {"a"} : {"b"}</code><br>
<code class="green">a</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">(2 &lt; 1) ? {"a"} : {"b"}</code><br>
<code class="green">b</code></p>
</div>
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
<div class="paragraph">
<p>The triple special case of the selector operator is very useful, but it only works with boolean expressions.</p>
</div>
<div class="paragraph">
<div class="title">Example of confusion</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">int(true) ? {"T"}: {"F"}</code><br>
<code class="green">F</code></p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_variable_default_value_and"><a class="anchor" href="#_variable_default_value_and"></a><a class="link" href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code>, <code class="blue">?=</code>, and <code class="blue">?!</code></a></h3> <h3 id="_variable_default_value_and"><a class="anchor" href="#_variable_default_value_and"></a><a class="link" href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code>, <code class="blue">?=</code>, and <code class="blue">?!</code></a></h3>
<div class="paragraph"> <div class="paragraph">
<p>The left operand of the first two operators, <code class="blue">??</code> and <code class="blue">?=</code>, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.</p> <p>The left operand of the first two operators, <code class="blue">??</code> and <code class="blue">?=</code>, must be a variable. The right operatand can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.</p>
</div> </div>
<div class="admonitionblock important"> <div class="admonitionblock important">
<table> <table>
@@ -1953,7 +2022,7 @@ If the left variable is defined, the right expression is not evaluated at all.
<p>The <code class="blue">??</code> operator do not change the status of the left variable.</p> <p>The <code class="blue">??</code> operator do not change the status of the left variable.</p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p>The <code class="blue">?=</code> assigns the calculated value of the right expression to the left variable.</p> <p>The <code class="blue">?=</code> assigns the calculated value of the right expression to the variable on the left side.</p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p>The third one, <code class="blue">?!</code>, is the alternate operator. If the variable on the left size is not defined, it returns <em class="blue">nil</em>. Otherwise it returns the result of the expressione on the right side.</p> <p>The third one, <code class="blue">?!</code>, is the alternate operator. If the variable on the left size is not defined, it returns <em class="blue">nil</em>. Otherwise it returns the result of the expressione on the right side.</p>
@@ -1965,7 +2034,7 @@ If the left variable is defined, the right expression is not evaluated at all.
<i class="fa icon-important" title="Important"></i> <i class="fa icon-important" title="Important"></i>
</td> </td>
<td class="content"> <td class="content">
If the left variable is NOT defined, the right expression is not evaluated at all. If the variable <code class="blue">?!</code> is NOT defined, the expression is not evaluated at all.
</td> </td>
</tr> </tr>
</table> </table>
@@ -2371,7 +2440,7 @@ These operators have a high priority, in particular higher than the operator <co
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> <code>=</code> <em>any</em> &#8594; <em>any</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> <code>=</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-center valign-top" colspan="4"><p class="tableblock"><em>See also the table of special allocation operators below</em></p></td> <td class="tableblock halign-center valign-top" colspan="4"><p class="tableblock"><em>See also the table of special assignment operators below</em></p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
@@ -2381,6 +2450,40 @@ These operators have a high priority, in particular higher than the operator <co
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>but</code> <em>any</em> &#8594; <em>any</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>but</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>ITER-OP</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">digest</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-digesting</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>digest</code> <em>expr</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">filter</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-filtering</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>filter</code> <em>expr</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">groupby</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict-grouping</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>groupby</code> <em>key-expr</em> &#8594; <em>dict</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">cat</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-concatenation</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> `cat ` <em>iterable</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">map</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-mapping</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>map</code> <em>-expr</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" colspan="4"><p class="tableblock"><em>See iterators section for examples</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>RANGE</strong></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><strong>RANGE</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">:</code></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">:</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
@@ -2536,14 +2639,14 @@ short for<br>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p><code>&gt;&gt;&gt;</code> <em class="gray">// Required and optional parameters</em><br> <p><code>&gt;&gt;&gt;</code> <em class="gray">// Required and optional parameters</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">measure = func(value, unit="meter"){ value + " " + unit + (value &gt; 1) ? [true] {"s"} :: {""}}</code><br> <code>&gt;&gt;&gt;</code> <code class="blue">measure = func(value, unit="meter"){ value + " " + unit + (value &gt; 1) ? {"s"} :: {""}}</code><br>
<code class="green">measure(value, unit="meter"):any{}</code></p> <code class="green">measure(value, unit="meter"):any{}</code></p>
</div> </div>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_golang_function_definition"><a class="anchor" href="#_golang_function_definition"></a><a class="link" href="#_golang_function_definition">6.2. <em>Golang</em> function definition</a></h3> <h3 id="_golang_function_definition"><a class="anchor" href="#_golang_function_definition"></a><a class="link" href="#_golang_function_definition">6.2. <em>Golang</em> function definition</a></h3>
<div class="paragraph"> <div class="paragraph">
<p>Description of how to define Golang functions and how to bind them to <em>Expr</em> are topics covered in another document that I&#8217;ll write, one day, maybe.</p> <p>Description of how to define Golang functions and how to bind them to <em>Expr</em> are topics covered in another documents that I&#8217;ll write, one day, maybe.</p>
</div> </div>
</div> </div>
<div class="sect2"> <div class="sect2">
@@ -2642,7 +2745,7 @@ short for<br>
<div class="title">Example</div> <div class="title">Example</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">f = func() { @x = 3; x = 5 }</code> <em class="gray">// f() declares two <strong>different</strong> local variables: <code>@x</code> and <code>x</code></em><br> <p><code>&gt;&gt;&gt;</code> <code class="blue">f = func() { @x = 3; x = 5 }</code> <em class="gray">// f() declares two <strong>different</strong> local variables: <code>@x</code> and <code>x</code></em><br>
<code class="green">f():any{}</code><br> <code class="green">f():any{}</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">f()</code> <em class="gray">// The multi-expression (two) in f() is calculated and the last result is returned</em><br> <code>&gt;&gt;&gt;</code> <code class="blue">f()</code> <em class="gray">// The multi-expression (two expressions) in f() is calculated and the last result is returned</em><br>
<code class="green">5</code><br> <code class="green">5</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">x</code> <em class="gray">// The <code>x</code> variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the <code>@x</code> variable, local to f() after its termnation.</em><br> <code>&gt;&gt;&gt;</code> <code class="blue">x</code> <em class="gray">// The <code>x</code> variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the <code>@x</code> variable, local to f() after its termnation.</em><br>
<code class="green">3</code></p> <code class="green">3</code></p>
@@ -2727,7 +2830,7 @@ The clone modifier <code class="blue">@</code> does not make a variable a refere
<div class="title">Example 16. Builtin activation syntax</div> <div class="title">Example 16. Builtin activation syntax</div>
<div class="content"> <div class="content">
<div class="paragraph"> <div class="paragraph">
<p><strong><em>builtin-activation</em></strong> = <code class="blue">BUILTIN</code> (<em>builtin-name</em> | <em>list-of-builtin-names</em>)<br> <p><strong><em>builtin-activation</em></strong> = <code class="blue">BUILTIN</code> (<em>builtin-name</em> | <em>list-of-builtin-names</em> | <strong>"*"</strong>)<br>
<em>builtin-name</em> = <em>string</em><br> <em>builtin-name</em> = <em>string</em><br>
<em>list-of-builtin-names</em> = <strong>[</strong> <em>string</em> { "<strong>,</strong>" <em>string</em> } <strong>]</strong></p> <em>list-of-builtin-names</em> = <strong>[</strong> <em>string</em> { "<strong>,</strong>" <em>string</em> } <strong>]</strong></p>
</div> </div>
@@ -2815,9 +2918,15 @@ To avoid the need to activate builtin modules one by one, it is possible to acti
<div class="title">Other functions</div> <div class="title">Other functions</div>
<ul> <ul>
<li> <li>
<p><a href="#_char">char()</a></p>
</li>
<li>
<p><a href="#_eval">eval()</a></p> <p><a href="#_eval">eval()</a></p>
</li> </li>
<li> <li>
<p><a href="#_set">set()</a></p>
</li>
<li>
<p><a href="#_var">var()</a></p> <p><a href="#_var">var()</a></p>
</li> </li>
</ul> </ul>
@@ -2833,7 +2942,9 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is boolean, fal
<p><code>&gt;&gt;&gt;</code> <code class="blue">isBool(true)</code><br> <p><code>&gt;&gt;&gt;</code> <code class="blue">isBool(true)</code><br>
<code class="green">true</code><br> <code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isBool(3==2)</code><br> <code>&gt;&gt;&gt;</code> <code class="blue">isBool(3==2)</code><br>
<code class="green">true</code></p> <code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isBool(3 + 2)</code><br>
<code class="green">false</code></p>
</div> </div>
</div> </div>
<div class="sect4"> <div class="sect4">
@@ -2938,7 +3049,7 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is fraction or
<h5 id="_isstring"><a class="anchor" href="#_isstring"></a><a class="link" href="#_isstring">isString()</a></h5> <h5 id="_isstring"><a class="anchor" href="#_isstring"></a><a class="link" href="#_isstring">isString()</a></h5>
<div class="paragraph"> <div class="paragraph">
<p>Syntax: <code>isString(&lt;expr&gt;) &#8594; bool</code><br> <p>Syntax: <code>isString(&lt;expr&gt;) &#8594; bool</code><br>
Returns a boolean value , false otherwise.</p> Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is string, false otherwise.</p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<div class="title">Examples</div> <div class="title">Examples</div>
@@ -2954,7 +3065,7 @@ Returns a boolean value , false otherwise.</p>
<h5 id="_bool"><a class="anchor" href="#_bool"></a><a class="link" href="#_bool">bool()</a></h5> <h5 id="_bool"><a class="anchor" href="#_bool"></a><a class="link" href="#_bool">bool()</a></h5>
<div class="paragraph"> <div class="paragraph">
<p>Syntax: <code>bool(&lt;expr&gt;) &#8594; bool</code><br> <p>Syntax: <code>bool(&lt;expr&gt;) &#8594; bool</code><br>
Returns a <em>boolean</em> value consisent to the value of the expression.</p> Returns a <em>boolean</em> value consisent with the value of the expression.</p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<div class="title">Examples</div> <div class="title">Examples</div>
@@ -3077,6 +3188,20 @@ Returns a <em>fraction</em> value consistent with the value of the expression.</
</div> </div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_char"><a class="anchor" href="#_char"></a><a class="link" href="#_char">char()</a></h5>
<div class="paragraph">
<p>Syntax: <code>char(&lt;intexpr&gt;) &#8594; string</code><br>
Returns the character whose ASCII (soon Unicode too) code point is specified by the integer expression.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">char(65)</code><br>
<code class="green">"A"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">char(97)</code><br>
<code class="green">"a"</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_eval"><a class="anchor" href="#_eval"></a><a class="link" href="#_eval">eval()</a></h5> <h5 id="_eval"><a class="anchor" href="#_eval"></a><a class="link" href="#_eval">eval()</a></h5>
<div class="paragraph"> <div class="paragraph">
<p>Syntax: <code>eval(&lt;string-expr&gt;) &#8594; any</code><br> <p>Syntax: <code>eval(&lt;string-expr&gt;) &#8594; any</code><br>
@@ -3096,13 +3221,13 @@ Computes and returns the value of the <span class="underline">string</span> expr
<code>&#160;&#160;&#160;&#160;var(&lt;string-expr&gt;) &#8594; any</code></p> <code>&#160;&#160;&#160;&#160;var(&lt;string-expr&gt;) &#8594; any</code></p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p>This function allows you to define variables whose names must include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.</p> <p>This function allows you to define variables whose names can include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.</p>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<div class="title">Examples</div> <div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">var("$x", 3+9)</code><br> <p><code>&gt;&gt;&gt;</code> <code class="blue">var("$x", 3+9)</code><br>
<code class="green">12</code><br> <code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("$x")</code><br> <code>&gt;&gt;&gt;</code> <code class="blue">var("$"+"x")</code><br>
<code class="green">12</code><br> <code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("gain%", var("$x"))</code><br> <code>&gt;&gt;&gt;</code> <code class="blue">var("gain%", var("$x"))</code><br>
<code class="green">12</code><br> <code class="green">12</code><br>
@@ -3110,9 +3235,32 @@ Computes and returns the value of the <span class="underline">string</span> expr
<code class="green">13</code></p> <code class="green">13</code></p>
</div> </div>
</div> </div>
<div class="sect4">
<h5 id="_set"><a class="anchor" href="#_set"></a><a class="link" href="#_set">set</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;set(&lt;string-expr&gt;, &lt;expr&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>This function allows you to set the value of a variable whose name can include special characters. The first parameter is the name of the variable and the second parameter is the new value to assign to that variable.</p>
</div>
<div class="paragraph">
<p>It is equivalent to the first form of the var() function, but it is more explicit about the intent of changing the value of an existing variable.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">set("$x", 100)</code><br>
<code class="green">100</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("$x")</code><br>
<code class="green">100</code><br></p>
</div>
</div>
</div> </div>
<div class="sect3"> <div class="sect3">
<h4 id="_module_fmt"><a class="anchor" href="#_module_fmt"></a><a class="link" href="#_module_fmt">7.1.2. Module "fmt"</a></h4> <h4 id="_module_fmt"><a class="anchor" href="#_module_fmt"></a><a class="link" href="#_module_fmt">7.1.2. Module "fmt"</a></h4>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
<div class="sect4"> <div class="sect4">
<h5 id="_print"><a class="anchor" href="#_print"></a><a class="link" href="#_print">print()</a></h5> <h5 id="_print"><a class="anchor" href="#_print"></a><a class="link" href="#_print">print()</a></h5>
@@ -3124,10 +3272,18 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div> </div>
<div class="sect3"> <div class="sect3">
<h4 id="_module_import"><a class="anchor" href="#_module_import"></a><a class="link" href="#_module_import">7.1.3. Module "import"</a></h4> <h4 id="_module_import"><a class="anchor" href="#_module_import"></a><a class="link" href="#_module_import">7.1.3. Module "import"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "import"</code></p>
</div>
<div class="sect4"> <div class="sect4">
<h5 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import"><em>import()</em></a></h5> <h5 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import"><em>import()</em></a></h5>
<div class="paragraph"> <div class="paragraph">
<p><em class="blue">import(<span class="grey">&lt;source-file&gt;</span>)</em>&#8201;&#8212;&#8201;loads the multi-expression contained in the specified source and returns its value.</p> <p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;import(&lt;source-file&gt;)</code></p>
</div>
<div class="paragraph">
<p>Loads the multi-expression contained in the specified source and returns its value.</p>
</div> </div>
</div> </div>
<div class="sect4"> <div class="sect4">
@@ -3137,16 +3293,40 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div> </div>
<div class="sect3"> <div class="sect3">
<h4 id="_module_iterator"><a class="anchor" href="#_module_iterator"></a><a class="link" href="#_module_iterator">7.1.4. Module "iterator"</a></h4> <h4 id="_module_iterator"><a class="anchor" href="#_module_iterator"></a><a class="link" href="#_module_iterator">7.1.4. Module "iterator"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "iterator"</code></p>
</div>
<div class="sect4"> <div class="sect4">
<h5 id="_run"><a class="anchor" href="#_run"></a><a class="link" href="#_run">run()</a></h5> <h5 id="_run"><a class="anchor" href="#_run"></a><a class="link" href="#_run">run()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;run(&lt;iterator&gt;, &lt;operator&gt;, &lt;vars&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>Iterates over the specified iterator and applies the specified operator to the current value of the iterator.</p>
</div>
</div> </div>
</div> </div>
<div class="sect3"> <div class="sect3">
<h4 id="_module_math_arith"><a class="anchor" href="#_module_math_arith"></a><a class="link" href="#_module_math_arith">7.1.5. Module "math.arith"</a></h4> <h4 id="_module_math_arith"><a class="anchor" href="#_module_math_arith"></a><a class="link" href="#_module_math_arith">7.1.5. Module "math.arith"</a></h4>
<div class="paragraph"> <div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "math.arith"</code></p>
</div>
<div class="paragraph">
<p>Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.</p> <p>Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.</p>
</div> </div>
<div class="ulist">
<ul>
<li>
<p><a href="#_add">add()</a></p>
</li>
<li>
<p><a href="#_mul">mul()</a></p>
</li>
</ul>
</div>
<div class="sect4"> <div class="sect4">
<h5 id="_add"><a class="anchor" href="#_add"></a><a class="link" href="#_add">add()</a></h5> <h5 id="_add"><a class="anchor" href="#_add"></a><a class="link" href="#_add">add()</a></h5>
<div class="paragraph"> <div class="paragraph">
@@ -3202,37 +3382,213 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div> </div>
<div class="sect3"> <div class="sect3">
<h4 id="_module_os_file"><a class="anchor" href="#_module_os_file"></a><a class="link" href="#_module_os_file">7.1.6. Module "os.file"</a></h4> <h4 id="_module_os_file"><a class="anchor" href="#_module_os_file"></a><a class="link" href="#_module_os_file">7.1.6. Module "os.file"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "os.file"</code></p>
</div>
<div class="paragraph">
<p>The "os.file" module provides functions for working with files.</p>
</div>
<div class="ulist">
<div class="title">File related functions</div>
<ul>
<li>
<p><a href="#_fileOpen">fileOpen()</a></p>
</li>
<li>
<p><a href="#_fileAppend">fileAppend()</a></p>
</li>
<li>
<p><a href="#_fileCreate">fileCreate()</a></p>
</li>
<li>
<p><a href="#_fileClose">fileClose()</a></p>
</li>
<li>
<p><a href="#_fileWriteText">fileWriteText()</a></p>
</li>
<li>
<p><a href="#_fileReadText">fileReadText()</a></p>
</li>
<li>
<p><a href="#_fileReadTextAll">fileReadTextAll()</a></p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Iterator functions for files</div>
<ul>
<li>
<p><a href="#_fileByteIterator">fileByteIterator()</a></p>
</li>
<li>
<p><a href="#_fileLineIterator">fileLineIterator()</a></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>More functions will be added in the future.</p>
</div>
<hr>
<div class="sect4"> <div class="sect4">
<h5 id="_fileopen"><a class="anchor" href="#_fileopen"></a><a class="link" href="#_fileopen">fileOpen()</a></h5> <h5 id="_fileopen"><a class="anchor" href="#_fileopen"></a><a class="link" href="#_fileopen">fileOpen()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileOpen(&lt;file-path&gt;) &#8594; file-handle</code></p>
</div>
<div class="paragraph">
<p>Returns a file handle for the specified file path. The file is opened in read-write mode. If the file does not exist, it is created.</p>
</div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_fileappend"><a class="anchor" href="#_fileappend"></a><a class="link" href="#_fileappend">fileAppend()</a></h5> <h5 id="_fileappend"><a class="anchor" href="#_fileappend"></a><a class="link" href="#_fileappend">fileAppend()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileAppend(&lt;file-path&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>Like <a href="#_fileCreate">fileCreate()</a> but write operations happen at the end of the file.</p>
</div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_filecreate"><a class="anchor" href="#_filecreate"></a><a class="link" href="#_filecreate">fileCreate()</a></h5> <h5 id="_filecreate"><a class="anchor" href="#_filecreate"></a><a class="link" href="#_filecreate">fileCreate()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileCreate(&lt;file-path&gt;) &#8594; file-handle</code></p>
</div>
<div class="paragraph">
<p>Creates or truncates the named <em>&lt;file-path&gt;</em>. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0o666 (before umask). The associated file descriptor has mode [O_RDWR]. The directory containing the file must already exist.</p>
</div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_fileclose"><a class="anchor" href="#_fileclose"></a><a class="link" href="#_fileclose">fileClose()</a></h5> <h5 id="_fileclose"><a class="anchor" href="#_fileclose"></a><a class="link" href="#_fileclose">fileClose()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_filewritetext"><a class="anchor" href="#_filewritetext"></a><a class="link" href="#_filewritetext">fileWriteText()</a></h5> <h5 id="_filewritetext"><a class="anchor" href="#_filewritetext"></a><a class="link" href="#_filewritetext">fileWriteText()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_filereadtext"><a class="anchor" href="#_filereadtext"></a><a class="link" href="#_filereadtext">fileReadText()</a></h5> <h5 id="_filereadtext"><a class="anchor" href="#_filereadtext"></a><a class="link" href="#_filereadtext">fileReadText()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div> </div>
<div class="sect4"> <div class="sect4">
<h5 id="_filereadtextall"><a class="anchor" href="#_filereadtextall"></a><a class="link" href="#_filereadtextall">fileReadTextAll()</a></h5> <h5 id="_filereadtextall"><a class="anchor" href="#_filereadtextall"></a><a class="link" href="#_filereadtextall">fileReadTextAll()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div>
<div class="sect4">
<h5 id="_filebyteiterator"><a class="anchor" href="#_filebyteiterator"></a><a class="link" href="#_filebyteiterator">fileByteIterator()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileByteIterator(handle-or-path) &#8594; iterator</code></p>
</div>
<div class="paragraph">
<p>Returns an iterator that produces the bytes of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; builtin "os.file"<br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fileByteIterator("test-file.txt") map $_</code><br>
<code class="green">[
117,
110,
111,
10,
100,
117,
101,
10
]</code></p>
</div>
<div class="paragraph">
<p>&gt;&gt;&gt; builtin ["os.file", "string"]<br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fileByteIterator("test-file.txt") map char($_)</code><br>
<code class="green">[
"u",
"n",
"o",
"
",
"d",
"u",
"e",
"
",
]</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_filelineiterator"><a class="anchor" href="#_filelineiterator"></a><a class="link" href="#_filelineiterator">fileLineIterator()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileLineIterator(handle-or-path) &#8594; iterator</code></p>
</div>
<div class="paragraph">
<p>Returns an iterator that produces the lines of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">builtin "os.file"</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fileLineIterator("test-file.txt") map $_</code><br>
<code class="green">[
"uno",
"due"
]</code></p>
</div>
</div> </div>
</div> </div>
<div class="sect3"> <div class="sect3">
<h4 id="_module_string"><a class="anchor" href="#_module_string"></a><a class="link" href="#_module_string">7.1.7. Module "string"</a></h4> <h4 id="_module_string"><a class="anchor" href="#_module_string"></a><a class="link" href="#_module_string">7.1.7. Module "string"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "string"</code></p>
</div>
<div class="paragraph">
<p>This module provides functions for working with strings.</p>
</div>
<div class="paragraph">
<p>Currently available functions:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#_strJoin">strJoin()</a></p>
</li>
<li>
<p><a href="#_strSub">strSub()</a></p>
</li>
<li>
<p><a href="#_strSplit">strSplit()</a></p>
</li>
<li>
<p><a href="#_strTrim">strTrim()</a></p>
</li>
<li>
<p><a href="#_strStartsWith">strStartsWith()</a></p>
</li>
<li>
<p><a href="#_strEndsWith">strEndsWith()</a></p>
</li>
<li>
<p><a href="#_strUpper">strUpper()</a></p>
</li>
<li>
<p><a href="#_strLower">strLower()</a></p>
</li>
</ul>
</div>
<hr>
<div class="sect4"> <div class="sect4">
<h5 id="_strjoin"><a class="anchor" href="#_strjoin"></a><a class="link" href="#_strjoin">strJoin()</a></h5> <h5 id="_strjoin"><a class="anchor" href="#_strjoin"></a><a class="link" href="#_strjoin">strJoin()</a></h5>
<div class="paragraph"> <div class="paragraph">
@@ -3589,7 +3945,81 @@ Iterators built on custom data-sources can provide additional named operators, d
</div> </div>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_iterator_over_custom_data_sources"><a class="anchor" href="#_iterator_over_custom_data_sources"></a><a class="link" href="#_iterator_over_custom_data_sources">8.2. Iterator over custom data-sources</a></h3> <h3 id="_infixed_operators_on_iterators"><a class="anchor" href="#_infixed_operators_on_iterators"></a><a class="link" href="#_infixed_operators_on_iterators">8.2. Infixed operators on iterators</a></h3>
<div class="paragraph">
<p>There are also some infixed operators that can be used with iterators. They are defined as follows.</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#_cat">cat operator</a></p>
</li>
<li>
<p><a href="#_diget">digest operator</a></p>
</li>
<li>
<p><a href="#_filter">filter operator</a></p>
</li>
<li>
<p><a href="#_groupby">groupby operator</a></p>
</li>
<li>
<p><a href="#_map">map operator</a></p>
</li>
</ul>
</div>
<hr>
<div class="sect3">
<h4 id="_cat_operator"><a class="anchor" href="#_cat_operator"></a><a class="link" href="#_cat_operator">8.2.1. <code class="blue">cat</code> operator</a></h4>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;iterable&gt; cat &lt;iterable&gt; &#8594; &lt;iterator&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">cat</code> operator takes two iterators or iterables and returns a new iterator that produces the elements of the first iterator followed by the elements of the second iterator.</p>
</div>
</div>
<div class="sect3">
<h4 id="_filter_operator"><a class="anchor" href="#_filter_operator"></a><a class="link" href="#_filter_operator">8.2.2. <code class="blue">filter</code> operator</a></h4>
<div class="paragraph">
<div class="title">Examples</div>
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;iterable&gt; filter &lt;expr&gt; &#8594; &lt;iterator&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">filter</code> applies a boolean expression to each element of the iterator and returns a list of the elements for which the expression evaluates to true.</p>
</div>
</div>
<div class="sect3">
<h4 id="_groupby_operator"><a class="anchor" href="#_groupby_operator"></a><a class="link" href="#_groupby_operator">8.2.3. <code class="blue">groupby</code> operator</a></h4>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;dict&gt; groupby &lt;key&gt; &#8594; &lt;dict&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">groupby</code> operator groups the elements of the iterator based on the value of a specified expression and returns a dictionary where the keys are the group values and the values are lists of the elements in each group.</p>
</div>
</div>
<div class="sect3">
<h4 id="_map_operator"><a class="anchor" href="#_map_operator"></a><a class="link" href="#_map_operator">8.2.4. <code class="blue">map</code> operator</a></h4>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;iterable&gt; map &lt;expr&gt; &#8594; &lt;list&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">map</code> operator iterates over the elements of the iterator and evaluates the expressions provided on the right side for each element. Its result is a list of the computed values of the that expression. The current element of the iterator is available in the expression as the variable <code>$_</code>.</p>
</div>
<div class="paragraph">
<div class="title">Example: using the <code class="blue">map</code> operator</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">it = $(["one", "two", "three"])</code><br>
<code class="green">$(#3)</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">it map $_ + "!"</code><br>
<code class="green">["one!", "two!", "three!"]</code></p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_iterator_over_custom_data_sources"><a class="anchor" href="#_iterator_over_custom_data_sources"></a><a class="link" href="#_iterator_over_custom_data_sources">8.3. Iterator over custom data-sources</a></h3>
<div class="paragraph"> <div class="paragraph">
<p>It is possible to create iterators over custom data-sources by defining a dictionary that has the key <code>next</code> whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.</p> <p>It is possible to create iterators over custom data-sources by defining a dictionary that has the key <code>next</code> whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.</p>
</div> </div>
@@ -3610,7 +4040,7 @@ Iterators built on custom data-sources can provide additional named operators, d
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2026-04-21 06:35:14 +0200 Last updated 2026-05-12 16:25:27 +0200
</div> </div>
</div> </div>
</body> </body>
+322
View File
@@ -0,0 +1,322 @@
<!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
@@ -0,0 +1,46 @@
// 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
@@ -0,0 +1,83 @@
// 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
@@ -0,0 +1,62 @@
// 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
@@ -0,0 +1,69 @@
// 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
@@ -0,0 +1,82 @@
// 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
}
-388
View File
@@ -1,388 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package expr
import (
"fmt"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// ---- Function template
// type FuncTemplate func(ctx expr.ExprContext, name string, args map[string]any) (result any, err error)
// ---- Common functor definition
type BaseFunctor struct {
info kern.ExprFunc
}
func (functor *BaseFunctor) ToString(opt kern.FmtOpt) (s string) {
if functor.info != nil {
s = functor.info.ToString(opt)
} else {
s = "func(){}"
}
return s
}
func (functor *BaseFunctor) GetParams() (params []kern.ExprFuncParam) {
if functor.info != nil {
return functor.info.Params()
} else {
return []kern.ExprFuncParam{}
}
}
func (functor *BaseFunctor) SetFunc(info kern.ExprFunc) {
functor.info = info
}
func (functor *BaseFunctor) GetFunc() kern.ExprFunc {
return functor.info
}
func (functor *BaseFunctor) GetDefinitionContext() kern.ExprContext {
return nil
}
// ---- Function Parameters
type paramFlags uint16
const (
PfDefault paramFlags = 1 << iota
PfOptional
PfRepeat
)
type funcParamInfo struct {
name string
flags paramFlags
defaultValue any
}
func NewFuncParam(name string) kern.ExprFuncParam {
return &funcParamInfo{name: name}
}
func NewFuncParamFlag(name string, flags paramFlags) kern.ExprFuncParam {
return &funcParamInfo{name: name, flags: flags}
}
func NewFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return kern.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
}
// --- Functions
// funcInfo implements expr.ExprFunc
type funcInfo struct {
name string
minArgs int
maxArgs int
functor kern.Functor
formalParams []kern.ExprFuncParam
returnType string
}
func newFuncInfo(name string, functor kern.Functor, returnType string, params []kern.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() []kern.ExprFuncParam {
return info.formalParams
}
func (info *funcInfo) ReturnType() string {
return info.returnType
}
func (info *funcInfo) ToString(opt kern.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(kern.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() kern.Functor {
return info.functor
}
func (info *funcInfo) AllocContext(parentCtx kern.ExprContext) (ctx kern.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) kern.ExprFuncParam {
for _, spec := range info.formalParams {
if spec.Name() == paramName {
return spec
}
}
return nil
}
func initActualParams(ctx kern.ExprContext, info kern.ExprFunc, callTerm *scan.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
}
for i, tree := range callTerm.Children {
var paramValue any
paramCtx := ctx.Clone()
if paramValue, err = tree.Compute(paramCtx); err != nil {
break
}
if paramName, namedParam := kern.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, len(callTerm.Children)-i)
varArgs = append(varArgs, paramValue)
varName = spec.Name()
} else {
actualParams[spec.Name()] = paramValue
}
} else {
err = kern.ErrTooManyParams(info.Name(), len(formalParams), len(callTerm.Children))
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 = kern.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 = kern.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 CallFunctionByTerm(parentCtx expr.ExprContext, name string, callTerm *term) (result any, err error) {
// var actualParams map[string]any
// if info, exists := GetFuncInfo(parentCtx, 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 expr.ExprContext, name string, args []any) (result any, err error) {
// var actualParams map[string]any
// if info, exists := GetFuncInfo(parentCtx, 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 expr.ExprContext, name string, actualParams map[string]any) (result any, err error) {
// //var actualParams map[string]any
// if info, exists := GetFuncInfo(parentCtx, 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
// }
+15 -16
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// global-context.go // global-context.go
@@ -9,6 +9,7 @@ import (
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types/boolean"
) )
//var globalCtx *SimpleStore //var globalCtx *SimpleStore
@@ -42,12 +43,16 @@ func ImportInContextByGlobPattern(ctx kern.ExprContext, pattern string) (count i
return 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) { func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil { if globalCtx := ctx.GetGlobal(); globalCtx != nil {
if !strings.HasPrefix(name, "_") { name = fixCtrlVar(name)
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name) currentValue, _ = globalCtx.GetVar(name)
globalCtx.SetVar(name, newValue) globalCtx.SetVar(name, newValue)
} }
@@ -56,19 +61,15 @@ func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValu
func GlobalCtrlGet(ctx kern.ExprContext, name string) (currentValue any) { func GlobalCtrlGet(ctx kern.ExprContext, name string) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil { if globalCtx := ctx.GetGlobal(); globalCtx != nil {
if !strings.HasPrefix(name, "_") { name = fixCtrlVar(name)
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name) currentValue, _ = globalCtx.GetVar(name)
} }
return currentValue return currentValue
} }
func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) { func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") { name = fixCtrlVar(name)
name = "_" + name if v, exists := ctx.GetVar(name); exists && boolean.IsBool(v) {
}
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool) currentStatus, _ = v.(bool)
} }
@@ -77,10 +78,8 @@ func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
} }
func CtrlDisable(ctx kern.ExprContext, name string) (currentStatus bool) { func CtrlDisable(ctx kern.ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") { name = fixCtrlVar(name)
name = "_" + name if v, exists := ctx.GetVar(name); exists && boolean.IsBool(v) {
}
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool) currentStatus, _ = v.(bool)
} }
-2
View File
@@ -1,4 +1,2 @@
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= 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= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
//go:build graph //go:build graph
+6 -4
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// helpers.go // helpers.go
@@ -12,6 +12,8 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/float"
"git.portale-stac.it/go-pkg/expr/util" "git.portale-stac.it/go-pkg/expr/util"
) )
@@ -45,14 +47,14 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
functor := kern.NewGolangFunctor(f) functor := kern.NewGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1) // ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc(arg.Name, functor, kern.TypeAny, []kern.ExprFuncParam{
NewFuncParamFlagDef(kern.ParamValue, PfDefault|PfRepeat, 0), kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, 0),
}) })
} else { } else {
err = fmt.Errorf("invalid function specification: %q", arg.Name) err = fmt.Errorf("invalid function specification: %q", arg.Name)
} }
} else if integer, ok := kern.AnyInteger(arg.Value); ok { } else if integer, ok := types.AnyInteger(arg.Value); ok {
ctx.SetVar(arg.Name, integer) ctx.SetVar(arg.Name, integer)
} else if float, ok := kern.AnyFloat(arg.Value); ok { } else if float, ok := float.AnyFloat(arg.Value); ok {
ctx.SetVar(arg.Name, float) ctx.SetVar(arg.Name, float)
} else if _, ok := arg.Value.(string); ok { } else if _, ok := arg.Value.(string); ok {
ctx.SetVar(arg.Name, arg.Value) ctx.SetVar(arg.Name, arg.Value)
+10 -8
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// import-utils.go // import-utils.go
@@ -13,6 +13,7 @@ import (
"strings" "strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types/str"
"git.portale-stac.it/go-pkg/expr/util" "git.portale-stac.it/go-pkg/expr/util"
) )
@@ -22,7 +23,7 @@ const (
) )
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) { func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(kern.IsString(paramValue) /*|| isList(paramValue)*/) { if !(str.IsString(paramValue) /*|| isArray(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, kern.TypeName(paramValue)) err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, kern.TypeName(paramValue))
} }
return return
@@ -89,16 +90,17 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
if dir, err = util.ExpandPath(dir); err != nil { if dir, err = util.ExpandPath(dir); err != nil {
continue continue
} }
if fullPath := path.Join(dir, filename); isFile(fullPath) { fullPath := path.Join(dir, filename)
if isFile(fullPath) {
filePath = fullPath filePath = fullPath
break break
} }
subdir := strings.TrimSuffix(filename, suffix) // subdir := strings.TrimSuffix(filename, suffix)
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) { // if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
filePath = fullPath // filePath = fullPath
break // break
} // }
} }
return return
} }
+43 -26
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// int-iterator.go // int-iterator.go
@@ -10,14 +10,32 @@ import (
"slices" "slices"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/interval"
) )
type IntIterator struct { type IntIterator struct {
count int64 count int64
index int64 index int64
start int64 next int64
stop int64 current any
step int64 start int64
stop int64
step int64
}
func NewIntIteratorFromInterval(p *interval.IntervalType) (it *IntIterator, err error) {
b := p.Begin()
e := p.End()
s := p.Step()
if b > e {
s = -s
}
return NewIntIteratorA(b, e, s)
}
func NewIntIteratorA(args ...any) (it *IntIterator, err error) {
return NewIntIterator(args)
} }
func NewIntIterator(args []any) (it *IntIterator, err error) { func NewIntIterator(args []any) (it *IntIterator, err error) {
@@ -27,16 +45,16 @@ func NewIntIterator(args []any) (it *IntIterator, err error) {
} }
it = &IntIterator{count: 0, index: -1, start: 0, stop: 0, step: 1} it = &IntIterator{count: 0, index: -1, start: 0, stop: 0, step: 1}
if argc >= 1 { if argc >= 1 {
if it.stop, err = kern.ToGoInt64(args[0], "start index"); err != nil { if it.stop, err = types.ToGoInt64(args[0], "start index"); err != nil {
return return
} }
if argc >= 2 { if argc >= 2 {
it.start = it.stop it.start = it.stop
if it.stop, err = kern.ToGoInt64(args[1], "stop index"); err != nil { if it.stop, err = types.ToGoInt64(args[1], "stop index"); err != nil {
return return
} }
if argc >= 3 { if argc >= 3 {
if it.step, err = kern.ToGoInt64(args[2], "step"); err != nil { if it.step, err = types.ToGoInt64(args[2], "step"); err != nil {
return return
} }
} else if it.start > it.stop { } else if it.start > it.stop {
@@ -94,26 +112,23 @@ func (it *IntIterator) CallOperation(name string, args map[string]any) (v any, e
} }
func (it *IntIterator) Current() (item any, err error) { func (it *IntIterator) Current() (item any, err error) {
if it.start <= it.stop { return it.current, nil
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) { func (it *IntIterator) Next() (item any, err error) {
it.index += it.step item = it.next
if item, err = it.Current(); err != io.EOF { if it.step > 0 {
if it.next+it.step > it.stop {
err = io.EOF
}
} else if it.next+it.step < it.stop {
err = io.EOF
}
if err == nil {
it.index++
it.count++ it.count++
it.next += it.step
it.current = item
} }
return return
} }
@@ -127,7 +142,9 @@ func (it *IntIterator) Count() int64 {
} }
func (it *IntIterator) Reset() error { func (it *IntIterator) Reset() error {
it.index = it.start - it.step it.index = -1
it.next = it.start
it.current = nil
it.count = 0 it.count = 0
return nil return nil
} }
+34 -5
View File
@@ -1,29 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// iter-factory.go // iter-factory.go
package expr package expr
import ( import (
"slices"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/list"
) )
func NewIterator(value any) (it kern.Iterator, err error) { 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 { if value == nil {
return NewArrayIterator([]any{}), nil return NewArrayIterator([]any{}), nil
} }
switch v := value.(type) { switch v := value.(type) {
case *kern.ListType: case *array.ArrayType:
it = NewListIterator(v, nil) it = NewListIterator(v, nil)
case *kern.DictType: case *list.LinkedList:
it = NewLinkedListIterator(v, nil)
case *dict.DictType:
it, err = NewDictIterator(v, nil) it, err = NewDictIterator(v, nil)
case []any: case []any:
it = NewArrayIterator(v) it = NewArrayIterator(v)
case kern.Iterator: case kern.Iterator:
it = v // var exprs []*scan.Term
it, err = NewIterIter(v, ctx, ops)
default: default:
it = NewArrayIterator([]any{value}) it = NewArrayIterator([]any{value})
} }
return 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
@@ -0,0 +1,135 @@
// 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
}
+6 -6
View File
@@ -1,23 +1,23 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// bind-go-function.go // bind-go-function.go
package kern package kern
// ---- Linking with Go functions // ---- Linking with Go functions
type golangFunctor struct { type GolangFunctor struct {
BaseFunctor BaseFunctor
f FuncTemplate f FuncTemplate
} }
func NewGolangFunctor(f FuncTemplate) *golangFunctor { func NewGolangFunctor(f FuncTemplate) *GolangFunctor {
return &golangFunctor{f: f} return &GolangFunctor{f: f}
} }
func (functor *golangFunctor) TypeName() string { func (functor *GolangFunctor) TypeName() string {
return "GoFunctor" return "GoFunctor"
} }
func (functor *golangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) { func (functor *GolangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return functor.f(ctx, name, args) return functor.f(ctx, name, args)
} }
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// cloner.go
package kern
type Cloner interface {
Clone() Cloner
}
func IsCloner(v any) (ok bool) {
_, ok = v.(Cloner)
return
}
func Clone(a any) (clone any) {
if IsCloner(a) {
clone = a.(Cloner).Clone()
} else {
clone = a
}
return
}
+5 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// common-errors.go // common-errors.go
@@ -84,6 +84,10 @@ func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName) 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 // --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm Term) error { func ErrLeftOperandMustBeVariable(leftTerm, opTerm Term) error {
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// common-params.go // common-params.go
+4 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// common-type-names.go // common-type-names.go
@@ -6,6 +6,7 @@ package kern
const ( const (
TypeAny = "any" TypeAny = "any"
TypeArray = "array"
TypeNil = "nil" TypeNil = "nil"
TypeBoolean = "boolean" TypeBoolean = "boolean"
TypeFloat = "float" TypeFloat = "float"
@@ -15,9 +16,10 @@ const (
TypeItem = "item" TypeItem = "item"
TypeIterator = "iterator" TypeIterator = "iterator"
TypeNumber = "number" TypeNumber = "number"
TypePair = "pair" TypeIntPair = "int-pair"
TypeString = "string" TypeString = "string"
TypeDict = "dict" TypeDict = "dict"
TypeListOf = "list-of-" TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings" TypeListOfStrings = "list-of-strings"
TypeLinkedList = "linked-list"
) )
-37
View File
@@ -1,37 +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 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
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// consts.go
package kern
const MaxUint64Allowed = uint64(9_223_372_036_854_775_807)
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// context-helpers.go // context-helpers.go
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// control.go // control.go
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// equaler.go
package kern
type Equaler interface {
EqualTo(other Equaler) bool
}
func IsEqualer(v any) (ok bool) {
_, ok = v.(Equaler)
return
}
func Equal(a, b any) (equal bool) {
if IsEqualer(a) && IsEqualer(b) {
equal = a.(Equaler).EqualTo(b.(Equaler))
} else {
equal = a == b
}
return
}
+4 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// expr-context.go // expr-context.go
@@ -28,4 +28,7 @@ type ExprContext interface {
Call(name string, args map[string]any) (result any, err error) Call(name string, args map[string]any) (result any, err error)
RegisterFuncInfo(info ExprFunc) RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error) RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error)
ToDict() (dict any) // must be a *DictType
ToString(opt FmtOpt) string
} }
+6 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// expr-function.go // expr-function.go
@@ -37,3 +37,8 @@ type ExprFunc interface {
PrepareCall(name string, actualParams map[string]any) (err error) PrepareCall(name string, actualParams map[string]any) (err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext) AllocContext(parentCtx ExprContext) (ctx ExprContext)
} }
func IsFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// expr.go // expr.go
+19 -23
View File
@@ -1,10 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// formatter.go // formatter.go
package kern package kern
import "fmt" import (
"fmt"
"strings"
)
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
@@ -18,6 +21,10 @@ const (
Base16 Base16
) )
type Formatter interface {
ToString(options FmtOpt) string
}
const ( const (
TruncateEllipsis = "(...)" TruncateEllipsis = "(...)"
MinTruncateSize = 10 MinTruncateSize = 10
@@ -42,8 +49,16 @@ func GetFormatIndent(opt FmtOpt) int {
return int(opt >> 16) return int(opt >> 16)
} }
type Formatter interface { func Format(sb *strings.Builder, item any, opt FmtOpt) {
ToString(options FmtOpt) string 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) { func GetFormatted(v any, opt FmtOpt) (text string) {
@@ -58,22 +73,3 @@ func GetFormatted(v any, opt FmtOpt) (text string) {
} }
return 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
}
+164
View File
@@ -0,0 +1,164 @@
// 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
}
+6 -11
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// function.go // function.go
@@ -14,11 +14,6 @@ type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (resul
type DeepFuncTemplate func(a, b any) (eq bool, err error) type DeepFuncTemplate func(a, b any) (eq bool, err error)
func IsFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
// ---- Common functor definition // ---- Common functor definition
type BaseFunctor struct { type BaseFunctor struct {
info ExprFunc info ExprFunc
@@ -54,17 +49,17 @@ func (functor *BaseFunctor) GetDefinitionContext() ExprContext {
} }
// ---- Function Parameters // ---- Function Parameters
type paramFlags uint16 type FuncParamFlags uint16
const ( const (
PfDefault paramFlags = 1 << iota PfDefault FuncParamFlags = 1 << iota
PfOptional PfOptional
PfRepeat PfRepeat
) )
type funcParamInfo struct { type funcParamInfo struct {
name string name string
flags paramFlags flags FuncParamFlags
defaultValue any defaultValue any
} }
@@ -72,11 +67,11 @@ func NewFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name} return &funcParamInfo{name: name}
} }
func NewFuncParamFlag(name string, flags paramFlags) ExprFuncParam { func NewFuncParamFlag(name string, flags FuncParamFlags) ExprFuncParam {
return &funcParamInfo{name: name, flags: flags} return &funcParamInfo{name: name, flags: flags}
} }
func NewFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo { func NewFuncParamFlagDef(name string, flags FuncParamFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue} return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
} }
+44 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// iterator.go // iterator.go
@@ -7,6 +7,7 @@ package kern
import ( import (
// "errors" // "errors"
"fmt" "fmt"
"slices"
) )
// Operator names // Operator names
@@ -50,3 +51,45 @@ func IsIterator(v any) (ok bool) {
_, ok = v.(Iterator) _, ok = v.(Iterator)
return 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
}
-202
View File
@@ -1,202 +0,0 @@
// Copyright (c) 2024 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 (dict *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteByte('[')
if len(*dict) > 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(*dict) {
if i > 0 {
if flags&MultiLine != 0 {
sb.WriteString(",\n")
sb.WriteString(nest)
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(innerOpt))
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
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 (dict *ListType) String() string {
return dict.ToString(0)
}
func (dict *ListType) TypeName() string {
return "list"
}
func (dict *ListType) Contains(t *ListType) (answer bool) {
if len(*dict) >= len(*t) {
answer = true
for _, item := range *t {
if answer = dict.IndexDeepSameCmp(item) >= 0; !answer {
break
}
}
}
return
}
func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls1) == len(ls2) {
answer = true
for index, i1 := range *ls1 {
// if !reflect.DeepEqual(i1, ls2[index]) {
// answer = false
// break
// }
if !Equal(i1, ls2[index]) {
answer = false
break
}
}
}
return
}
func (dict *ListType) IndexDeepSameCmp(target any) (index int) {
var eq bool
var err error
index = -1
for i, item := range *dict {
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 (dict *ListType) SetItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*dict)) {
(*dict)[index] = value
} else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*dict)-1)
}
return
}
func (dict *ListType) AppendItem(value any) {
*dict = append(*dict, value)
}
+13 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// term.go // term.go
@@ -6,6 +6,8 @@ package kern
import ( import (
"fmt" "fmt"
"git.portale-stac.it/go-pkg/expr/sym"
) )
type Term interface { type Term interface {
@@ -15,7 +17,16 @@ type Term interface {
GetChildCount() (count int) GetChildCount() (count int)
GetChild(index int) Term GetChild(index int) Term
GetChildSource(index int) string GetChildSource(index int) string
Compute(ctx ExprContext) (result any, err error) GetLeftChild() (c Term)
GetRightChild() (c Term)
Symbol() (symbol sym.Symbol)
IsSymbol(symbol sym.Symbol) bool
IsOneOf(symbols ...sym.Symbol) bool
IsAssign() bool IsAssign() bool
IsVar() bool
SetSymbol(symbol sym.Symbol)
Compute(ctx ExprContext) (result any, err error)
EvalInfix(ctx ExprContext) (leftValue, rightValue any, err error)
Errorf(template string, args ...any) (err error) Errorf(template string, args ...any) (err error)
ErrIncompatibleTypes(leftValue, rightValue any) error
} }
+26
View File
@@ -0,0 +1,26 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// typer.go
package kern
import "fmt"
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 _, ok := v.(int64); ok {
name = "integer"
} else if _, ok := v.(float64); ok {
name = "float"
} else {
name = fmt.Sprintf("%T", v)
}
return
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// util.go
package kern
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
}
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build darwin //go:build darwin
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// lib-ext-darwin.go // lib-ext-darwin.go
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build linux //go:build linux
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// lib-ext-linux.go // lib-ext-linux.go
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build windows //go:build windows
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// lib-ext-windows.go // lib-ext-windows.go
+103
View File
@@ -0,0 +1,103 @@
// 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"
"git.portale-stac.it/go-pkg/expr/types/list"
)
type LinkedListIterator struct {
a *list.LinkedList
count int64
index int64
current *list.ListNode
}
func NewLinkedListIterator(list *list.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
}
+17 -6
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-dict.go // operand-dict.go
@@ -7,12 +7,16 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/str"
) )
// -------- dict term // -------- dict term
func newDictTerm(args map[any]*scan.Term) *scan.Term { func newDictTerm(args map[any]*scan.Term) *scan.Term {
return &scan.Term{ return &scan.Term{
Tk: *scan.NewValueToken(0, 0, scan.SymDict, "{}", args), Tk: *scan.NewValueToken(0, 0, sym.SymDict, "{}", args),
Parent: nil, Parent: nil,
Children: nil, Children: nil,
Position: scan.PosLeaf, Position: scan.PosLeaf,
@@ -23,14 +27,21 @@ func newDictTerm(args map[any]*scan.Term) *scan.Term {
// -------- dict func // -------- dict func
func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
dict, _ := opTerm.Value().(map[any]*scan.Term) dt, _ := opTerm.Value().(map[any]*scan.Term)
items := make(kern.DictType, len(dict)) items := make(dict.DictType, len(dt))
for key, tree := range dict { for key, tree := range dt {
var param any var param any
if param, err = tree.Compute(ctx); err != nil { if param, err = tree.Compute(ctx); err != nil {
break break
} }
items[key] = param var keyValue any
if keyValue, err = (key.(*scan.Term)).Compute(ctx); err == nil {
if types.IsInteger(keyValue) || str.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 { if err == nil {
v = &items v = &items
+3 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-expr.go // operand-expr.go
@@ -9,11 +9,12 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
// -------- expr term // -------- expr term
func newExprTerm(root *scan.Term) *scan.Term { func newExprTerm(root *scan.Term) *scan.Term {
tk := scan.NewValueToken(root.Tk.Row(), root.Tk.Col(), scan.SymExpression, root.Source(), root) tk := scan.NewValueToken(root.Tk.Row(), root.Tk.Col(), sym.SymExpression, root.Source(), root)
return &scan.Term{ return &scan.Term{
Tk: *tk, Tk: *tk,
Parent: nil, Parent: nil,
+4 -4
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-func.go // operand-func.go
@@ -70,14 +70,14 @@ func evalFuncDef(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
paramList := make([]kern.ExprFuncParam, 0, len(opTerm.Children)) paramList := make([]kern.ExprFuncParam, 0, len(opTerm.Children))
for _, param := range opTerm.Children { for _, param := range opTerm.Children {
var defValue any var defValue any
flags := paramFlags(0) flags := kern.FuncParamFlags(0)
if len(param.Children) > 0 { if len(param.Children) > 0 {
flags |= PfDefault flags |= kern.PfDefault
if defValue, err = param.Children[0].Compute(ctx); err != nil { if defValue, err = param.Children[0].Compute(ctx); err != nil {
return return
} }
} }
info := NewFuncParamFlagDef(param.Source(), flags, defValue) info := kern.NewFuncParamFlagDef(param.Source(), flags, defValue)
paramList = append(paramList, info) paramList = append(paramList, info)
} }
v = newExprFunctor(ast, paramList, ctx) v = newExprFunctor(ast, paramList, ctx)
+58 -15
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-iterator.go // operand-iterator.go
@@ -10,12 +10,17 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/interval"
"git.portale-stac.it/go-pkg/expr/types/list"
) )
// -------- iterator term // -------- iterator term
func newIteratorTerm(tk *scan.Token, args []*scan.Term) *scan.Term { func newIteratorTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
tk.Sym = scan.SymIterator tk.Sym = sym.SymIterator
return &scan.Term{ return &scan.Term{
Tk: *tk, Tk: *tk,
Parent: nil, Parent: nil,
@@ -52,7 +57,7 @@ func evalFirstChild(ctx kern.ExprContext, iteratorTerm *scan.Term) (value any, e
} }
func getDataSourceDict(iteratorTerm *scan.Term, firstChildValue any) (ds map[string]kern.Functor, err error) { func getDataSourceDict(iteratorTerm *scan.Term, firstChildValue any) (ds map[string]kern.Functor, err error) {
if dictAny, ok := firstChildValue.(*kern.DictType); ok { if dictAny, ok := firstChildValue.(*dict.DictType); ok {
requiredFields := []string{kern.NextName} requiredFields := []string{kern.NextName}
fieldsMask := 0b1 fieldsMask := 0b1
foundFields := 0 foundFields := 0
@@ -123,35 +128,73 @@ func evalIterator(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v = dc v = dc
} else { } else {
if dictIt, ok := firstChildValue.(*kern.DictType); ok { if dictIt, ok := firstChildValue.(*dict.DictType); ok {
var args []any var args []any
if args, err = evalSibling(ctx, opTerm.Children, nil); err == nil { if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v, err = NewDictIterator(dictIt, args) v, err = NewDictIterator(dictIt, args)
} }
} else { } else {
err = opTerm.Children[0].Errorf("the data-source must be a dictionary") err = opTerm.Children[0].Errorf("the data-source must be a dictionary")
} }
} }
} else if list, ok := firstChildValue.(*kern.ListType); ok { } else if a, ok := firstChildValue.(*array.ArrayType); ok {
var args []any var args []any
if args, err = evalSibling(ctx, opTerm.Children, nil); err == nil { if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v = NewListIterator(list, args) v = NewListIterator(a, args)
} }
} else if intVal, ok := firstChildValue.(int64); ok { } else if ll, ok := firstChildValue.(*list.LinkedList); ok {
var args []any var args []any
if args, err = evalSibling(ctx, opTerm.Children, intVal); err == nil { if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v, err = NewIntIterator(args) v = NewLinkedListIterator(ll, args)
} }
} else if intervalVal, ok := firstChildValue.(*interval.IntervalType); ok {
v, err = NewIntIteratorFromInterval(intervalVal)
// } 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 { } else {
var list []any var siblings []any
if list, err = evalSibling(ctx, opTerm.Children, firstChildValue); err == nil { if siblings, err = evalSiblings(ctx, opTerm.Children, firstChildValue); err == nil {
v = NewArrayIterator(list) v = NewArrayIterator(siblings)
} }
} }
return return
} }
func evalSibling(ctx kern.ExprContext, terms []*scan.Term, firstChildValue any) (list []any, err error) { // 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)) items := make([]any, 0, len(terms))
for i, tree := range terms { for i, tree := range terms {
var param any var param any
+45
View File
@@ -0,0 +1,45 @@
// 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"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/list"
)
// -------- 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, sym.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) {
listTerm, _ := opTerm.Value().([]*scan.Term)
items := list.NewLinkedList()
for _, tree := range listTerm {
var param any
if param, err = tree.Compute(ctx); err != nil {
break
}
items.PushBack(param)
}
if err == nil {
v = items
}
return
}
+5 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-list.go // operand-list.go
@@ -7,6 +7,8 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
) )
// -------- list term // -------- list term
@@ -16,7 +18,7 @@ func newListTermA(args ...*scan.Term) *scan.Term {
func newListTerm(row, col int, args []*scan.Term) *scan.Term { func newListTerm(row, col int, args []*scan.Term) *scan.Term {
return &scan.Term{ return &scan.Term{
Tk: *scan.NewValueToken(row, col, scan.SymList, "[]", args), Tk: *scan.NewValueToken(row, col, sym.SymList, "[]", args),
Parent: nil, Parent: nil,
Children: nil, Children: nil,
Position: scan.PosLeaf, Position: scan.PosLeaf,
@@ -28,7 +30,7 @@ func newListTerm(row, col int, args []*scan.Term) *scan.Term {
// -------- list func // -------- list func
func evalList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
list, _ := opTerm.Value().([]*scan.Term) list, _ := opTerm.Value().([]*scan.Term)
items := make(kern.ListType, len(list)) items := make(array.ArrayType, len(list))
for i, tree := range list { for i, tree := range list {
var param any var param any
if param, err = tree.Compute(ctx); err != nil { if param, err = tree.Compute(ctx); err != nil {
+19 -7
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-literal.go // operand-literal.go
@@ -7,10 +7,22 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/fract"
) )
// -------- literal term // -------- literal term
func newLiteralTerm(tk *scan.Token) *scan.Term { func newLiteralTerm(tk *scan.Token) *scan.Term {
if tk.Sym == sym.SymFraction {
if v, isString := tk.Value.(string); isString {
var err error
if tk.Value, err = fract.MakeGeneratingFraction(v); err != nil {
return nil
}
}
}
return &scan.Term{ return &scan.Term{
Tk: *tk, Tk: *tk,
Parent: nil, Parent: nil,
@@ -29,10 +41,10 @@ func evalLiteral(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymString, newLiteralTerm) scan.RegisterTermConstructor(sym.SymString, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymInteger, newLiteralTerm) scan.RegisterTermConstructor(sym.SymInteger, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymFloat, newLiteralTerm) scan.RegisterTermConstructor(sym.SymFloat, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymFraction, newLiteralTerm) scan.RegisterTermConstructor(sym.SymFraction, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymBool, newLiteralTerm) scan.RegisterTermConstructor(sym.SymBool, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymKwNil, newLiteralTerm) scan.RegisterTermConstructor(sym.SymKwNil, newLiteralTerm)
} }
+23 -20
View File
@@ -1,15 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-selector-case.go // operand-selector-case.go
package expr package expr
import ( import (
"fmt"
"strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
// -------- selector case term // -------- selector case term
@@ -19,20 +17,20 @@ type selectorCase struct {
caseExpr kern.Expr caseExpr kern.Expr
} }
func (sc *selectorCase) String() string { // func (sc *selectorCase) String() string {
var sb strings.Builder // var sb strings.Builder
if sc.filterList != nil { // if sc.filterList != nil {
sc.filterList.ToString(&sb) // sc.filterList.ToString(&sb)
sb.WriteByte(' ') // sb.WriteByte(' ')
} // }
sb.WriteByte('{') // sb.WriteByte('{')
sb.WriteString(sc.caseExpr.String()) // sb.WriteString(sc.caseExpr.String())
sb.WriteByte('}') // sb.WriteByte('}')
return sb.String() // return sb.String()
} // }
func newSelectorCaseTerm(row, col int, filterList *scan.Term, caseExpr kern.Expr) *scan.Term { func newSelectorCaseTerm(row, col int, filterList *scan.Term, caseExpr kern.Expr) *scan.Term {
tk := scan.NewValueToken(row, col, scan.SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr}) tk := scan.NewValueToken(row, col, sym.SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
return &scan.Term{ return &scan.Term{
Tk: *tk, Tk: *tk,
Parent: nil, Parent: nil,
@@ -45,9 +43,14 @@ func newSelectorCaseTerm(row, col int, filterList *scan.Term, caseExpr kern.Expr
// -------- eval selector case // -------- eval selector case
func evalSelectorCase(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalSelectorCase(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var ok bool v, _ = opTerm.Value().(*selectorCase)
if v, ok = opTerm.Value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", opTerm.Value())
}
return return
} }
// func evalSelectorCase(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// var ok bool
// if v, ok = opTerm.Value().(*selectorCase); !ok {
// err = fmt.Errorf("selector-case expected, got %T", opTerm.Value())
// }
// return
// }
+4 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-var.go // operand-var.go
@@ -9,6 +9,7 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
// -------- variable term // -------- variable term
@@ -21,7 +22,7 @@ func newVarTerm(tk *scan.Token) *scan.Term {
Priority: scan.PriValue, Priority: scan.PriValue,
EvalFunc: evalVar, EvalFunc: evalVar,
} }
t.Tk.Sym = scan.SymVariable t.Tk.Sym = sym.SymVariable
return t return t
} }
@@ -41,5 +42,5 @@ func evalVar(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymIdentifier, newVarTerm) scan.RegisterTermConstructor(sym.SymIdentifier, newVarTerm)
} }
+110 -40
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserightChilded. // All rights reserightChilded.
// operator-assign.go // operator-assign.go
@@ -7,6 +7,10 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/list"
"git.portale-stac.it/go-pkg/expr/util" "git.portale-stac.it/go-pkg/expr/util"
) )
@@ -22,9 +26,29 @@ func newAssignTerm(tk *scan.Token) (inst *scan.Term) {
} }
} }
func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v, err = generalEvalAssign(ctx, opTerm, false)
return
}
func newDeepCopyAssignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriAssign,
EvalFunc: evalDeepCopyAssign,
}
}
func evalDeepCopyAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v, err = generalEvalAssign(ctx, opTerm, true)
return
}
func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term, value any) (err error) { func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term, value any) (err error) {
var collectionValue, keyListValue, keyValue any var collectionValue, keyListValue, keyValue any
var keyList *kern.ListType var keyList *array.ArrayType
var ok bool var ok bool
if collectionValue, err = collectionTerm.Compute(ctx); err != nil { if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
@@ -33,7 +57,7 @@ func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *sca
if keyListValue, err = keyListTerm.Compute(ctx); err != nil { if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
return return
} else if keyList, ok = keyListValue.(*kern.ListType); !ok || len(*keyList) != 1 { } else if keyList, ok = keyListValue.(*array.ArrayType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue)) err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
return return
} }
@@ -43,37 +67,82 @@ func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *sca
} }
switch collection := collectionValue.(type) { switch collection := collectionValue.(type) {
case *kern.ListType: case *array.ArrayType:
if index, ok := keyValue.(int64); ok { if index, ok := keyValue.(int64); ok {
err = collection.SetItem(index, value) err = collection.SetItem(index, value)
} else { } else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue)) err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
} }
case *kern.DictType: case *dict.DictType:
collection.SetItem(keyValue, value) collection.SetItem(keyValue, value)
case *list.LinkedList:
if index, ok := keyValue.(int64); ok {
err = collection.SetItem(index, value)
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
}
default: default:
err = collectionTerm.Errorf("collection expected") err = collectionTerm.Errorf("collection expected")
} }
return return
} }
func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any) (err error) { func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any, deepCopy bool) (err error) {
if leftTerm.Symbol() == scan.SymIndex { if leftTerm.Symbol() == sym.SymIndex {
err = assignCollectionItem(ctx, leftTerm.Children[0], leftTerm.Children[1], v) err = assignCollectionItem(ctx, leftTerm.Children[0], leftTerm.Children[1], v)
} else { } else {
if deepCopy {
v = kern.Clone(v)
}
ctx.UnsafeSetVar(leftTerm.Source(), v) ctx.UnsafeSetVar(leftTerm.Source(), v)
} }
return return
} }
func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalAssignDictItem(ctx kern.ExprContext, dotTerm *scan.Term, valueTerm kern.Term) (v any, err error) {
var ok bool
var dictAny, dotKey any
var dt *dict.DictType
if err = dotTerm.CheckOperands(); err != nil {
return
}
dotLeftTerm := dotTerm.GetChild(0)
if dictAny, err = dotLeftTerm.Compute(ctx); err != nil {
return
}
if dt, ok = dictAny.(*dict.DictType); !ok {
err = dotTerm.Tk.ErrorExpectedGot(dict.DictTypeName)
return
}
dotRightTerm := dotTerm.Children[1]
dotRightSym := dotRightTerm.Symbol()
if dotRightSym == sym.SymVariable || dotRightSym == sym.SymString {
dotKey = util.UnquoteString(dotRightTerm.Source())
} else if dotKey, err = dotRightTerm.Compute(ctx); err != nil {
return
}
if v, err = valueTerm.Compute(ctx); err != nil {
return
}
dt.SetItem(dotKey, v)
return
}
func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (v any, err error) {
if err = opTerm.CheckOperands(); err != nil { if err = opTerm.CheckOperands(); err != nil {
return return
} }
leftTerm := opTerm.Children[0] leftTerm := opTerm.Children[0]
leftSym := leftTerm.Symbol() leftSym := leftTerm.Symbol()
if leftSym != scan.SymVariable && leftSym != scan.SymIndex { if leftSym == sym.SymDot {
return evalAssignDictItem(ctx, opTerm.Children[0], opTerm.GetChild(1))
} else if leftSym != sym.SymVariable && leftSym != sym.SymIndex {
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source()) err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
return return
} }
@@ -82,7 +151,7 @@ func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if v, err = rightChild.Compute(ctx); err == nil { if v, err = rightChild.Compute(ctx); err == nil {
if functor, ok := v.(kern.Functor); ok { if functor, ok := v.(kern.Functor); ok {
if leftSym == scan.SymVariable { if leftSym == sym.SymVariable {
if info := functor.GetFunc(); info != nil { if info := functor.GetFunc(); info != nil {
ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params()) ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok { } else if funcDef, ok := functor.(*exprFunctor); ok {
@@ -93,10 +162,10 @@ func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
err = opTerm.Errorf("unknown function %s()", rightChild.Source()) err = opTerm.Errorf("unknown function %s()", rightChild.Source())
} }
} else { } else {
err = assignValue(ctx, leftTerm, v) err = assignValue(ctx, leftTerm, v, deepCopy)
} }
} else { } else {
err = assignValue(ctx, leftTerm, v) err = assignValue(ctx, leftTerm, v, deepCopy)
} }
} }
if err != nil { if err != nil {
@@ -119,7 +188,7 @@ func newOpAssignTerm(tk *scan.Token) (inst *scan.Term) {
func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term) (value any, err error) { func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term) (value any, err error) {
var collectionValue, keyListValue, keyValue any var collectionValue, keyListValue, keyValue any
var keyList *kern.ListType var keyList *array.ArrayType
var ok bool var ok bool
if collectionValue, err = collectionTerm.Compute(ctx); err != nil { if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
@@ -128,7 +197,7 @@ func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *s
if keyListValue, err = keyListTerm.Compute(ctx); err != nil { if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
return return
} else if keyList, ok = keyListValue.(*kern.ListType); !ok || len(*keyList) != 1 { } else if keyList, ok = keyListValue.(*array.ArrayType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue)) err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
return return
} }
@@ -138,13 +207,13 @@ func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *s
} }
switch collection := collectionValue.(type) { switch collection := collectionValue.(type) {
case *kern.ListType: case *array.ArrayType:
if index, ok := keyValue.(int64); ok { if index, ok := keyValue.(int64); ok {
value = (*collection)[index] value = (*collection)[index]
} else { } else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue)) err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
} }
case *kern.DictType: case *dict.DictType:
value = (*collection)[keyValue] value = (*collection)[keyValue]
default: default:
err = collectionTerm.Errorf("collection expected") err = collectionTerm.Errorf("collection expected")
@@ -153,7 +222,7 @@ func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *s
} }
func getAssignValue(ctx kern.ExprContext, leftTerm *scan.Term) (value any, err error) { func getAssignValue(ctx kern.ExprContext, leftTerm *scan.Term) (value any, err error) {
if leftTerm.Symbol() == scan.SymIndex { if leftTerm.Symbol() == sym.SymIndex {
value, err = getCollectionItemValue(ctx, leftTerm.Children[0], leftTerm.Children[1]) value, err = getCollectionItemValue(ctx, leftTerm.Children[0], leftTerm.Children[1])
} else { } else {
value, _ = ctx.GetVar(leftTerm.Source()) value, _ = ctx.GetVar(leftTerm.Source())
@@ -169,7 +238,7 @@ func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
leftTerm := opTerm.Children[0] leftTerm := opTerm.Children[0]
leftSym := leftTerm.Symbol() leftSym := leftTerm.Symbol()
if leftSym != scan.SymVariable && leftSym != scan.SymIndex { if leftSym != sym.SymVariable && leftSym != sym.SymIndex {
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source()) err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
return return
} }
@@ -179,31 +248,31 @@ func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if rightValue, err = rightChild.Compute(ctx); err == nil { if rightValue, err = rightChild.Compute(ctx); err == nil {
if leftValue, err = getAssignValue(ctx, leftTerm); err == nil { if leftValue, err = getAssignValue(ctx, leftTerm); err == nil {
switch opTerm.Symbol() { switch opTerm.Symbol() {
case scan.SymPlusEqual: case sym.SymPlusEqual:
v, err = sumValues(opTerm, leftValue, rightValue) v, err = sumValues(opTerm, leftValue, rightValue)
case scan.SymMinusEqual: case sym.SymMinusEqual:
v, err = diffValues(opTerm, leftValue, rightValue) v, err = diffValues(opTerm, leftValue, rightValue)
case scan.SymStarEqual: case sym.SymStarEqual:
v, err = mulValues(opTerm, leftValue, rightValue) v, err = mulValues(opTerm, leftValue, rightValue)
case scan.SymSlashEqual: case sym.SymSlashEqual:
v, err = divValues(opTerm, leftValue, rightValue) v, err = divValues(opTerm, leftValue, rightValue)
case scan.SymPercEqual: case sym.SymPercEqual:
v, err = remainderValues(opTerm, leftValue, rightValue) v, err = remainderValues(opTerm, leftValue, rightValue)
case scan.SymAmpersandEqual: case sym.SymAmpersandEqual:
v, err = bitwiseAnd(opTerm, leftValue, rightValue) v, err = bitwiseAnd(opTerm, leftValue, rightValue)
case scan.SymVertBarEqual: case sym.SymVertBarEqual:
v, err = bitwiseOr(opTerm, leftValue, rightValue) v, err = bitwiseOr(opTerm, leftValue, rightValue)
case scan.SymCaretEqual: case sym.SymCaretEqual:
v, err = bitwiseXor(opTerm, leftValue, rightValue) v, err = bitwiseXor(opTerm, leftValue, rightValue)
case scan.SymDoubleLessEqual: case sym.SymDoubleLessEqual:
v, err = bitLeftShift(opTerm, leftValue, rightValue) v, err = bitLeftShift(opTerm, leftValue, rightValue)
case scan.SymDoubleGreaterEqual: case sym.SymDoubleGreaterEqual:
v, err = bitRightShift(opTerm, leftValue, rightValue) v, err = bitRightShift(opTerm, leftValue, rightValue)
default: default:
err = opTerm.Errorf("unsupported assign operator %q", opTerm.Source()) err = opTerm.Errorf("unsupported assign operator %q", opTerm.Source())
} }
if err == nil { if err == nil {
err = assignValue(ctx, leftTerm, v) err = assignValue(ctx, leftTerm, v, false)
} }
} }
} }
@@ -212,15 +281,16 @@ func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymEqual, newAssignTerm) scan.RegisterTermConstructor(sym.SymEqual, newAssignTerm)
scan.RegisterTermConstructor(scan.SymPlusEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymColonEqual, newDeepCopyAssignTerm)
scan.RegisterTermConstructor(scan.SymMinusEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymPlusEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymStarEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymMinusEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymSlashEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymStarEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymPercEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymSlashEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymDoubleLessEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymPercEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymDoubleGreaterEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymDoubleLessEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymAmpersandEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymDoubleGreaterEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymVertBarEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymAmpersandEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymCaretEqual, newOpAssignTerm) scan.RegisterTermConstructor(sym.SymVertBarEqual, newOpAssignTerm)
scan.RegisterTermConstructor(sym.SymCaretEqual, newOpAssignTerm)
} }
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-at.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/list"
)
//-------- at term
func newAtTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalAt,
}
}
// func hasKey(d map[any]any, target any) (ok bool) {
// _, ok = d[target]
// return
// }
func evalAt(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v = int64(-1) // default value if not found
if array.IsArray(rightValue) {
a, _ := rightValue.(*array.ArrayType)
if index := a.IndexDeepSameCmp(leftValue); index >= 0 {
v = index
}
} else if dict.IsDict(rightValue) {
dict, _ := rightValue.(*dict.DictType)
if k, exists := dict.FindKey(leftValue); exists {
v = k
} else {
v = nil
}
} else if list.IsLinkedList(rightValue) {
ls, _ := rightValue.(*list.LinkedList)
if index := ls.Index(leftValue); index >= 0 {
v = index
}
} else {
v = nil
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(sym.SymKwAt, newAtTerm)
}
+8 -6
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-bitwise.go // operator-bitwise.go
@@ -7,6 +7,8 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types"
) )
//-------- Bitwise NOT term //-------- Bitwise NOT term
@@ -28,7 +30,7 @@ func evalBitwiseNot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error)
return return
} }
if kern.IsInteger(value) { if types.IsInteger(value) {
i, _ := value.(int64) i, _ := value.(int64)
v = ^i v = ^i
} else { } else {
@@ -152,8 +154,8 @@ func evalBitwiseXor(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error)
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymTilde, newBitwiseNotTerm) scan.RegisterTermConstructor(sym.SymTilde, newBitwiseNotTerm)
scan.RegisterTermConstructor(scan.SymAmpersand, newBitwiseAndTerm) scan.RegisterTermConstructor(sym.SymAmpersand, newBitwiseAndTerm)
scan.RegisterTermConstructor(scan.SymVertBar, newBitwiseOrTerm) scan.RegisterTermConstructor(sym.SymVertBar, newBitwiseOrTerm)
scan.RegisterTermConstructor(scan.SymCaret, newBitwiseXorTerm) scan.RegisterTermConstructor(sym.SymCaret, newBitwiseXorTerm)
} }
+15 -13
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-bool.go // operator-bool.go
@@ -9,6 +9,8 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/boolean"
) )
//-------- NOT term //-------- NOT term
@@ -30,7 +32,7 @@ func evalNot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if b, ok := kern.ToBool(rightValue); ok { if b, ok := boolean.ToBool(rightValue); ok {
v = !b v = !b
} else { } else {
err = opTerm.ErrIncompatiblePrefixPostfixType(rightValue) err = opTerm.ErrIncompatiblePrefixPostfixType(rightValue)
@@ -68,8 +70,8 @@ func evalAndWithoutShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err e
return return
} }
leftBool, lok = kern.ToBool(leftValue) leftBool, lok = boolean.ToBool(leftValue)
rightBool, rok = kern.ToBool(rightValue) rightBool, rok = boolean.ToBool(rightValue)
if lok && rok { if lok && rok {
v = leftBool && rightBool v = leftBool && rightBool
@@ -90,14 +92,14 @@ func evalAndWithShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err erro
return return
} }
if leftBool, lok := kern.ToBool(leftValue); !lok { if leftBool, lok := boolean.ToBool(leftValue); !lok {
// err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", expr.TypeName(leftValue)) // err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", expr.TypeName(leftValue))
// return // return
err = self.ErrIncompatibleType(leftValue, "left") err = self.ErrIncompatibleType(leftValue, "left")
} else if !leftBool { } else if !leftBool {
v = false v = false
} else if rightValue, err = self.Children[1].Compute(ctx); err == nil { } else if rightValue, err = self.Children[1].Compute(ctx); err == nil {
if rightBool, rok := kern.ToBool(rightValue); rok { if rightBool, rok := boolean.ToBool(rightValue); rok {
v = rightBool v = rightBool
} else { } else {
err = self.ErrIncompatibleTypes(leftValue, rightValue) err = self.ErrIncompatibleTypes(leftValue, rightValue)
@@ -136,8 +138,8 @@ func evalOrWithoutShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err er
return return
} }
leftBool, lok = kern.ToBool(leftValue) leftBool, lok = boolean.ToBool(leftValue)
rightBool, rok = kern.ToBool(rightValue) rightBool, rok = boolean.ToBool(rightValue)
if lok && rok { if lok && rok {
v = leftBool || rightBool v = leftBool || rightBool
@@ -158,13 +160,13 @@ func evalOrWithShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error
return return
} }
if leftBool, lok := kern.ToBool(leftValue); !lok { if leftBool, lok := boolean.ToBool(leftValue); !lok {
err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", kern.TypeName(leftValue)) err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", kern.TypeName(leftValue))
return return
} else if leftBool { } else if leftBool {
v = true v = true
} else if rightValue, err = self.Children[1].Compute(ctx); err == nil { } else if rightValue, err = self.Children[1].Compute(ctx); err == nil {
if rightBool, rok := kern.ToBool(rightValue); rok { if rightBool, rok := boolean.ToBool(rightValue); rok {
v = rightBool v = rightBool
} else { } else {
err = self.ErrIncompatibleTypes(leftValue, rightValue) err = self.ErrIncompatibleTypes(leftValue, rightValue)
@@ -175,7 +177,7 @@ func evalOrWithShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymNot, newNotTerm) scan.RegisterTermConstructor(sym.SymNot, newNotTerm)
scan.RegisterTermConstructor(scan.SymAnd, newAndTerm) scan.RegisterTermConstructor(sym.SymAnd, newAndTerm)
scan.RegisterTermConstructor(scan.SymOr, newOrTerm) scan.RegisterTermConstructor(sym.SymOr, newOrTerm)
} }
+6 -4
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-builtin.go // operator-builtin.go
@@ -9,6 +9,8 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/str"
) )
//-------- builtin term //-------- builtin term
@@ -31,13 +33,13 @@ func evalBuiltin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} }
count := 0 count := 0
if kern.IsString(childValue) { if str.IsString(childValue) {
module, _ := childValue.(string) module, _ := childValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module) count, err = ImportInContextByGlobPattern(ctx, module)
} else { } else {
var moduleSpec any var moduleSpec any
var it kern.Iterator var it kern.Iterator
if it, err = NewIterator(childValue); err != nil { if it, err = NewIterator(ctx, childValue, nil); err != nil {
return return
} }
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() { for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
@@ -65,5 +67,5 @@ func evalBuiltin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwBuiltin, newBuiltinTerm) scan.RegisterTermConstructor(sym.SymKwBuiltin, newBuiltinTerm)
} }
+3 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-but.go // operator-but.go
@@ -7,6 +7,7 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
//-------- but term //-------- but term
@@ -28,5 +29,5 @@ func evalBut(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwBut, newButTerm) scan.RegisterTermConstructor(sym.SymKwBut, newButTerm)
} }
+152
View File
@@ -0,0 +1,152 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-cat.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
)
//-------- cat term
func newCatTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalCat,
}
}
func evalCat(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var itLeft, itRight kern.Iterator
// var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
return
}
if itLeft, ok = leftValue.(kern.Iterator); !ok {
if itLeft, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
if itRight, ok = rightValue.(kern.Iterator); !ok {
if itRight, err = NewIterator(ctx, rightValue, nil); err != nil {
return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", kern.TypeName(rightValue))
}
}
// values := kern.NewListA()
// for _, it := range []kern.Iterator{itLeft, itRight} {
// for item, err = it.Next(); err == nil; item, err = it.Next() {
// values.AppendItem(item)
// }
// }
// if err == io.EOF {
// err = nil
// }
// v = values
v = newCatIterator(itLeft, itRight)
return
}
const catIteratorType = "cat"
type catIterator struct {
kern.IteratorBase
itLeft kern.Iterator
itRight kern.Iterator
itCurrent kern.Iterator
}
func newCatIterator(itLeft, itRight kern.Iterator) (it *catIterator) {
it = &catIterator{
IteratorBase: kern.IteratorBase{},
itLeft: itLeft,
itRight: itRight,
itCurrent: itLeft,
}
it.Reset()
return
}
func (it *catIterator) TypeName() string {
return catIteratorType
}
func (it *catIterator) String() string {
return fmt.Sprintf("$(%s %s %s)", it.itLeft, catIteratorType, it.itRight)
}
func (it *catIterator) Next() (item any, err error) {
if it.itCurrent == nil {
err = io.EOF
} else {
if item, err = it.itCurrent.Next(); err == nil {
it.Increment()
} else if err == io.EOF {
if it.itCurrent == it.itLeft {
it.itCurrent = it.itRight
} else {
return
}
if item, err = it.itCurrent.Next(); err == nil {
it.Increment()
} else {
it.itCurrent = nil
}
}
}
it.SetCurrent(item)
return
}
// func (it *catIterator) Reset() (err error) {
// err = it.itLeft.Reset()
// it.IteratorBase.Reset()
// return
// }
func (it *catIterator) 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 = it.Index()
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.Count()
default:
err = kern.ErrNoOperation(name)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(sym.SymKwCat, newCatTerm)
}
+24 -18
View File
@@ -1,12 +1,16 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-context-value.go // operator-context-value.go
package expr package expr
import ( import (
"io"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/list"
) )
//-------- context term //-------- context term
@@ -23,35 +27,37 @@ func newContextTerm(tk *scan.Token) (inst *scan.Term) {
func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any var childValue any
var sourceCtx kern.ExprContext var sourceCtx kern.ExprContext
if len(opTerm.Children) == 0 { if len(opTerm.Children) == 0 {
sourceCtx = ctx sourceCtx = ctx
} else if opTerm.Children[0].Symbol() == scan.SymVariable && opTerm.Children[0].Source() == "global" { } else if opTerm.Children[0].Symbol() == sym.SymVariable && opTerm.Children[0].Source() == "global" {
sourceCtx = ctx.GetGlobal() sourceCtx = ctx.GetGlobal()
} else if childValue, err = opTerm.EvalPrefix(ctx); err == nil { } else if childValue, err = opTerm.EvalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok { if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx sourceCtx = dc.ctx
} }
} else {
return
} }
if sourceCtx != nil { if sourceCtx != nil {
if formatter, ok := sourceCtx.(kern.DictFormat); ok { v = sourceCtx.ToDict()
v = formatter.ToDict() } else if childValue != nil {
} else if formatter, ok := sourceCtx.(kern.Formatter); ok { it, ok := childValue.(kern.Iterator)
v = formatter.ToString(0) if !ok {
} else { it, err = NewIterator(ctx, childValue, nil)
// keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' }) }
keys := sourceCtx.EnumVars(nil) if err == nil {
d := make(map[string]any) var item any
for _, key := range keys { values := list.NewLinkedListA()
d[key], _ = sourceCtx.GetVar(key) for item, err = it.Next(); err == nil; item, err = it.Next() {
values.PushBack(item)
} }
keys = sourceCtx.EnumFuncs(func(name string) bool { return true }) if err == io.EOF {
for _, key := range keys { err = nil
d[key], _ = sourceCtx.GetFuncInfo(key) v = values
} }
v = d
} }
} else { } else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue) err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
@@ -61,5 +67,5 @@ func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymDoubleDollar, newContextTerm) scan.RegisterTermConstructor(sym.SymDoubleDollar, newContextTerm)
} }
+3 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-ctrl.go // operator-ctrl.go
@@ -7,6 +7,7 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
//-------- export all term //-------- export all term
@@ -28,5 +29,5 @@ func evalExportAll(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymDoubleAt, newExportAllTerm) scan.RegisterTermConstructor(sym.SymDoubleAt, newExportAllTerm)
} }
+9 -8
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-default.go // operator-default.go
@@ -7,6 +7,7 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
//-------- default term //-------- default term
@@ -29,7 +30,7 @@ func evalDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} }
leftTerm := opTerm.Children[0] leftTerm := opTerm.Children[0]
if leftTerm.Tk.Sym != scan.SymVariable { if leftTerm.Tk.Sym != sym.SymVariable {
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source) // err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm) err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
return return
@@ -63,7 +64,7 @@ func evalAlternate(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} }
leftTerm := opTerm.Children[0] leftTerm := opTerm.Children[0]
if leftTerm.Tk.Sym != scan.SymVariable { if leftTerm.Tk.Sym != sym.SymVariable {
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source) // err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm) err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
return return
@@ -99,7 +100,7 @@ func evalAssignDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err erro
} }
leftTerm := opTerm.Children[0] leftTerm := opTerm.Children[0]
if leftTerm.Tk.Sym != scan.SymVariable { if leftTerm.Tk.Sym != sym.SymVariable {
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source) // err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm) err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
return return
@@ -111,7 +112,7 @@ func evalAssignDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err erro
if functor, ok := rightValue.(kern.Functor); ok { if functor, ok := rightValue.(kern.Functor); ok {
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1) //ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, []kern.ExprFuncParam{ ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, []kern.ExprFuncParam{
NewFuncParamFlag(kern.ParamValue, PfDefault|PfRepeat), kern.NewFuncParamFlag(kern.ParamValue, kern.PfDefault|kern.PfRepeat),
}) })
} else { } else {
v = rightValue v = rightValue
@@ -123,7 +124,7 @@ func evalAssignDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err erro
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymDoubleQuestion, newDefaultTerm) scan.RegisterTermConstructor(sym.SymDoubleQuestion, newDefaultTerm)
scan.RegisterTermConstructor(scan.SymQuestionEqual, newDefaultAssignTerm) scan.RegisterTermConstructor(sym.SymQuestionEqual, newDefaultAssignTerm)
scan.RegisterTermConstructor(scan.SymQuestionExclam, newAlternateTerm) scan.RegisterTermConstructor(sym.SymQuestionExclam, newAlternateTerm)
} }
+8 -6
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-digest.go // operator-digest.go
@@ -10,6 +10,7 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
) )
//-------- digest term //-------- digest term
@@ -37,7 +38,7 @@ func evalDigest(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if it, err = NewIterator(leftValue); err != nil { if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of DIGEST must be an iterable data-source; got %s", kern.TypeName(leftValue)) return nil, fmt.Errorf("left operand of DIGEST must be an iterable data-source; got %s", kern.TypeName(leftValue))
} }
@@ -45,15 +46,16 @@ func evalDigest(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item) ctx.SetVar("_", item)
ctx.SetVar("__", it.Index()) ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count()) ctx.SetVar("#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if rightValue == nil { if rightValue == nil {
break err = io.EOF
} else { } else {
lastValue = rightValue lastValue = rightValue
ctx.SetVar(kern.ControlLastResult, lastValue)
} }
} }
ctx.DeleteVar("_#") ctx.DeleteVar("#")
ctx.DeleteVar("__") ctx.DeleteVar("__")
ctx.DeleteVar("_") ctx.DeleteVar("_")
if err != nil { if err != nil {
@@ -69,5 +71,5 @@ func evalDigest(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwDigest, newDigestTerm) scan.RegisterTermConstructor(sym.SymKwDigest, newDigestTerm)
} }
+36 -19
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-dot.go // operator-dot.go
@@ -7,6 +7,9 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/util"
) )
// -------- dot term // -------- dot term
@@ -34,7 +37,7 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
switch unboxedValue := leftValue.(type) { switch unboxedValue := leftValue.(type) {
case kern.ExtIterator: case kern.ExtIterator:
if indexTerm.Tk.Sym == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ { if indexTerm.Symbol() == sym.SymVariable /*|| indexTerm.Tk.Sym == sym.SymString */ {
opName := indexTerm.Source() opName := indexTerm.Source()
if unboxedValue.HasOperation(opName) { if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, map[string]any{}) v, err = unboxedValue.CallOperation(opName, map[string]any{})
@@ -45,22 +48,8 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} else { } else {
err = indexTerm.Tk.ErrorExpectedGot("identifier") err = indexTerm.Tk.ErrorExpectedGot("identifier")
} }
case *kern.DictType: case *dict.DictType:
var ok bool v, err = dotGetDictItemValue(ctx, unboxedValue, opTerm.Children[1])
s := opTerm.Children[1].Tk.Sym
if s == scan.SymVariable || s == scan.SymString {
src := opTerm.Children[1].Source()
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1]
}
if v, ok = unboxedValue.GetItem(src); !ok {
err = opTerm.Errorf("key %q not found", src)
}
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if v, ok = unboxedValue.GetItem(rightValue); !ok {
err = opTerm.Errorf("key %q not found", rightValue)
}
}
default: default:
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
err = opTerm.Errorf("incompatible types: %s and %s", kern.TypeName(leftValue), kern.TypeName(rightValue)) err = opTerm.Errorf("incompatible types: %s and %s", kern.TypeName(leftValue), kern.TypeName(rightValue))
@@ -69,7 +58,35 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
func dotGetDictItemValue(ctx kern.ExprContext, d *dict.DictType, rightTerm *scan.Term) (v any, err error) {
var ok bool
var rightValue any
s := rightTerm.Symbol()
if s == sym.SymVariable || s == sym.SymString {
// src := rightTerm.Source()
// if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
// src = src[1 : len(src)-1]
// }
src := util.UnquoteString(rightTerm.Source())
if v, ok = d.GetItem(src); !ok {
err = errDictKeyNotFound(rightTerm, src)
}
} else if rightValue, err = rightTerm.Compute(ctx); err == nil {
if v, ok = d.GetItem(rightValue); !ok {
err = errDictKeyNotFound(rightTerm, rightValue)
}
}
return
}
func errDictKeyNotFound(term *scan.Term, key any) error {
if s, ok := key.(string); ok {
return term.Errorf("key %q not found", s)
}
return term.Errorf("key %v not found", key)
}
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymDot, newDotTerm) scan.RegisterTermConstructor(sym.SymDot, newDotTerm)
} }
+5 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-fact.go // operator-fact.go
@@ -9,6 +9,8 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types"
) )
//-------- fact term //-------- fact term
@@ -30,7 +32,7 @@ func evalFact(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if kern.IsInteger(leftValue) { if types.IsInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 { if i, _ := leftValue.(int64); i >= 0 {
f := int64(1) f := int64(1)
for k := int64(1); k <= i; k++ { for k := int64(1); k <= i; k++ {
@@ -48,5 +50,5 @@ func evalFact(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymExclamation, newFactTerm) scan.RegisterTermConstructor(sym.SymExclamation, newFactTerm)
} }
+118 -24
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-filter.go // operator-filter.go
@@ -6,10 +6,11 @@ package expr
import ( import (
"fmt" "fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/boolean"
) )
//-------- map term //-------- map term
@@ -25,9 +26,11 @@ func newFilterTerm(tk *scan.Token) (inst *scan.Term) {
} }
func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any // var leftValue, rightValue any
var leftValue any
var it kern.Iterator var it kern.Iterator
var item any // var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil { if err = opTerm.CheckOperands(); err != nil {
return return
@@ -37,39 +40,130 @@ func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if it, err = NewIterator(leftValue); err != nil { if it, ok = leftValue.(kern.Iterator); !ok {
return nil, fmt.Errorf("left operand of FILTER must be an iterable data-source; got %s", kern.TypeName(leftValue)) if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of FILTER must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
} }
values := kern.NewListA() // values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() { // for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item) // ctx.SetVar("_", item)
// ctx.SetVar("__", it.Index())
// ctx.SetVar("_#", it.Count())
// if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
// if success, valid := kern.ToBool(rightValue); valid {
// if success {
// values.AppendItem(item)
// }
// } else {
// err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
// }
// }
// ctx.DeleteVar("_#")
// ctx.DeleteVar("__")
// ctx.DeleteVar("_")
// if err != nil {
// break
// }
// }
// if err == io.EOF {
// err = nil
// }
// v = values
v = newFilerIterator(ctx, it, opTerm.Children[1])
return
}
const filterIteratorType = "filter"
type filterIterator struct {
kern.IteratorBase
ctx kern.ExprContext
itSrc kern.Iterator
expr *scan.Term
}
func newFilerIterator(ctx kern.ExprContext, itSrc kern.Iterator, filterExpr *scan.Term) (it *filterIterator) {
it = &filterIterator{
IteratorBase: kern.IteratorBase{},
ctx: ctx,
itSrc: itSrc,
expr: filterExpr,
}
it.IteratorBase.Reset()
return
}
func (it *filterIterator) TypeName() string {
return filterIteratorType
}
func (it *filterIterator) String() string {
return fmt.Sprintf("$(%s %s bool-expr)", it.itSrc, filterIteratorType)
}
func (it *filterIterator) Next() (item any, err error) {
var attempt, result any
ctx := it.ctx
for attempt, err = it.itSrc.Next(); err == nil; attempt, err = it.itSrc.Next() {
ctx.SetVar("_", attempt)
ctx.SetVar("__", it.Index()) ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count()) ctx.SetVar("#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil { result, err = it.expr.Compute(ctx)
if success, valid := kern.ToBool(rightValue); valid { ctx.DeleteVar("#")
if success {
values.AppendItem(item)
}
} else {
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
}
}
ctx.DeleteVar("_#")
ctx.DeleteVar("__") ctx.DeleteVar("__")
ctx.DeleteVar("_") ctx.DeleteVar("_")
if err == nil {
if success, valid := boolean.ToBool(result); valid {
if success {
item = attempt
break
}
} else {
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", result, result)
}
}
if err != nil { if err != nil {
break break
} }
} }
if err == io.EOF {
err = nil it.SetCurrent(item)
return
}
// func (it *filterIterator) Reset() (err error) {
// err = it.itLeft.Reset()
// it.IteratorBase.Reset()
// return
// }
func (it *filterIterator) 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 = it.Index()
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.Count()
default:
err = kern.ErrNoOperation(name)
} }
v = values
return return
} }
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwFilter, newFilterTerm) scan.RegisterTermConstructor(sym.SymKwFilter, newFilterTerm)
} }
+7 -5
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operand-fraction.go // operand-fraction.go
@@ -11,6 +11,8 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/fract"
) )
// -------- fraction term // -------- fraction term
@@ -51,7 +53,7 @@ func evalFraction(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
num = -num num = -num
} }
if num != 0 { if num != 0 {
if g := kern.Gcd(num, den); g != 1 { if g := fract.Gcd(num, den); g != 1 {
num = num / g num = num / g
den = den / g den = den / g
} }
@@ -59,11 +61,11 @@ func evalFraction(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v = num v = num
} else { } else {
// v = &expr.FractionType{num, den} // v = &expr.FractionType{num, den}
v = kern.NewFraction(num, den) v = fract.NewFraction(num, den)
} }
} else { } else {
// v = &FractionType{0, den} // v = &FractionType{0, den}
v = kern.NewFraction(0, den) v = fract.NewFraction(0, den)
} }
return return
} }
@@ -71,5 +73,5 @@ func evalFraction(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
// registerTermConstructor(SymVertBar, newFractionTerm) // registerTermConstructor(SymVertBar, newFractionTerm)
scan.RegisterTermConstructor(scan.SymColon, newFractionTerm) scan.RegisterTermConstructor(sym.SymColon, newFractionTerm)
} }
+24 -13
View File
@@ -11,6 +11,10 @@ import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/str"
) )
//-------- group by term //-------- group by term
@@ -31,6 +35,7 @@ func evalGroupBy(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var item any var item any
var sKey string var sKey string
var keyByIndex bool var keyByIndex bool
var ok bool
if err = opTerm.CheckOperands(); err != nil { if err = opTerm.CheckOperands(); err != nil {
return return
@@ -40,35 +45,41 @@ func evalGroupBy(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if it, err = NewIterator(leftValue); err != nil { if it, ok = leftValue.(kern.Iterator); !ok {
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", kern.TypeName(leftValue)) if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
} }
rightTk := opTerm.Children[1].Tk rightTk := opTerm.Children[1].Tk
if rightTk.IsSymbol(scan.SymVariable) && rightTk.Source() == "__" { if rightTk.IsSymbol(sym.SymVariable) && rightTk.Source() == "__" {
keyByIndex = true keyByIndex = true
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil { } else if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
return return
} else if kern.IsString(rightValue) { } else if str.IsString(rightValue) {
sKey = rightValue.(string) sKey = rightValue.(string)
} else { } else {
return nil, fmt.Errorf("right operand of GROUPBY must be a string or identifier '__'; got %s", kern.TypeName(rightValue)) return nil, fmt.Errorf("right operand of GROUPBY must be a string or identifier '__'; got %s", kern.TypeName(rightValue))
} }
values := kern.MakeDict() values := dict.MakeDict()
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item) ctx.SetVar("_", item)
ctx.SetVar("__", it.Index()) ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count()) ctx.SetVar("#", it.Count())
var sItemKey string var sItemKey string
if d, ok := item.(*kern.DictType); ok { if d, ok := item.(*dict.DictType); ok {
if keyByIndex || len(sKey) == 0 { if keyByIndex || len(sKey) == 0 {
sItemKey = strconv.Itoa(int(it.Index())) sItemKey = strconv.Itoa(int(it.Index()))
} else if d.HasKey(sKey) { } else if d.HasKey(sKey) {
if keyValue, exists := d.GetItem(sKey); exists { if keyValue, exists := d.GetItem(sKey); exists {
sItemKey = fmt.Sprintf("%v", keyValue) if s, ok := keyValue.(string); ok {
sItemKey = s
} else {
sItemKey = fmt.Sprintf("%v", keyValue)
}
} else { } else {
sItemKey = "_" sItemKey = "_"
} }
@@ -79,17 +90,17 @@ func evalGroupBy(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
sItemKey = strconv.Itoa(int(it.Index())) sItemKey = strconv.Itoa(int(it.Index()))
} }
var ls *kern.ListType var ls *array.ArrayType
if lsAny, exists := values.GetItem(sItemKey); exists && lsAny != nil { if lsAny, exists := values.GetItem(sItemKey); exists && lsAny != nil {
ls = lsAny.(*kern.ListType) ls = lsAny.(*array.ArrayType)
} }
if ls == nil { if ls == nil {
ls = kern.NewListA() ls = array.NewArrayA()
} }
ls.AppendItem(item) ls.AppendItem(item)
values.SetItem(sItemKey, ls) values.SetItem(sItemKey, ls)
ctx.DeleteVar("_#") ctx.DeleteVar("#")
ctx.DeleteVar("__") ctx.DeleteVar("__")
ctx.DeleteVar("_") ctx.DeleteVar("_")
} }
@@ -102,5 +113,5 @@ func evalGroupBy(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwGroupBy, newGroupByTerm) scan.RegisterTermConstructor(sym.SymKwGroupBy, newGroupByTerm)
} }
+14 -7
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-in.go // operator-in.go
@@ -7,6 +7,10 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/list"
) )
//-------- in term //-------- in term
@@ -33,12 +37,15 @@ func evalIn(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if kern.IsList(rightValue) { if array.IsArray(rightValue) {
list, _ := rightValue.(*kern.ListType) a, _ := rightValue.(*array.ArrayType)
v = list.IndexDeepSameCmp(leftValue) >= 0 v = a.IndexDeepSameCmp(leftValue) >= 0
} else if kern.IsDict(rightValue) { } else if dict.IsDict(rightValue) {
dict, _ := rightValue.(*kern.DictType) dict, _ := rightValue.(*dict.DictType)
v = dict.HasKey(leftValue) v = dict.HasKey(leftValue)
} else if list.IsLinkedList(rightValue) {
ls, _ := rightValue.(*list.LinkedList)
v = ls.HasValue(leftValue)
} else { } else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue) err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
} }
@@ -47,5 +54,5 @@ func evalIn(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwIn, newInTerm) scan.RegisterTermConstructor(sym.SymKwIn, newInTerm)
} }
+9 -6
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-include.go // operator-include.go
@@ -7,6 +7,9 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/str"
) )
//-------- include term //-------- include term
@@ -29,9 +32,9 @@ func evalInclude(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} }
count := 0 count := 0
if kern.IsList(childValue) { if array.IsArray(childValue) {
list, _ := childValue.(*kern.ListType) a, _ := childValue.(*array.ArrayType)
for i, filePathSpec := range *list { for i, filePathSpec := range *a {
if filePath, ok := filePathSpec.(string); ok { if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil { if v, err = EvalFile(ctx, filePath); err == nil {
count++ count++
@@ -44,7 +47,7 @@ func evalInclude(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
break break
} }
} }
} else if kern.IsString(childValue) { } else if str.IsString(childValue) {
filePath, _ := childValue.(string) filePath, _ := childValue.(string)
if v, err = EvalFile(ctx, filePath); err == nil { if v, err = EvalFile(ctx, filePath); err == nil {
count++ count++
@@ -61,5 +64,5 @@ func evalInclude(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymKwInclude, newIncludeTerm) scan.RegisterTermConstructor(sym.SymKwInclude, newIncludeTerm)
} }
+164 -66
View File
@@ -1,12 +1,20 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-index.go // operator-index.go
package expr package expr
import ( import (
"strings"
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/sym"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/interval"
"git.portale-stac.it/go-pkg/expr/types/list"
) )
// -------- index term // -------- index term
@@ -20,15 +28,15 @@ func newIndexTerm(tk *scan.Token) (inst *scan.Term) {
} }
} }
func verifyKey(indexList *kern.ListType) (index any, err error) { func verifyKey(indexList *array.ArrayType) (index any, err error) {
index = (*indexList)[0] index = (*indexList)[0]
return return
} }
func verifyIndex(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (index int, err error) { func verifyIndex(indexTerm kern.Term, indexList *array.ArrayType, maxValue int64) (index int64, err error) {
var v int var v int64
if v, err = kern.ToGoInt((*indexList)[0], "index expression"); err == nil { if v, err = types.ToGoInt64((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue { if v < 0 && v >= -maxValue {
v = maxValue + v v = maxValue + v
} }
@@ -41,32 +49,32 @@ func verifyIndex(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (
return return
} }
func verifyRange(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (startIndex, endIndex int, err error) { // func verifyInterval(indexTerm kern.Term, indexList *array.ArrayType, maxValue int64) (startIndex, endIndex int64, err error) {
v, _ := ((*indexList)[0]).(*intPair) // v, _ := ((*indexList)[0]).(*interval.IntervalType)
startIndex = v.a // startIndex = v.Begin()
endIndex = v.b // endIndex = v.End()
if endIndex == kern.ConstLastIndex { // if endIndex == kern.ConstLastIndex {
endIndex = maxValue // endIndex = int64(maxValue)
} // }
if startIndex < 0 && startIndex >= -maxValue { // if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex // startIndex = int64(maxValue) + startIndex
} // }
if endIndex < 0 && endIndex >= -maxValue { // if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex // endIndex = int64(maxValue) + endIndex
} // }
if startIndex < 0 || startIndex > maxValue { // if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex) // err = indexTerm.Errorf("%s start-index %d is out of bounds", interval.TypeName, startIndex)
} else if endIndex < 0 || endIndex > maxValue { // } else if endIndex < 0 || endIndex > maxValue {
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex) // err = indexTerm.Errorf("%s end-index %d is out of bounds", interval.TypeName, endIndex)
} else if startIndex > endIndex { // } else if startIndex > endIndex {
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex) // err = indexTerm.Errorf("%s start-index %d must not be greater than end-index %d", interval.TypeName, startIndex, endIndex)
} // }
return // return
} // }
func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) { func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
var indexList *kern.ListType var indexList *array.ArrayType
var ok bool var ok bool
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
@@ -74,7 +82,7 @@ func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} }
indexTerm := opTerm.Children[1] indexTerm := opTerm.Children[1]
if indexList, ok = rightValue.(*kern.ListType); !ok { if indexList, ok = rightValue.(*array.ArrayType); !ok {
err = opTerm.Errorf("invalid index expression") err = opTerm.Errorf("invalid index expression")
return return
} else if len(*indexList) != 1 { } else if len(*indexList) != 1 {
@@ -82,41 +90,12 @@ func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
if kern.IsInteger((*indexList)[0]) { if types.IsInteger((*indexList)[0]) {
switch unboxedValue := leftValue.(type) { v, err = collectionItemByIndex(opTerm, leftValue, rightValue, indexList)
case *kern.ListType: } else if interval.IsInterval((*indexList)[0]) {
var index int v, err = subCollectionByIndex(opTerm, leftValue, rightValue, indexList)
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil { } else if dict.IsDict(leftValue) {
v = (*unboxedValue)[index] d := leftValue.(*dict.DictType)
}
case string:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
v = string(unboxedValue[index])
}
case *kern.DictType:
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
} else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *kern.ListType:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
sublist := kern.ListType((*unboxedValue)[start:end])
v = &sublist
}
case string:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
v = unboxedValue[start:end]
}
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
} else if kern.IsDict(leftValue) {
d := leftValue.(*kern.DictType)
v, err = getDictItem(d, indexTerm, indexList, rightValue) v, err = getDictItem(d, indexTerm, indexList, rightValue)
} else { } else {
rightChild := opTerm.Children[1] rightChild := opTerm.Children[1]
@@ -125,7 +104,126 @@ func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return return
} }
func getDictItem(d *kern.DictType, indexTerm *scan.Term, indexList *kern.ListType, rightValue any) (v any, err error) { func collectionItemByIndex(opTerm kern.Term, leftValue, rightValue any, indexList *array.ArrayType) (v any, err error) {
indexTerm := opTerm.GetChild(1)
switch unboxedValue := leftValue.(type) {
case *array.ArrayType:
var index int64
if index, err = verifyIndex(indexTerm, indexList, int64(len(*unboxedValue))); err == nil {
v = (*unboxedValue)[index]
}
case *list.LinkedList:
var index int64
if index, err = verifyIndex(indexTerm, indexList, int64(unboxedValue.Len())); err == nil {
v, err = unboxedValue.At(index)
}
case string:
var index int64
if index, err = verifyIndex(indexTerm, indexList, int64(len(unboxedValue))); err == nil {
v = string(unboxedValue[index])
}
case *dict.DictType:
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func subCollectionByIndex(opTerm kern.Term, leftValue, rightValue any, indexList *array.ArrayType) (v any, err error) {
triple := (*indexList)[0].(*interval.IntervalType)
switch unboxedValue := leftValue.(type) {
case *array.ArrayType:
v = intervalSubArray(unboxedValue, triple)
case *list.LinkedList:
v = intervalSubLinkedList(unboxedValue, triple)
case string:
v = intervalSubString(unboxedValue, triple)
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func fixInterval(p *interval.IntervalType, maxLength int) (b, e, s int) {
b, e, s = p.ToIntTriple()
if b < 0 {
b = maxLength + b
}
if e < 0 {
e = maxLength + e //+ 1
}
return
}
func intervalSubString(source string, p *interval.IntervalType) (result string) {
b, e, s := fixInterval(p, len(source))
if b <= e {
e = min(e, len(source))
if s == 1 {
result = source[b:e]
} else {
var sb strings.Builder
for i := b; i < e; i += s {
sb.WriteByte(source[i])
}
result = sb.String()
}
} else {
var sb strings.Builder
for i := b; i >= e; i -= s {
sb.WriteByte(source[i])
}
result = sb.String()
}
return
}
func intervalSubArray(source *array.ArrayType, p *interval.IntervalType) (result *array.ArrayType) {
var a array.ArrayType
b, e, s := fixInterval(p, len(*source))
if b <= e {
e = min(e, len(*source))
if s == 1 {
a = (*source)[b:e]
} else {
size := (e-b)/s + 1
a = make(array.ArrayType, 0, size)
for i := b; i < e; i += s {
a = append(a, (*source)[i])
}
}
} else {
size := (b-e)/s + 1
a = make(array.ArrayType, 0, size)
for i := b; i > e; i -= s {
a = append(a, (*source)[i])
}
}
result = &a
return
}
func intervalSubLinkedList(source *list.LinkedList, p *interval.IntervalType) (result *list.LinkedList) {
b, e, s := fixInterval(p, source.Len())
revert := b > e
if revert {
b, e = e, b
source = source.Revert()
}
e = min(e, source.Len())
if s == 1 {
result = source.Sub(int64(b), int64(e))
} else {
result = source.SubStep(int64(b), int64(e), int64(s))
}
if revert {
source = source.Revert()
}
return
}
func getDictItem(d *dict.DictType, indexTerm kern.Term, indexList *array.ArrayType, rightValue any) (v any, err error) {
var ok bool var ok bool
var indexValue any var indexValue any
@@ -139,5 +237,5 @@ func getDictItem(d *kern.DictType, indexTerm *scan.Term, indexList *kern.ListTyp
// init // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymIndex, newIndexTerm) scan.RegisterTermConstructor(sym.SymIndex, newIndexTerm)
} }

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