Compare commits

...

20 Commits

Author SHA1 Message Date
f100adead3 increased test coverage and splitted some utility functions by OS 2026-04-26 06:16:43 +02:00
7d2cf1e687 new operator 'join' 2026-04-25 07:00:30 +02:00
49728307f3 t_operator_test.go: added a test for the digest operator 2026-04-25 06:33:35 +02:00
20dc502438 new operator 'digest' 2026-04-25 06:25:39 +02:00
ce7bfc5e3f operator-map/filter.go: added deletion of temporary variables '_index' and '_count' 2026-04-25 06:24:28 +02:00
6e98bdd16b new operator 'filter' 2026-04-24 22:42:46 +02:00
6ee365bacc new operator 'map' ans general variable access by ${} 2026-04-24 20:11:25 +02:00
20d8236325 Iterators: Removed NewAnyIterator(), added NewIterator() 2026-04-24 06:26:00 +02:00
c39ee7cec0 dict-iterator.go: fixed behaviour of NewDictIterator() with no args 2026-04-23 22:06:23 +02:00
9e4252173b Iterator interface now embeds fmt.Stringer 2026-04-23 22:04:41 +02:00
2b80ba6789 t_plugin_test.go: TestLoadPluginName() removed due to linkage mismatch 2026-04-23 22:02:41 +02:00
d7247f97c5 doc: added description of set() and unset() functions defined in the base buiiltin 2026-04-23 19:09:09 +02:00
02df7f1c1f Plugin file name extension adapted to the host OS convention (.so on linux, .dll on windows, .dylib on Darwin/MacOS) 2026-04-23 19:06:11 +02:00
b6b09b2fb1 builtin-os-file: add function fileReadIterator() that creates an iterator over lines of a text file 2026-04-23 19:01:07 +02:00
0677180456 builtin-iterator.go/run(): removed default operator (print item) 2026-04-23 18:55:03 +02:00
b6952444ab builtin-base: added functions set() and unset() 2026-04-21 17:14:51 +02:00
6c3a071a02 doc: last change date-time updated 2026-04-21 06:49:58 +02:00
d7d03b4af8 README: added link to the main Expr documentation hosted on cdn.paas.portale-stac.it 2026-04-21 06:47:15 +02:00
49c5cb6a22 doc: progress about builtin functions 2026-04-21 06:45:41 +02:00
53d805a832 doc: test commit 2026-04-19 21:27:43 +02:00
40 changed files with 1557 additions and 481 deletions

View File

@ -144,4 +144,4 @@ Variables and functions can be added to a context both programmatically and ad a
== Expressions syntax
See #TODO link to doc/Expr.html#
See https://cdn.paas.portale-stac.it/howto/go-pkg/expr/doc/Expr.html[Expr documentation] for a complete reference of the expression syntax and available functions.

View File

@ -236,6 +236,35 @@ func varFunc(ctx ExprContext, name string, args map[string]any) (result any, err
return
}
func setFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[ParamName].(string); !ok {
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
}
if result, ok = args[ParamValue]; ok {
ctx.GetParent().UnsafeSetVar(varName, result)
} else {
err = ErrWrongParamType(name, ParamValue, TypeAny, args[ParamValue])
}
return
}
func unsetFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[ParamName].(string); !ok {
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
} else {
ctx.GetParent().DeleteVar(varName)
result = nil
}
return
}
//// import
func ImportBuiltinsFuncs(ctx ExprContext) {
@ -270,6 +299,16 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
NewFuncParam(ParamName),
NewFuncParamFlagDef(ParamValue, PfDefault, nil),
})
ctx.RegisterFunc("set", NewGolangFunctor(setFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParam(ParamValue),
})
ctx.RegisterFunc("unset", NewGolangFunctor(unsetFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParam(ParamValue),
})
}
func init() {

View File

@ -23,9 +23,11 @@ func parseRunArgs(localCtx ExprContext, args map[string]any) (it Iterator, op Fu
return
}
if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
return
if args[iterParamOperator] != nil {
if op, ok = args[iterParamOperator].(Functor); !ok || op == nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
return
}
}
var vars *DictType
@ -51,7 +53,7 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
var ok bool
var op Functor
var v any
var usingDefaultOp = false
// var usingDefaultOp = false
var params map[string]any
var item any
@ -60,24 +62,27 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
if it, op, err = parseRunArgs(localCtx, args); err != nil {
return
} else if op == nil {
op = NewGolangFunctor(printLnFunc)
usingDefaultOp = true
// } else if op == nil {
// op = NewGolangFunctor(printLnFunc)
// usingDefaultOp = true
}
for item, err = it.Next(); err == nil; item, err = it.Next() {
if usingDefaultOp {
params = map[string]any{ParamItem: []any{item}}
} else {
params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
}
// if usingDefaultOp {
// params = map[string]any{ParamItem: []any{item}}
// } else {
// params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
// }
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = ToBool(v); !success || !ok {
if op != nil {
params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = ToBool(v); !success || !ok {
break
}
}
}
}
@ -86,6 +91,9 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
err = nil
}
if err == nil {
if op == nil {
ctx.UnsafeSetVar(iterVarStatus, it.Count())
}
result, _ = localCtx.GetVar(iterVarStatus)
}
return

149
builtin-os-file-iter.go Normal file
View File

@ -0,0 +1,149 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"fmt"
"io"
"slices"
)
const paramHandleOrPath = "handle-or-path"
const fileReadTextIteratorType = "fileReadTextIterator"
type fileReadTextIterator struct {
osReader *osReader
index int
count int
line string
autoClose bool
}
func newReadTextIterator(r *osReader, autoClose bool) *fileReadTextIterator {
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() int {
return it.count
}
func (it *fileReadTextIterator) Next() (item any, err error) { // must return io.EOF after the last item
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() int {
return it.index
}
func (it *fileReadTextIterator) Reset() (err error) {
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{NextName, ResetName, IndexName, CountName, CurrentName, CleanName}, name)
}
func (it *fileReadTextIterator) Clean() (err error) {
if it.osReader.fh != nil {
if err = it.osReader.fh.Close(); err == nil {
it.osReader = nil
}
}
return nil
}
func (it *fileReadTextIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case NextName:
v, err = it.Next()
case ResetName:
err = it.Reset()
case CleanName:
err = it.Clean()
case IndexName:
v = int64(it.Index())
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
default:
err = errNoOperation(name)
}
return
}
func fileReadIteratorFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var handle *osReader
var invalidFileHandle any
var ok, autoClose bool
result = nil
if handle, ok = args[paramHandleOrPath].(*osReader); !ok {
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
var handleAny any
if handleAny, err = openFileFunc(ctx, name, map[string]any{ParamFilepath: fileName}); err != nil {
return
}
if handleAny != nil {
handle = handleAny.(*osReader)
autoClose = true
}
} else {
invalidFileHandle = args[paramHandleOrPath]
}
}
if handle != nil {
result = newReadTextIterator(handle, autoClose)
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
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")
// }

View File

@ -250,6 +250,10 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamHandle),
})
ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
NewFuncParam(paramHandleOrPath),
})
}
func init() {

View File

@ -13,6 +13,7 @@ const (
TypeFileHandle = "file-handle"
TypeInt = "integer"
TypeItem = "item"
TypeIterator = "iterator"
TypeNumber = "number"
TypePair = "pair"
TypeString = "string"

View File

@ -68,42 +68,54 @@ func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone
var s string
it = &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
var argAny any
dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
if len(args) > 0 {
if s, err = ToGoString(args[0], "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
argAny = args[0]
} else {
argAny = "default"
}
if s, err = ToGoString(argAny, "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
}
if err == nil {
if len(args) > 1 {
argAny = args[1]
} else {
argAny = "default"
}
if err == nil && len(args) > 1 {
if s, err = ToGoString(args[1], "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
it.iterMode = dictIterModeKeys
case "v", "value", "values":
it.iterMode = dictIterModeValues
case "i", "item", "items":
it.iterMode = dictIterModeItems
case "", "default":
it.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
if s, err = ToGoString(argAny, "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
dictIt.iterMode = dictIterModeKeys
case "v", "value", "values":
dictIt.iterMode = dictIterModeValues
case "i", "item", "items":
dictIt.iterMode = dictIterModeItems
case "", "default":
dictIt.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
}
}
}
it.makeKeys(*dict, sortType)
return
dictIt.makeKeys(*dict, sortType)
return dictIt, err
}
func NewMapIterator(m map[any]any) (it *DictIterator) {
@ -117,7 +129,7 @@ func (it *DictIterator) String() string {
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
return fmt.Sprintf("$({#%d})", l)
}
func (it *DictIterator) TypeName() string {

View File

@ -34,7 +34,7 @@ Expressions calculator
toc::[]
#TODO: Work in progress (last update on 2026/04/15, 6:02 p.m.)#
#TODO: Work in progress (last update on 2026/04/21, 6:49 p.m.)#
== Expr
_Expr_ is a GO package that can analyze, interpret and calculate expressions.
@ -1036,13 +1036,13 @@ Builtins are collection of function dedicated to specific domains of application
There are currently several builtin modules. More builtin modules will be added in the future.
.Available builtin modules
* *base*: Base expression tools like isNil(), int(), etc.
* *fmt*: String and console formatting functions
* *import*: Functions import() and include()
* *iterator*: Iterator helper functions
* *math.arith*: Functions add() and mul()
* *os.file*: Operating system file functions
* *string*: string utilities
* <<_module_base,*base*>>: Base expression tools like isNil(), int(), etc.
* <<_module_fmt,*fmt*>>: String and console formatting functions
* <<_module_import,*import*>>: Functions <<_import,import()>> and <<_include,include()>>
* <<_module_iterator,*iterator*>>: Iterator helper functions
* <<_module_math_arith,*math.arith*>>: Functions <<_add,add()>> and <<_mul,mul()>>
* <<_module_os_file,*os.file*>>: Operating system file functions
* <<_module_string,*string*>>: string utilities
Builtins activation is done by using the [blue]`BUILTIN` operator. All modules except "base" must be explicitly enabled. The syntax is as follows.
@ -1087,6 +1087,8 @@ The "base" builtin module provides functions for type checking and type conversi
.Other functions
* <<_eval,eval()>>
* <<_set,set()>>
* <<_unset,unset()>>
* <<_var,var()>>
@ -1095,144 +1097,144 @@ Syntax: `isBool(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is boolean, false otherwise.
.Examples
>>> isBool(true) +
true +
>>> isBool(3==2) +
true
`>>>` [blue]`isBool(true)` +
[green]`true` +
`>>>` [blue]`isBool(3==2)` +
[green]`true`
===== isDict()
Syntax: `isDict(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is dictionary, false otherwise.
.Examples
>>> isDict({}) +
true +
>>> isDict({1: "one", 2: "two"}) +
true +
>>> isDict(1:"one") +
Eval Error: denominator must be integer, got string (one)
`>>>` [blue]`isDict({})` +
[green]`true` +
`>>>` [blue]`isDict({1: "one", 2: "two"})` +
[green]`true` +
`>>>` [blue]`isDict(1:"one")` +
[red]`Eval Error: denominator must be integer, got string (one)`
===== isFloat()
Syntax: `isFloat(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is float, false otherwise.
.Examples
>>> isFloat(4.) +
true +
>>> isFloat(4) +
false +
>>> isFloat("2.1") +
false
`>>>` [blue]`isFloat(4.)` +
[green]`true` +
`>>>` [blue]`isFloat(4)` +
[green]`false` +
`>>>` [blue]`isFloat("2.1")` +
[green]`false`
===== isFract()
Syntax: `isFract(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is fraction, false otherwise.
.Examples
>>> isFract(4.5) +
false +
>>> isFract(4:5) +
true +
>>> isFract(4) +
**false** +
>>> isFract(1.(3)) +
true
`>>>` [blue]`isFract(4.5)` +
[green]`false` +
`>>>` [blue]`isFract(4:5)` +
[green]`true` +
`>>>` [blue]`isFract(4)` +
[green]**`false`** +
`>>>` [blue]`isFract(1.(3))` +
[green]`true`
===== isList()
Syntax: `isList(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is list, false otherwise.
.Examples
>>> isList([]) +
true +
>>> isList([1, "2"])
true
>>> isList(1,2)
Eval Error: isList(): too many params -- expected 1, got 2
`>>>` [blue]`isList([])` +
[green]`true` +
`>>>` [blue]`isList([1, "2"])` +
[green]`true` +
`>>>` [blue]`isList(1,2)` +
[red]`Eval Error: isList(): too many params -- expected 1, got 2`
===== isNil()
Syntax: `isNil(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is nil, false otherwise.
.Examples
>>> isNil(nil)
true
>>> isNil(1)
false
`>>>` [blue]`isNil(nil)` +
[green]`true` +
`>>>` [blue]`isNil(1)` +
[green]`false`
===== isRational()
Syntax: `isRational(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is fraction or int, false otherwise.
.Examples
>>> isRational(4.5) +
false +
>>> isRational(4:5) +
true +
>>> isRational(4) +
**true** +
>>> isRational(1.(3)) +
true
`>>>` [blue]`isRational(4.5)` +
[green]`false` +
`>>>` [blue]`isRational(4:5)` +
[green]`true` +
`>>>` [blue]`isRational(4)` +
[green]**`true`** +
`>>>` [blue]`isRational(1.(3))` +
[green]`true`
===== isString()
Syntax: `isString(<expr>) -> bool` +
Returns a boolean value , false otherwise.
.Examples
>>> isString("ciao") +
true +
>>> isString(2) +
false +
>>> isString(2+"2") +
true
`>>>` [blue]`isString("ciao")` +
[green]`true` +
`>>>` [blue]`isString(2)` +
[green]`false` +
`>>>` [blue]`isString(2+"2")` +
[green]`true`
===== bool()
Syntax: `bool(<expr>) -> bool` +
Returns a _boolean_ value consisent to the value of the expression.
.Examples
>>> bool(1)
true
>>> bool(0)
false
>>> bool("")
false
>>> bool([])
false
>>> bool([1])
true
>>> bool({})
false
>>> bool({1: "one"})
true
`>>>` [blue]`bool(1)` +
[green]`true` +
`>>>` [blue]`bool(0)` +
[green]`false` +
`>>>` [blue]`bool("")` +
[green]`false` +
`>>>` [blue]`bool([])` +
[green]`false` +
`>>>` [blue]`bool([1])` +
[green]`true` +
`>>>` [blue]`bool({})` +
[green]`false` +
`>>>` [blue]`bool({1: "one"})` +
[green]`true`
===== int()
Syntax: `int(<expr>) -> int` +
Returns an _integer_ value consistent with the value of the expression.
.Examples
>>> int(2) +
2 +
>>> int("2") +
2 +
>>> int("0x1") +
Eval Error: strconv.Atoi: parsing "0x1": invalid syntax +
>>> int(0b10) +
2 +
>>> int(0o2) +
2 +
>>> int(0x2) +
2 +
>>> int(1.8) +
1 +
>>> int(5:2) +
2 +
>>> int([]) +
Eval Error: int(): can't convert list to int+
>>> int(true) +
1 +
>>> int(false) +
0
`>>>` [blue]`int(2)` +
[green]`2` +
`>>>` [blue]`int("2")` +
[green]`2` +
`>>>` [blue]`int("0x1")` +
[red]`Eval Error: strconv.Atoi: parsing "0x1": invalid syntax` +
`>>>` [blue]`int(0b10)` +
[green]`2` +
`>>>` [blue]`int(0o2)` +
[green]`2` +
`>>>` [blue]`int(0x2)` +
[green]`2` +
`>>>` [blue]`int(1.8)` +
[green]`1` +
`>>>` [blue]`int(5:2)` +
[green]`2` +
`>>>` [blue]`int([])` +
[red]`Eval Error: int(): can't convert list to int` +
`>>>` [blue]`int(true)` +
[green]`1` +
`>>>` [blue]`int(false)` +
[green]`0`
===== dec()
@ -1240,66 +1242,66 @@ Syntax: `dec(<expr>) -> float` +
Returns a _float_ value consistent with the value of the expression.
.Examples
>>> dec(2) +
2 +
>>> dec(2.1) +
2.1 +
>>> dec(2.3(1)) +
2.311111111111111 +
>>> dec("3.14") +
3.14 +
>>> dec(3:4) +
0.75 +
>>> dec(true) +
1 +
>>> dec(false) +
0
`>>>` [blue]`dec(2)` +
[green]`2` +
`>>>` [blue]`dec(2.1)` +
[green]`2.1` +
`>>>` [blue]`dec(2.3(1))` +
[green]`2.311111111111111` +
`>>>` [blue]`dec("3.14")` +
[green]`3.14` +
`>>>` [blue]`dec(3:4)` +
[green]`0.75` +
`>>>` [blue]`dec(true)` +
[green]`1` +
`>>>` [blue]`dec(false)` +
[green]`0`
===== string()
Syntax: `string(<expr>) -> string` +
Returns a _string_ value consistent with the value of the expression.
.Examples
>>> string(2) +
"2" +
>>> string(0.8) +
"0.8" +
>>> string([1,2]) +
"[1, 2]" +
>>> string({1: "one", 2: "two"}) +
"{1: "one", 2: "two"}" +
>>> string(2:5) +
"2:5" +
>>> string(3==2) +
"false"
`>>>` [blue]`string(2)` +
[green]`"2"` +
`>>>` [blue]`string(0.8)` +
[green]`"0.8"` +
`>>>` [blue]`string([1,2])` +
[green]`"[1, 2]"` +
`>>>` [blue]`string({1: "one", 2: "two"})` +
[green]`"{1: "one", 2: "two"}"` +
`>>>` [blue]`string(2:5)` +
[green]`"2:5"` +
`>>>` [blue]`string(3==2)` +
[green]`"false"`
===== fract()
Syntax: `fract(<expr>) -> fraction` +
Returns a _fraction_ value consistent with the value of the expression.
.Examples
>>> fract(2) +
2:1 +
>>> fract(2.5) +
5:2 +
>>> fract("2.5") +
5:2 +
>>> fract(1.(3)) +
4:3 +
>>> fract([2]) +
Eval Error: fract(): can't convert list to float +
>>> fract(false) +
0:1 +
>>> fract(true) +
1:1
`>>>` [blue]`fract(2)` +
[green]`2:1` +
`>>>` [blue]`fract(2.5)` +
[green]`5:2` +
`>>>` [blue]`fract("2.5")` +
[green]`5:2` +
`>>>` [blue]`fract(1.(3))` +
[green]`4:3` +
`>>>` [blue]`fract([2])` +
[red]`Eval Error: fract(): can't convert list to float` +
`>>>` [blue]`fract(false)` +
[green]`0:1` +
`>>>` [blue]`fract(true)` +
[green]`1:1`
===== eval()
Syntax: `eval(<string-expr>) -> any` +
Computes and returns the value of the [.underline]#string# expression.
.Examples
>>> eval( "2 + fract(1.(3))" ) +
10:3
`>>>` [blue]`eval( "2 + fract(1.(3))" )` +
[green]`10:3`
===== var()
Syntax: +
@ -1309,14 +1311,40 @@ Syntax: +
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.
.Examples
>>> var("$x", 3+9) +
12 +
>>> var("$x") +
12 +
>>> var("gain%", var("$x")) +
12 +
>>> var("gain%", var("$x")+1) +
13
`>>>` [blue]`var("$x", 3+9)` +
[green]`12` +
`>>>` [blue]`var("$x")` +
[green]`12` +
`>>>` [blue]`var("gain%", var("$x"))` +
[green]`12` +
`>>>` [blue]`var("gain%", var("$x")+1)` +
[green]`13`
===== set
Syntax: +
`{4sp}set(<string-expr>, <expr>) -> any`
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.
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.
.Examples
`>>>` [blue]`set("$x", 100)` +
[green]`100` +
`>>>` [blue]`var("$x")` +
[green]`100` +
===== unset()
Syntax: +
`{4sp}unset(<string-expr>) -> any`
This function allows you to unset a variable whose name can include special characters. The parameter is the name of the variable to unset.
.Examples
`>>>` [blue]`unset("$x")` +
[green]`nil` +
`>>>` [blue]`var("$x")` +
[red]`Eval Error: var(): unknown variable "$x"`
==== Module "fmt"
@ -1325,18 +1353,31 @@ This function allows you to define variables whose names must include special ch
===== println()
==== Module "import"
Module actiovation: +
===== _import()_
[blue]_import([grey]#<source-file>#)_ -- loads the multi-expression contained in the specified source and returns its value.
Syntax: +
`{4sp}import(<source-file>)`
Loads the multi-expression contained in the specified source and returns its value.
===== _importAll()_
==== Module "iterator"
===== run()
Syntax: +
`{4sp}run(<iterator>, <operator>, <vars>) -> any`
Iterates over the specified iterator and applies the specified operator to the current value of the iterator.
==== Module "math.arith"
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.
* <<_add,add()>>
* <<_mul,mul()>>
===== add()
Syntax: +
`{4sp}add(<num-expr1>, <num-expr2>, ...) -> any` +
@ -1346,22 +1387,24 @@ Syntax: +
Returns the sum of the values of the parameters. The parameters can be of any numeric type for which the [blue]`+` operator is defined. The result type depends on the types of the parameters. If all parameters are of the same type, the result is of that type. If the parameters are of different types, the result is of the type that can represent all the parameter types without loss of information. For example, if the parameters are a mix of integers and floats, the result is a float. If the parameters are a mix of number types, the result has the type of the most general one.
.Examples
>>> add(1,2,3) +
6
>>> add(1.1,2.1,3.1) +
6.300000000000001 +
>>> add("1","2","3") +
Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected +
>>> add(1:3, 2:3, 3:3) +
2:1 +
>>> add(1, "2") +
Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected +
>>> add([1,2,3]) +
6 +
>>> iterator=$([1,2,3]); add(iterator) +
6 +
>>> add($([1,2,3])) +
6
`>>>` [blue]`builtin "math.arith"` +
[green]`1` +
`>>>` [blue]`add(1,2,3)` +
[green]`6` +
`>>>` [blue]`add(1.1,2.1,3.1)` +
[green]`6.300000000000001` +
`>>>` [blue]`add("1","2","3")` +
[red]`Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected` +
`>>>` [blue]`add(1:3, 2:3, 3:3)` +
[green]`2:1` +
`>>>` [blue]`add(1, "2")` +
[red]`Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected` +
`>>>` [blue]`add([1,2,3])` +
[green]`6` +
`>>>` [blue]`iterator=$([1,2,3]); add(iterator)` +
[green]`6` +
`>>>` [blue]`add($([1,2,3]))` +
[green]`6`
===== mul()
Syntax: +
@ -1369,8 +1412,13 @@ Syntax: +
`{4sp}mul(<list-of-num-expr>]) -> any` +
`{4sp}mul(<iterator-over-num-values>) -> any`
Same as add() but returns the product of the values of the parameters.
Same as <<_add,add()>> but returns the product of the values of the parameters.
.Examples
`>>>` [blue]`builtin "math.arith"` +
[green]`1` +
`>>>` [blue]`mul(2,3,4)` +
[green]`24`
==== Module "os.file"
@ -1392,45 +1440,131 @@ Same as add() but returns the product of the values of the parameters.
==== Module "string"
===== strJoin()
Syntax: +
`{4sp}strJoin(<separator>, <item> ...) -> string`
Returns a string obtained by concatenating the string items (sarting from the second argument), separated by the string value of the separator.
.Examples
`>>>` [blue]`strJoin(", ", "one", "two", "three")` +
[green]`"one, two, three"` +
`>>>` [blue]`strJoin(", ", ["one", "two", "three"])` +
[green]`"one, two, three"`
===== strSub()
Syntax: +
`{4sp}strSub(<string>, <start-index>=0, <count>=-1) -> string`
Returns a substring of the specified string starting at the specified index and having the specified length. If the start index is negative, it is interpreted as an offset from the end of the string. If the count is negative, it means to take all characters until the end of the string.
.Examples
`>>>` [blue]`strSub("Hello, world!", 7, 5)` +
[green]`"world"` +
`>>>` [blue]`strSub("Hello, world!", -6, 5)` +
[green]`"world"` +
`>>>` [blue]`strSub("Hello, world!", 7)` +
[green]`"world!"` +
`>>>` [blue]`strSub("Hello, world!", -6)` +
[green]`"world!"`
===== strSplit()
Syntax: +
`{4sp}strSplit(<string>, <separator>="", <count>=-1) -> list`
Returns a list of substrings obtained by splitting the specified string using the specified separator. If the separator is an empty string, the string is split into individual characters. If the count is negative, it means to split all occurrences of the separator.
.Examples
`>>>` [blue]`strSplit("one, two, three", ", ")` +
[green]`["one", "two", "three"]` +
`>>>` [blue]`strSplit("one, two, three", ", ", 2)` +
[green]`["one", "two, three"]` +
`>>>` [blue]`strSplit("one, two, three")` +
[green]`["o", "n", "e", ",", " ", "t", "w", "o", ",", " ", "t", "h", "r", "e", "e"]`
===== strTrim()
Syntax: +
`{4sp}strTrim(<string>) -> string`
Returns a string obtained by removing leading and trailing whitespace characters from the specified string.
.Examples
`>>>` [blue]`strTrim(" Hello, world! ")` +
[green]`"Hello, world!"`
===== strStartsWith()
Syntax: +
`{4sp}strStartsWith(<string>, <prefix> ...) -> bool`
Returns a boolean indicating whether the specified string starts with any of the given prefixes.
.Examples
`>>>` [blue]`strStartsWith("Hello, world!", "Hello")` +
[green]`true` +
`>>>` [blue]`strStartsWith("Hello, world!", "world")` +
[green]`false` +
`>>>` [blue]`strStartsWith("Hello, world!", "Hi", "He")` +
[green]`true`
===== strEndsWith()
Syntax: +
`{4sp}strEndsWith(<string>, <suffix> ...) -> bool`
Returns a boolean indicating whether the specified string ends with any of the given suffixes.
.Examples
`>>>` [blue]`strEndsWith("Hello, world!", "world!")` +
[green]`true` +
`>>>` [blue]`strEndsWith("Hello, world!", "Hello")` +
[green]`false` +
`>>>` [blue]`strEndsWith("Hello, world!", "Hi", "world!")` +
[green]`true`
===== strUpper()
Syntax: +
`{4sp}strUpper(<string>) -> string`
Returns a string obtained by converting all characters of the specified string to uppercase.
.Examples
`>>>` [blue]`strUpper("Hello, world!")` +
[green]`"HELLO, WORLD!"`
===== strLower()
Syntax: +
`{4sp}strLower(<string>) -> string`
Returns a string obtained by converting all characters of the specified string to lowercase.
.Examples
`>>>` [blue]`strLower("Hello, world!")` +
[green]`"hello, world!"`
== Iterators
Iterators are objects that can be used to traverse collections, such as lists and dictionaries. They are created by providing a _data-source_ object, the collection, in a `$(<data-source>)` expression. Once an iterator is created, it can be used to access the elements of the collection one by one.
In general, data-sources are objects that can be iterated over. They are defined as dictionaries having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The _next_ function must return a special value, [blue]_nil_, when there are no more elements to iterate over.
Lists and, soon, dictionaries, are implicit data-sources. The syntax for creating an iterator is as follows.
Lists and dictionaries are implicit data-sources. The syntax for creating an iterator is as follows.
.Iterator creation syntax
====
*_iterator_* = "**$(**" _data-source_ "**)**" +
_data-source_ = _explicit_ | _list-spec_ | _dict-spec_ | _custom-data-source_
_explicit_ = _any-expr_ { "," _any-expr_ }
_explicit_ = _any-expr_ { "**,**" _any-expr_ }
_list-spec_ = _list_ _range-options_ +
_list_ = "**[**" _any-expr_ { "," _any-expr_ } "**]**" +
_range-options_ = [ "," _start-index_ [ "," _end-index_ [ "," _step_ ]]] +
_list_ = "**[**" _any-expr_ { "**,**" _any-expr_ } "**]**" +
_range-options_ = [ "**,**" _start-index_ [ "**,**" _end-index_ [ "**,**" _step_ ]]] +
_start-index_, _end-index_, _step_ = _integer-expr_
_dict-spec_ = _dict_ _dict-options_ +
_dict_ = "**{**" _key-value-pair_ { "," _key-value-pair_ } "**}**" +
_key-value-pair_ = _scalar-value_ ":" _any-expr_ +
_dict_ = "**{**" _key-value-pair_ { "**,**" _key-value-pair_ } "**}**" +
_key-value-pair_ = _scalar-value_ "**:**" _any-expr_ +
_scalar-value_ = _string_ | _number_ | _boolean_ +
_dict-options_ = [ "," _sort-order_ [ "," _iter-mode_ ] ] +
_dict-options_ = [ "**,**" _sort-order_ [ "**,**" _iter-mode_ ] ] +
_sort-order_ = _asc-order_ | _desc-order_ | _no-sort_ | _default-order_ +
_asc-order_ = "**asc**" | "**a**" +
_desc-order_ = "**desc**" | "**d**" +
@ -1473,7 +1607,7 @@ NOTE: Currently, _default-iter_ is the same as _keys-iter_. In the future, it wi
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
On a list-type iterator creation expression, it is possible to specify an index range and a step to iterate over a subset of the list.
When creating a list-type iterator expression, you can specify a range of indices and a step to iterate over a subset of the list.
Indexing starts at 0. If the start index is not specified, it defaults to 0. If the end index is not specified, it defaults to the index of the last element of the list. If the step is not specified, it defaults to 1.

View File

@ -2698,25 +2698,25 @@ The clone modifier <code class="blue">@</code> does not make a variable a refere
<div class="title">Available builtin modules</div>
<ul>
<li>
<p><strong>base</strong>: Base expression tools like isNil(), int(), etc.</p>
<p><a href="#_module_base"><strong>base</strong></a>: Base expression tools like isNil(), int(), etc.</p>
</li>
<li>
<p><strong>fmt</strong>: String and console formatting functions</p>
<p><a href="#_module_fmt"><strong>fmt</strong></a>: String and console formatting functions</p>
</li>
<li>
<p><strong>import</strong>: Functions import() and include()</p>
<p><a href="#_module_import"><strong>import</strong></a>: Functions <a href="#_import">import()</a> and <a href="#_include">include()</a></p>
</li>
<li>
<p><strong>iterator</strong>: Iterator helper functions</p>
<p><a href="#_module_iterator"><strong>iterator</strong></a>: Iterator helper functions</p>
</li>
<li>
<p><strong>math.arith</strong>: Functions add() and mul()</p>
<p><a href="#_module_math_arith"><strong>math.arith</strong></a>: Functions <a href="#_add">add()</a> and <a href="#_mul">mul()</a></p>
</li>
<li>
<p><strong>os.file</strong>: Operating system file functions</p>
<p><a href="#_module_os_file"><strong>os.file</strong></a>: Operating system file functions</p>
</li>
<li>
<p><strong>string</strong>: string utilities</p>
<p><a href="#_module_string"><strong>string</strong></a>: string utilities</p>
</li>
</ul>
</div>
@ -2830,10 +2830,10 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is boolean, fal
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isBool(true)<br>
true<br>
&gt;&gt;&gt; isBool(3==2)<br>
true</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isBool(true)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isBool(3==2)</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
@ -2844,12 +2844,12 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is dictionary,
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isDict({})<br>
true<br>
&gt;&gt;&gt; isDict({1: "one", 2: "two"})<br>
true<br>
&gt;&gt;&gt; isDict(1:"one")<br>
Eval Error: denominator must be integer, got string (one)</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isDict({})</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isDict({1: "one", 2: "two"})</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isDict(1:"one")</code><br>
<code class="red">Eval Error: denominator must be integer, got string (one)</code></p>
</div>
</div>
<div class="sect4">
@ -2860,12 +2860,12 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is float, false
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isFloat(4.)<br>
true<br>
&gt;&gt;&gt; isFloat(4)<br>
false<br>
&gt;&gt;&gt; isFloat("2.1")<br>
false</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isFloat(4.)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isFloat(4)</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isFloat("2.1")</code><br>
<code class="green">false</code></p>
</div>
</div>
<div class="sect4">
@ -2876,14 +2876,14 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is fraction, fa
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isFract(4.5)<br>
false<br>
&gt;&gt;&gt; isFract(4:5)<br>
true<br>
&gt;&gt;&gt; isFract(4)<br>
<strong>false</strong><br>
&gt;&gt;&gt; isFract(1.(3))<br>
true</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isFract(4.5)</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isFract(4:5)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isFract(4)</code><br>
<strong class="green"><code>false</code></strong><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isFract(1.(3))</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
@ -2894,12 +2894,12 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is list, false
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isList([])<br>
true<br>
&gt;&gt;&gt; isList([1, "2"])
true
&gt;&gt;&gt; isList(1,2)
Eval Error: isList(): too many params&#8201;&#8212;&#8201;expected 1, got 2</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isList([])</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isList([1, "2"])</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isList(1,2)</code><br>
<code class="red">Eval Error: isList(): too many params&#8201;&#8212;&#8201;expected 1, got 2</code></p>
</div>
</div>
<div class="sect4">
@ -2910,10 +2910,10 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is nil, false o
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isNil(nil)
true
&gt;&gt;&gt; isNil(1)
false</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isNil(nil)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isNil(1)</code><br>
<code class="green">false</code></p>
</div>
</div>
<div class="sect4">
@ -2924,14 +2924,14 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is fraction or
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isRational(4.5)<br>
false<br>
&gt;&gt;&gt; isRational(4:5)<br>
true<br>
&gt;&gt;&gt; isRational(4)<br>
<strong>true</strong><br>
&gt;&gt;&gt; isRational(1.(3))<br>
true</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isRational(4.5)</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isRational(4:5)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isRational(4)</code><br>
<strong class="green"><code>true</code></strong><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isRational(1.(3))</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
@ -2942,12 +2942,12 @@ Returns a boolean value , false otherwise.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; isString("ciao")<br>
true<br>
&gt;&gt;&gt; isString(2)<br>
false<br>
&gt;&gt;&gt; isString(2+"2")<br>
true</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">isString("ciao")</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isString(2)</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isString(2+"2")</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
@ -2958,20 +2958,20 @@ Returns a <em>boolean</em> value consisent to the value of the expression.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; bool(1)
true
&gt;&gt;&gt; bool(0)
false
&gt;&gt;&gt; bool("")
false
&gt;&gt;&gt; bool([])
false
&gt;&gt;&gt; bool([1])
true
&gt;&gt;&gt; bool({})
false
&gt;&gt;&gt; bool({1: "one"})
true</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">bool(1)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">bool(0)</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">bool("")</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">bool([])</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">bool([1])</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">bool({})</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">bool({1: "one"})</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
@ -2982,28 +2982,28 @@ Returns an <em>integer</em> value consistent with the value of the expression.</
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; int(2)<br>
2<br>
&gt;&gt;&gt; int("2")<br>
2<br>
&gt;&gt;&gt; int("0x1")<br>
Eval Error: strconv.Atoi: parsing "0x1": invalid syntax<br>
&gt;&gt;&gt; int(0b10)<br>
2<br>
&gt;&gt;&gt; int(0o2)<br>
2<br>
&gt;&gt;&gt; int(0x2)<br>
2<br>
&gt;&gt;&gt; int(1.8)<br>
1<br>
&gt;&gt;&gt; int(5:2)<br>
2<br>
&gt;&gt;&gt; int([])<br>
Eval Error: int(): can&#8217;t convert list to int+
&gt;&gt;&gt; int(true)<br>
1<br>
&gt;&gt;&gt; int(false)<br>
0</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">int(2)</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int("2")</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int("0x1")</code><br>
<code class="red">Eval Error: strconv.Atoi: parsing "0x1": invalid syntax</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(0b10)</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(0o2)</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(0x2)</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(1.8)</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(5:2)</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int([])</code><br>
<code class="red">Eval Error: int(): can&#8217;t convert list to int</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(true)</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">int(false)</code><br>
<code class="green">0</code></p>
</div>
</div>
<div class="sect4">
@ -3014,20 +3014,20 @@ Returns a <em>float</em> value consistent with the value of the expression.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; dec(2)<br>
2<br>
&gt;&gt;&gt; dec(2.1)<br>
2.1<br>
&gt;&gt;&gt; dec(2.3(1))<br>
2.311111111111111<br>
&gt;&gt;&gt; dec("3.14")<br>
3.14<br>
&gt;&gt;&gt; dec(3:4)<br>
0.75<br>
&gt;&gt;&gt; dec(true)<br>
1<br>
&gt;&gt;&gt; dec(false)<br>
0</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">dec(2)</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">dec(2.1)</code><br>
<code class="green">2.1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">dec(2.3(1))</code><br>
<code class="green">2.311111111111111</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">dec("3.14")</code><br>
<code class="green">3.14</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">dec(3:4)</code><br>
<code class="green">0.75</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">dec(true)</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">dec(false)</code><br>
<code class="green">0</code></p>
</div>
</div>
<div class="sect4">
@ -3038,18 +3038,18 @@ Returns a <em>string</em> value consistent with the value of the expression.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; string(2)<br>
"2"<br>
&gt;&gt;&gt; string(0.8)<br>
"0.8"<br>
&gt;&gt;&gt; string([1,2])<br>
"[1, 2]"<br>
&gt;&gt;&gt; string({1: "one", 2: "two"})<br>
"{1: "one", 2: "two"}"<br>
&gt;&gt;&gt; string(2:5)<br>
"2:5"<br>
&gt;&gt;&gt; string(3==2)<br>
"false"</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">string(2)</code><br>
<code class="green">"2"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">string(0.8)</code><br>
<code class="green">"0.8"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">string([1,2])</code><br>
<code class="green">"[1, 2]"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">string({1: "one", 2: "two"})</code><br>
<code class="green">"{1: "one", 2: "two"}"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">string(2:5)</code><br>
<code class="green">"2:5"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">string(3==2)</code><br>
<code class="green">"false"</code></p>
</div>
</div>
<div class="sect4">
@ -3060,20 +3060,20 @@ Returns a <em>fraction</em> value consistent with the value of the expression.</
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; fract(2)<br>
2:1<br>
&gt;&gt;&gt; fract(2.5)<br>
5:2<br>
&gt;&gt;&gt; fract("2.5")<br>
5:2<br>
&gt;&gt;&gt; fract(1.(3))<br>
4:3<br>
&gt;&gt;&gt; fract([2])<br>
Eval Error: fract(): can&#8217;t convert list to float<br>
&gt;&gt;&gt; fract(false)<br>
0:1<br>
&gt;&gt;&gt; fract(true)<br>
1:1</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">fract(2)</code><br>
<code class="green">2:1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fract(2.5)</code><br>
<code class="green">5:2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fract("2.5")</code><br>
<code class="green">5:2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fract(1.(3))</code><br>
<code class="green">4:3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fract([2])</code><br>
<code class="red">Eval Error: fract(): can&#8217;t convert list to float</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fract(false)</code><br>
<code class="green">0:1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fract(true)</code><br>
<code class="green">1:1</code></p>
</div>
</div>
<div class="sect4">
@ -3084,8 +3084,8 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; eval( "2 + fract(1.(3))" )<br>
10:3</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">eval( "2 + fract(1.(3))" )</code><br>
<code class="green">10:3</code></p>
</div>
</div>
<div class="sect4">
@ -3100,14 +3100,14 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; var("$x", 3+9)<br>
12<br>
&gt;&gt;&gt; var("$x")<br>
12<br>
&gt;&gt;&gt; var("gain%", var("$x"))<br>
12<br>
&gt;&gt;&gt; var("gain%", var("$x")+1)<br>
13</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">var("$x", 3+9)</code><br>
<code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("$x")</code><br>
<code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("gain%", var("$x"))</code><br>
<code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("gain%", var("$x")+1)</code><br>
<code class="green">13</code></p>
</div>
</div>
</div>
@ -3160,22 +3160,24 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; add(1,2,3)<br>
6
&gt;&gt;&gt; add(1.1,2.1,3.1)<br>
6.300000000000001<br>
&gt;&gt;&gt; add("1","2","3")<br>
Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected<br>
&gt;&gt;&gt; add(1:3, 2:3, 3:3)<br>
2:1<br>
&gt;&gt;&gt; add(1, "2")<br>
Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected<br>
&gt;&gt;&gt; add([1,2,3])<br>
6<br>
&gt;&gt;&gt; iterator=$([1,2,3]); add(iterator)<br>
6<br>
&gt;&gt;&gt; add($([1,2,3]))<br>
6</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">builtin "math.arith"</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add(1,2,3)</code><br>
<code class="green">6</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add(1.1,2.1,3.1)</code><br>
<code class="green">6.300000000000001</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add("1","2","3")</code><br>
<code class="red">Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add(1:3, 2:3, 3:3)</code><br>
<code class="green">2:1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add(1, "2")</code><br>
<code class="red">Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add([1,2,3])</code><br>
<code class="green">6</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">iterator=$([1,2,3]); add(iterator)</code><br>
<code class="green">6</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">add($([1,2,3]))</code><br>
<code class="green">6</code></p>
</div>
</div>
<div class="sect4">
@ -3187,7 +3189,14 @@ Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected<br
<code>&#160;&#160;&#160;&#160;mul(&lt;iterator-over-num-values&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>Same as add() but returns the product of the values of the parameters.</p>
<p>Same as <a href="#_add">add()</a> but returns the product of the values of the parameters.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">builtin "math.arith"</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">mul(2,3,4)</code><br>
<code class="green">24</code></p>
</div>
</div>
</div>
@ -3226,35 +3235,143 @@ Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected<br
<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="sect4">
<h5 id="_strjoin"><a class="anchor" href="#_strjoin"></a><a class="link" href="#_strjoin">strJoin()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strJoin(&lt;separator&gt;, &lt;item&gt; &#8230;&#8203;) &#8594; string</code></p>
</div>
<div class="paragraph">
<p>Returns a string obtained by concatenating the string items (sarting from the second argument), separated by the string value of the separator.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strJoin(", ", "one", "two", "three")</code><br>
<code class="green">"one, two, three"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strJoin(", ", ["one", "two", "three"])</code><br>
<code class="green">"one, two, three"</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strsub"><a class="anchor" href="#_strsub"></a><a class="link" href="#_strsub">strSub()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strSub(&lt;string&gt;, &lt;start-index&gt;=0, &lt;count&gt;=-1) &#8594; string</code></p>
</div>
<div class="paragraph">
<p>Returns a substring of the specified string starting at the specified index and having the specified length. If the start index is negative, it is interpreted as an offset from the end of the string. If the count is negative, it means to take all characters until the end of the string.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strSub("Hello, world!", 7, 5)</code><br>
<code class="green">"world"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strSub("Hello, world!", -6, 5)</code><br>
<code class="green">"world"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strSub("Hello, world!", 7)</code><br>
<code class="green">"world!"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strSub("Hello, world!", -6)</code><br>
<code class="green">"world!"</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strsplit"><a class="anchor" href="#_strsplit"></a><a class="link" href="#_strsplit">strSplit()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strSplit(&lt;string&gt;, &lt;separator&gt;="", &lt;count&gt;=-1) &#8594; list</code></p>
</div>
<div class="paragraph">
<p>Returns a list of substrings obtained by splitting the specified string using the specified separator. If the separator is an empty string, the string is split into individual characters. If the count is negative, it means to split all occurrences of the separator.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strSplit("one, two, three", ", ")</code><br>
<code class="green">["one", "two", "three"]</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strSplit("one, two, three", ", ", 2)</code><br>
<code class="green">["one", "two, three"]</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strSplit("one, two, three")</code><br>
<code class="green">["o", "n", "e", ",", " ", "t", "w", "o", ",", " ", "t", "h", "r", "e", "e"]</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strtrim"><a class="anchor" href="#_strtrim"></a><a class="link" href="#_strtrim">strTrim()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strTrim(&lt;string&gt;) &#8594; string</code></p>
</div>
<div class="paragraph">
<p>Returns a string obtained by removing leading and trailing whitespace characters from the specified string.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strTrim(" Hello, world! ")</code><br>
<code class="green">"Hello, world!"</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strstartswith"><a class="anchor" href="#_strstartswith"></a><a class="link" href="#_strstartswith">strStartsWith()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strStartsWith(&lt;string&gt;, &lt;prefix&gt; &#8230;&#8203;) &#8594; bool</code></p>
</div>
<div class="paragraph">
<p>Returns a boolean indicating whether the specified string starts with any of the given prefixes.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strStartsWith("Hello, world!", "Hello")</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strStartsWith("Hello, world!", "world")</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strStartsWith("Hello, world!", "Hi", "He")</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strendswith"><a class="anchor" href="#_strendswith"></a><a class="link" href="#_strendswith">strEndsWith()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strEndsWith(&lt;string&gt;, &lt;suffix&gt; &#8230;&#8203;) &#8594; bool</code></p>
</div>
<div class="paragraph">
<p>Returns a boolean indicating whether the specified string ends with any of the given suffixes.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strEndsWith("Hello, world!", "world!")</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strEndsWith("Hello, world!", "Hello")</code><br>
<code class="green">false</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">strEndsWith("Hello, world!", "Hi", "world!")</code><br>
<code class="green">true</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strupper"><a class="anchor" href="#_strupper"></a><a class="link" href="#_strupper">strUpper()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strUpper(&lt;string&gt;) &#8594; string</code></p>
</div>
<div class="paragraph">
<p>Returns a string obtained by converting all characters of the specified string to uppercase.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strUpper("Hello, world!")</code><br>
<code class="green">"HELLO, WORLD!"</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_strlower"><a class="anchor" href="#_strlower"></a><a class="link" href="#_strlower">strLower()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;strLower(&lt;string&gt;) &#8594; string</code></p>
</div>
<div class="paragraph">
<p>Returns a string obtained by converting all characters of the specified string to lowercase.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">strLower("Hello, world!")</code><br>
<code class="green">"hello, world!"</code></p>
</div>
</div>
</div>
</div>
@ -3270,7 +3387,7 @@ Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected<br
<p>In general, data-sources are objects that can be iterated over. They are defined as dictionaries having 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 <em>next</em> function must return a special value, <em class="blue">nil</em>, when there are no more elements to iterate over.</p>
</div>
<div class="paragraph">
<p>Lists and, soon, dictionaries, are implicit data-sources. The syntax for creating an iterator is as follows.</p>
<p>Lists and dictionaries are implicit data-sources. The syntax for creating an iterator is as follows.</p>
</div>
<div class="exampleblock">
<div class="title">Example 17. Iterator creation syntax</div>
@ -3280,20 +3397,20 @@ Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected<br
<em>data-source</em> = <em>explicit</em> | <em>list-spec</em> | <em>dict-spec</em> | <em>custom-data-source</em></p>
</div>
<div class="paragraph">
<p><em>explicit</em> = <em>any-expr</em> { "," <em>any-expr</em> }</p>
<p><em>explicit</em> = <em>any-expr</em> { "<strong>,</strong>" <em>any-expr</em> }</p>
</div>
<div class="paragraph">
<p><em>list-spec</em> = <em>list</em> <em>range-options</em><br>
<em>list</em> = "<strong>[</strong>" <em>any-expr</em> { "," <em>any-expr</em> } "<strong>]</strong>"<br>
<em>range-options</em> = [ "," <em>start-index</em> [ "," <em>end-index</em> [ "," <em>step</em> ]]]<br>
<em>list</em> = "<strong>[</strong>" <em>any-expr</em> { "<strong>,</strong>" <em>any-expr</em> } "<strong>]</strong>"<br>
<em>range-options</em> = [ "<strong>,</strong>" <em>start-index</em> [ "<strong>,</strong>" <em>end-index</em> [ "<strong>,</strong>" <em>step</em> ]]]<br>
<em>start-index</em>, <em>end-index</em>, <em>step</em> = <em>integer-expr</em></p>
</div>
<div class="paragraph">
<p><em>dict-spec</em> = <em>dict</em> <em>dict-options</em><br>
<em>dict</em> = "<strong>{</strong>" <em>key-value-pair</em> { "," <em>key-value-pair</em> } "<strong>}</strong>"<br>
<em>key-value-pair</em> = <em>scalar-value</em> ":" <em>any-expr</em><br>
<em>dict</em> = "<strong>{</strong>" <em>key-value-pair</em> { "<strong>,</strong>" <em>key-value-pair</em> } "<strong>}</strong>"<br>
<em>key-value-pair</em> = <em>scalar-value</em> "<strong>:</strong>" <em>any-expr</em><br>
<em>scalar-value</em> = <em>string</em> | <em>number</em> | <em>boolean</em><br>
<em>dict-options</em> = [ "," <em>sort-order</em> [ "," <em>iter-mode</em> ] ]<br>
<em>dict-options</em> = [ "<strong>,</strong>" <em>sort-order</em> [ "<strong>,</strong>" <em>iter-mode</em> ] ]<br>
<em>sort-order</em> = <em>asc-order</em> | <em>desc-order</em> | <em>no-sort</em> | <em>default-order</em><br>
<em>asc-order</em> = "<strong>asc</strong>" | "<strong>a</strong>"<br>
<em>desc-order</em> = "<strong>desc</strong>" | "<strong>d</strong>"<br>
@ -3361,7 +3478,7 @@ Currently, <em>default-iter</em> is the same as <em>keys-iter</em>. In the futur
<code class="red">Eval Error: EOF</code></p>
</div>
<div class="paragraph">
<p>On a list-type iterator creation expression, it is possible to specify an index range and a step to iterate over a subset of the list.</p>
<p>When creating a list-type iterator expression, you can specify a range of indices and a step to iterate over a subset of the list.</p>
</div>
<div class="paragraph">
<p>Indexing starts at 0. If the start index is not specified, it defaults to 0. If the end index is not specified, it defaults to the index of the last element of the list. If the step is not specified, it defaults to 1.</p>
@ -3493,7 +3610,7 @@ Iterators built on custom data-sources can provide additional named operators, d
</div>
<div id="footer">
<div id="footer-text">
Last updated 2026-04-19 15:03:48 +0200
Last updated 2026-04-21 06:35:14 +0200
</div>
</div>
</body>

View File

@ -76,6 +76,12 @@ func isFile(filePath string) bool {
func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
suffix := SHAREDLIBRARY_EXTENSION
if strings.HasSuffix(filename, ".debug") {
suffix += ".debug"
}
for _, dir := range dirList {
if dir, err = ExpandPath(dir); err != nil {
continue
@ -84,6 +90,12 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
filePath = fullPath
break
}
subdir := strings.TrimSuffix(filename, suffix)
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}

25
iter-factory.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-factory.go
package expr
func NewIterator(value any) (it Iterator, err error) {
if value == nil {
return NewArrayIterator([]any{}), nil
}
switch v := value.(type) {
case *ListType:
it = NewListIterator(v, nil)
case *DictType:
it, err = NewDictIterator(v, nil)
case []any:
it = NewArrayIterator(v)
case Iterator:
it = v
default:
it = NewArrayIterator([]any{value})
}
return
}

View File

@ -27,6 +27,7 @@ const (
type Iterator interface {
Typer
fmt.Stringer
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int

9
lib-ext-darwin.go Normal file
View File

@ -0,0 +1,9 @@
//go:build darwin
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-darwin.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dylib"

9
lib-ext-linux.go Normal file
View File

@ -0,0 +1,9 @@
//go:build linux
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-linux.go
package expr
const SHAREDLIBRARY_EXTENSION = ".so"

9
lib-ext-windows.go Normal file
View File

@ -0,0 +1,9 @@
//go:build windows
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-windows.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dll"

View File

@ -62,27 +62,12 @@ func NewArrayIterator(array []any) (it *ListIterator) {
return
}
func NewAnyIterator(value any) (it *ListIterator) {
if value == nil {
it = NewArrayIterator([]any{})
} else if list, ok := value.(*ListType); ok {
it = NewListIterator(list, nil)
} else if array, ok := value.([]any); ok {
it = NewArrayIterator(array)
} else if it1, ok := value.(*ListIterator); ok {
it = it1
} else {
it = NewArrayIterator([]any{value})
}
return
}
func (it *ListIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
return fmt.Sprintf("$([#%d])", l)
}
func (it *ListIterator) TypeName() string {

View File

@ -26,9 +26,9 @@ func newList(listAny []any) (list *ListType) {
func NewList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
// for i, item := range listAny {
// ls[i] = item
// }
// for i, item := range listAny {
// ls[i] = item
// }
copy(ls, listAny)
list = &ls
}
@ -53,14 +53,14 @@ func ListFromStrings(stringList []string) (list *ListType) {
}
func (ls *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
@ -129,6 +129,20 @@ func (ls *ListType) contains(t *ListType) (answer bool) {
return
}
func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls1) == len(ls2) {
answer = true
for index, i1 := range *ls1 {
// if i1 != (ls2)[index] {
if !reflect.DeepEqual(i1, ls2[index]) {
answer = false
break
}
}
}
return
}
func (list *ListType) indexDeepSameCmp(target any) (index int) {
var eq bool
var err error
@ -193,3 +207,6 @@ func (list *ListType) setItem(index int64, value any) (err error) {
return
}
func (list *ListType) appendItem(value any) {
*list = append(*list, value)
}

View File

@ -31,7 +31,10 @@ func evalBuiltin(ctx ExprContext, opTerm *term) (v any, err error) {
count, err = ImportInContextByGlobPattern(module)
} else {
var moduleSpec any
it := NewAnyIterator(childValue)
var it Iterator
if it, err = NewIterator(childValue); err != nil {
return
}
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(module) {

70
operator-digest.go Normal file
View File

@ -0,0 +1,70 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-digest.go
package expr
import (
"fmt"
"io"
)
//-------- digest term
func newDigestTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priIterOp,
evalFunc: evalDigest,
}
}
func evalDigest(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var it Iterator
var item, lastValue any
if err = opTerm.checkOperands(); err != nil {
return
}
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return
}
if it, err = NewIterator(leftValue); err != nil {
return nil, fmt.Errorf("left operand of DIGEST must be an iterable data-source; got %s", TypeName(leftValue))
}
lastValue = nil
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index())
ctx.SetVar("_count", it.Count())
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
if rightValue == nil {
break
} else {
lastValue = rightValue
}
}
ctx.DeleteVar("_count")
ctx.DeleteVar("_index")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
v = lastValue
return
}
// init
func init() {
registerTermConstructor(SymKwDigest, newDigestTerm)
}

72
operator-filter.go Normal file
View File

@ -0,0 +1,72 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-filter.go
package expr
import (
"fmt"
"io"
)
//-------- map term
func newFilterTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priIterOp,
evalFunc: evalFilter,
}
}
func evalFilter(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var it Iterator
var item any
if err = opTerm.checkOperands(); err != nil {
return
}
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return
}
if it, err = NewIterator(leftValue); err != nil {
return nil, fmt.Errorf("left operand of FILTER must be an iterable data-source; got %s", TypeName(leftValue))
}
values := newListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index())
ctx.SetVar("_count", it.Count())
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
if success, valid := 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("_count")
ctx.DeleteVar("_index")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
registerTermConstructor(SymKwFilter, newFilterTerm)
}

65
operator-join.go Normal file
View File

@ -0,0 +1,65 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-join.go
package expr
import (
"fmt"
"io"
)
//-------- join term
func newJoinTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priIterOp,
evalFunc: evalJoin,
}
}
func evalJoin(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var itLeft, itRight Iterator
var item any
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, err = NewIterator(leftValue); err != nil {
return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", TypeName(leftValue))
}
if itRight, err = NewIterator(rightValue); err != nil {
return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", TypeName(rightValue))
}
values := newListA()
for _, it := range []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
return
}
// init
func init() {
registerTermConstructor(SymKwJoin, newJoinTerm)
}

66
operator-map.go Normal file
View File

@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-map.go
package expr
import (
"fmt"
"io"
)
//-------- map term
func newMapTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priIterOp,
evalFunc: evalMap,
}
}
func evalMap(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var it Iterator
var item any
if err = opTerm.checkOperands(); err != nil {
return
}
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return
}
if it, err = NewIterator(leftValue); err != nil {
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", TypeName(leftValue))
}
values := newListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("_index", it.Index())
ctx.SetVar("_count", it.Count())
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
values.appendItem(rightValue)
}
ctx.DeleteVar("_count")
ctx.DeleteVar("_index")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
registerTermConstructor(SymKwMap, newMapTerm)
}

View File

@ -31,11 +31,11 @@ func pluginExists(name string) (exists bool) {
func makePluginName(name string) (decorated string) {
var template string
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
template = "expr-%s-plugin.so"
template = "expr-%s-plugin%s"
} else {
template = "expr-%s-plugin.so.debug"
template = "expr-%s-plugin%s.debug"
}
decorated = fmt.Sprintf(template, name)
decorated = fmt.Sprintf(template, name, SHAREDLIBRARY_EXTENSION)
return
}
@ -93,10 +93,12 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
func importPluginFromSearchPath(name any) (count int, err error) {
var moduleSpec any
var it Iterator
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count = 0
it := NewAnyIterator(name)
if it, err = NewIterator(name); err != nil {
return
}
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {

View File

@ -210,14 +210,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.makeToken(SymQuote, ch)
escape = false
} else {
tk = scanner.fetchString(ch)
tk = scanner.fetchString(ch, true)
}
case '"':
if escape {
tk = scanner.makeToken(SymDoubleQuote, ch)
escape = false
} else {
tk = scanner.fetchString(ch)
tk = scanner.fetchString(ch, true)
}
case '`':
tk = scanner.makeToken(SymBackTick, ch)
@ -315,6 +315,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk.source += ")"
} else if next == '$' {
tk = scanner.moveOn(SymDoubleDollar, ch, next)
} else if next == '{' {
scanner.readChar()
if tk = scanner.fetchString('}', false); tk != nil {
tk.Sym = SymIdentifier
}
} else if next == '_' || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
scanner.readChar()
tk = scanner.fetchIdentifier(next)
} else {
tk = scanner.makeToken(SymDollar, ch)
}
@ -590,7 +598,7 @@ func (scanner *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (
return
}
func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
func (scanner *scanner) fetchString(termCh byte, addQuote bool) (tk *Token) {
var err error
var ch, prev byte
var sb strings.Builder
@ -628,7 +636,11 @@ func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
}
} else {
txt := sb.String()
tk = scanner.makeValueToken(SymString, `"`+txt+`"`, txt)
if addQuote {
tk = scanner.makeValueToken(SymString, `"`+txt+`"`, txt)
} else {
tk = scanner.makeValueToken(SymString, txt, txt)
}
}
return
}

View File

@ -135,6 +135,10 @@ func init() {
SymKwNot: {"not", symClassOperator, posInfix},
SymKwOr: {"or", symClassOperator, posInfix},
SymKwBut: {"but", symClassOperator, posInfix},
SymKwMap: {"map", symClassOperator, posInfix},
SymKwFilter: {"filter", symClassOperator, posInfix},
SymKwDigest: {"digest", symClassOperator, posInfix},
SymKwJoin: {"join", symClassOperator, posInfix},
SymKwFunc: {"func(", symClassDeclaration, posPrefix},
SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
SymKwPlugin: {"plugin", symClassOperator, posPrefix},

View File

@ -119,6 +119,10 @@ const (
SymKwPlugin
SymKwIn
SymKwInclude
SymKwMap
SymKwFilter
SymKwDigest
SymKwJoin
SymKwNil
SymKwUnset
)
@ -135,9 +139,13 @@ func init() {
"FUNC": SymKwFunc,
"IN": SymKwIn,
"INCLUDE": SymKwInclude,
"MAP": SymKwMap,
"FILTER": SymKwFilter,
"NOT": SymKwNot,
"OR": SymKwOr,
"NIL": SymKwNil,
"UNSET": SymKwUnset,
"DIGEST": SymKwDigest,
"JOIN": SymKwJoin,
}
}

View File

@ -21,10 +21,11 @@ func TestFuncRun(t *testing.T) {
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
/* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil},
/* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`},
/* 10 */ {`builtin ["os.file", "iterator"]; it = fileReadIterator("test-file.txt"); run(it)`, nil, nil},
}
//t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
runTestSuiteSpec(t, section, inputs, 10)
// runTestSuite(t, section, inputs)
}

View File

@ -5,6 +5,7 @@
package expr
import (
"io"
"testing"
)
@ -26,10 +27,16 @@ func TestFuncOs(t *testing.T) {
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`},
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`},
/* 16 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++`, "uno", nil},
/* 17 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++;it++;it++`, nil, io.EOF.Error()},
/* 18 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it.clean`, nil, nil},
/* 19 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); string(it)`, `$(fileReadTextIterator@"test-file.txt")`, nil},
/* 20 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle); string(handle)`, `reader`, nil},
/* 21 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle); string(handle)`, `writer`, nil},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 2)
// runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}

View File

@ -72,6 +72,7 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
var expr Expr
var gotResult any
var gotErr error
var eq, eqDone bool
wantErr := getWantedError(input)
@ -90,7 +91,18 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if input.wantResult != nil && gotResult != nil {
if ls1, ok := input.wantResult.(*ListType); ok {
if ls2, ok := gotResult.(*ListType); ok {
eq = ls1.Equals(*ls2)
eqDone = true
}
}
}
if !eqDone {
eq = reflect.DeepEqual(gotResult, input.wantResult)
}
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))

View File

@ -43,9 +43,11 @@ func TestExpr(t *testing.T) {
it++;
it++
`, int64(1), nil},
/* 20 */ {`a=2; ${a}`, int64(2), nil},
/* 21 */ {`$_=2; $_`, int64(2), nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 18)
// runTestSuiteSpec(t, section, inputs, 21)
runTestSuite(t, section, inputs)
}

View File

@ -59,7 +59,7 @@ func TestNewIterList2(t *testing.T) {
func TestNewIterList3(t *testing.T) {
list := []any{"a", "b", "c", "d"}
it := NewAnyIterator(list)
it, _ := NewIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
@ -71,7 +71,7 @@ func TestNewIterList3(t *testing.T) {
func TestNewIterList4(t *testing.T) {
list := any(nil)
it := NewAnyIterator(list)
it, _ := NewIterator(list)
if _, err := it.Next(); err != io.EOF {
t.Errorf("error: %v", err)
}
@ -79,7 +79,7 @@ func TestNewIterList4(t *testing.T) {
func TestNewIterList5(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
it, _ := NewIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "123" {
@ -91,8 +91,8 @@ func TestNewIterList5(t *testing.T) {
func TestNewIterList6(t *testing.T) {
list := newListA("a", "b", "c", "d")
it1 := NewAnyIterator(list)
it := NewAnyIterator(it1)
it1, _ := NewIterator(list)
it, _ := NewIterator(it1)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
@ -103,9 +103,9 @@ func TestNewIterList6(t *testing.T) {
}
func TestNewString(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
if s := it.String(); s != "$(#1)" {
t.Errorf("expected $(#1), got %s", s)
it, _ := NewIterator(list)
if s := it.String(); s != "$([#1])" {
t.Errorf("expected $([#1]), got %s", s)
}
}

View File

@ -31,8 +31,11 @@ func TestIteratorParser(t *testing.T) {
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
/* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil},
/* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, NewList([]any{int64(1), "one"}), nil},
/* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${_index}`, NewList([]any{int64(0), int64(1)}), nil},
/* 24 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil},
/* 25 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil},
}
// runTestSuiteSpec(t, section, inputs, 20)
// runTestSuiteSpec(t, section, inputs, 23)
runTestSuite(t, section, inputs)
}

View File

@ -30,13 +30,19 @@ func TestOperator(t *testing.T) {
/* 17 */ {`~true`, nil, `[1:2] prefix/postfix operator "~" do not support operand 'true' [bool]`},
/* 18 */ {`1^2`, int64(3), nil},
/* 19 */ {`3^2`, int64(1), nil},
/* 19 */ {`a=1; a^=2`, int64(3), nil},
/* 20 */ {`a=1; ++a`, int64(2), nil},
/* 21 */ {`a=1; --a`, int64(0), nil},
/* 20 */ {`a=1; a^=2`, int64(3), nil},
/* 21 */ {`a=1; ++a`, int64(2), nil},
/* 22 */ {`a=1; --a`, int64(0), nil},
/* 23 */ {`[1,2,3] map var("_")`, newList([]any{int64(1), int64(2), int64(3)}), nil},
/* 24 */ {`[1,2,3] map $_`, newList([]any{int64(1), int64(2), int64(3)}), nil},
/* 25 */ {`[1,2,3,4] filter ($_ % 2 == 0)`, newList([]any{int64(2), int64(4)}), nil},
/* 26 */ {`max=0; [2,3,1] digest max=(($_ > max) ? {$_} :: {max})`, int64(3), nil},
/* 27 */ {`["a","b"] join ["x"]`, newList([]any{"a", "b", "x"}), nil},
/* 28 */ {`["a","b"] join ["x"-true]`, nil, `[1:21] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 4)
// runTestSuiteSpec(t, section, inputs, 28)
runTestSuite(t, section, inputs)
}

View File

@ -30,9 +30,29 @@ func TestPluginExists(t *testing.T) {
func TestMakePluginName(t *testing.T) {
name := "json"
want := "expr-" + name + "-plugin.so"
want := "expr-" + name + "-plugin" + SHAREDLIBRARY_EXTENSION
if got := makePluginName(name); got != want {
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", name, got, want)
}
}
// func TestLoadPluginName(t *testing.T) {
// name := "json"
// // want := "expr-" + name + "-plugin
// want := 1
// os.Setenv("PLUGINS", "${HOME}/go/src/git.portale-stac.it/go")
// // os.Setenv("EXPR_PLUGIN_PATH", "${PLUGINS}/expr-json-plugin:${PLUGINS}/expr-csv-plugin")
// os.Setenv("EXPR_PLUGIN_PATH", "${PLUGINS}")
// got, err := importPluginFromSearchPath(name)
// if err != nil {
// t.Errorf("importPluginFromSearchPath(%q) failed: %v", name, err)
// return
// }
// if got != want {
// t.Errorf("importPluginFromSearchPath(%q) failed: Got: %q, Want: %q", name, got, want)
// }
// }

118
t_utils-unix_test.go Normal file
View File

@ -0,0 +1,118 @@
//go:build unix
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_utils_test.go
package expr
import (
"errors"
"os/user"
"path"
"testing"
)
func TestExpandPathRootOk(t *testing.T) {
source := "~root"
wantValue := "/root"
// wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil)
gotValue, gotErr := ExpandPath(source)
if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestExpandPathRootSubDirOk(t *testing.T) {
source := "~root/test"
wantValue := "/root/test"
// wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil)
gotValue, gotErr := ExpandPath(source)
if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestExpandPathUser(t *testing.T) {
u, _ := user.Current()
source := "~"
wantValue := path.Join("/home", u.Username)
// wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil)
gotValue, gotErr := ExpandPath(source)
if gotErr != nil {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestExpandPathEnv(t *testing.T) {
u, _ := user.Current()
source := "$HOME"
wantValue := path.Join("/home", u.Username)
// wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil)
gotValue, gotErr := ExpandPath(source)
if gotErr != nil {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestExpandPathErr(t *testing.T) {
source := "~fake-user/test"
wantValue := "~fake-user/test"
wantErr := errors.New(`user: unknown user fake-user`)
gotValue, gotErr := ExpandPath(source)
if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestExpandPathUserErr(t *testing.T) {
u, _ := user.Current()
source := "~"
wantValue := path.Join("/home", u.Username)
// wantErr := errors.New(`test expected string, got list ([])`)
wantErr := error(nil)
gotValue, gotErr := ExpandPath(source)
if gotErr != nil {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ExpandPath(%v) gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}

View File

@ -164,3 +164,48 @@ func TestCopyMap(t *testing.T) {
t.Errorf("utils.CopyMap() failed")
}
}
func TestToStringOk(t *testing.T) {
source := "ciao"
wantValue := "ciao"
wantErr := error(nil)
gotValue, gotErr := ToGoString(source, "test")
if gotErr != nil {
t.Error(gotErr)
} else if gotValue != wantValue {
t.Errorf("toGoString(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestToStringErr(t *testing.T) {
source := newListA()
wantValue := ""
wantErr := errors.New(`test expected string, got list ([])`)
gotValue, gotErr := ToGoString(source, "test")
if gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf(`ToGoString(%v, "test") gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
} else if gotValue != wantValue {
t.Errorf(`ToString(%v, "test") gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestIsFunctorSucc(t *testing.T) {
f := NewGolangFunctor(isNilFunc)
if !isFunctor(f) {
t.Errorf("isNilFunc() evalued as not a functor")
}
}
func TestIsFunctorFail(t *testing.T) {
f := int64(1)
if isFunctor(f) {
t.Errorf("int evalued as a functor")
}
}

View File

@ -13,6 +13,7 @@ type termPriority uint32
const (
priNone termPriority = iota
priRange
priIterOp // map, filter, digest, etc
priBut
priAssign
priInsert

48
utils-unix.go Normal file
View File

@ -0,0 +1,48 @@
//go:build unix
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils-unix.go
package expr
import (
"os"
"os/user"
"path"
"strings"
)
func ExpandPath(sourcePath string) (expandedPath string, err error) {
for expandedPath = os.ExpandEnv(sourcePath); expandedPath != sourcePath; expandedPath = os.ExpandEnv(sourcePath) {
sourcePath = expandedPath
}
if strings.HasPrefix(sourcePath, "~") {
var home, userName, remainder string
slashPos := strings.IndexRune(sourcePath, '/')
if slashPos > 0 {
userName = sourcePath[1:slashPos]
remainder = sourcePath[slashPos:]
} else {
userName = sourcePath[1:]
}
if len(userName) == 0 {
home, err = os.UserHomeDir()
if err != nil {
return
}
} else {
var userInfo *user.User
userInfo, err = user.Lookup(userName)
if err != nil {
return
}
home = userInfo.HomeDir
}
expandedPath = path.Join(home, remainder)
}
return
}

18
utils-windows.go Normal file
View File

@ -0,0 +1,18 @@
//go:build windows
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils-unix.go
package expr
import (
"os"
)
func ExpandPath(sourcePath string) (expandedPath string, err error) {
for expandedPath = os.ExpandEnv(sourcePath); expandedPath != sourcePath; expandedPath = os.ExpandEnv(sourcePath) {
sourcePath = expandedPath
}
return
}

View File

@ -6,11 +6,7 @@ package expr
import (
"fmt"
"os"
"os/user"
"path"
"reflect"
"strings"
)
func IsString(v any) (ok bool) {
@ -234,37 +230,3 @@ func ForAll[T, V any](ts []T, fn func(T) V) []V {
}
return result
}
func ExpandPath(sourcePath string) (expandedPath string, err error) {
for expandedPath = os.ExpandEnv(sourcePath); expandedPath != sourcePath; expandedPath = os.ExpandEnv(sourcePath) {
sourcePath = expandedPath
}
if strings.HasPrefix(sourcePath, "~") {
var home, userName, remainder string
slashPos := strings.IndexRune(sourcePath, '/')
if slashPos > 0 {
userName = sourcePath[1:slashPos]
remainder = sourcePath[slashPos:]
} else {
userName = sourcePath[1:]
}
if len(userName) == 0 {
home, err = os.UserHomeDir()
if err != nil {
return
}
} else {
var userInfo *user.User
userInfo, err = user.Lookup(userName)
if err != nil {
return
}
home = userInfo.HomeDir
}
expandedPath = path.Join(home, remainder)
}
return
}