Compare commits

...

15 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
37 changed files with 965 additions and 138 deletions

View File

@ -23,9 +23,11 @@ func parseRunArgs(localCtx ExprContext, args map[string]any) (it Iterator, op Fu
return return
} }
if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil { if args[iterParamOperator] != nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator])) if op, ok = args[iterParamOperator].(Functor); !ok || op == nil {
return err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
return
}
} }
var vars *DictType var vars *DictType
@ -51,7 +53,7 @@ func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err
var ok bool var ok bool
var op Functor var op Functor
var v any var v any
var usingDefaultOp = false // var usingDefaultOp = false
var params map[string]any var params map[string]any
var item 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 { if it, op, err = parseRunArgs(localCtx, args); err != nil {
return return
} else if op == nil { // } else if op == nil {
op = NewGolangFunctor(printLnFunc) // op = NewGolangFunctor(printLnFunc)
usingDefaultOp = true // usingDefaultOp = true
} }
for item, err = it.Next(); err == nil; item, err = it.Next() { for item, err = it.Next(); err == nil; item, err = it.Next() {
if usingDefaultOp { // if usingDefaultOp {
params = map[string]any{ParamItem: []any{item}} // params = map[string]any{ParamItem: []any{item}}
} else { // } else {
params = map[string]any{ParamIndex: it.Index(), ParamItem: item} // params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
} // }
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil { if op != nil {
break params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
} else { if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
var success bool
if success, ok = ToBool(v); !success || !ok {
break 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 err = nil
} }
if err == nil { if err == nil {
if op == nil {
ctx.UnsafeSetVar(iterVarStatus, it.Count())
}
result, _ = localCtx.GetVar(iterVarStatus) result, _ = localCtx.GetVar(iterVarStatus)
} }
return 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{ ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamHandle), NewFuncParam(ParamHandle),
}) })
ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
NewFuncParam(paramHandleOrPath),
})
} }
func init() { func init() {

View File

@ -13,6 +13,7 @@ const (
TypeFileHandle = "file-handle" TypeFileHandle = "file-handle"
TypeInt = "integer" TypeInt = "integer"
TypeItem = "item" TypeItem = "item"
TypeIterator = "iterator"
TypeNumber = "number" TypeNumber = "number"
TypePair = "pair" TypePair = "pair"
TypeString = "string" 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) { func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone var sortType = sortTypeNone
var s string 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 len(args) > 0 {
if s, err = ToGoString(args[0], "sort type"); err == nil { argAny = args[0]
switch strings.ToLower(s) { } else {
case "a", "asc": argAny = "default"
sortType = sortTypeAsc }
case "d", "desc": if s, err = ToGoString(argAny, "sort type"); err == nil {
sortType = sortTypeDesc switch strings.ToLower(s) {
case "n", "none", "nosort", "no-sort": case "a", "asc":
sortType = sortTypeNone sortType = sortTypeAsc
case "", "default": case "d", "desc":
sortType = sortTypeDefault sortType = sortTypeDesc
default: case "n", "none", "nosort", "no-sort":
err = fmt.Errorf("invalid sort type %q", s) 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(argAny, "iteration mode"); err == nil {
if s, err = ToGoString(args[1], "iteration mode"); err == nil { switch strings.ToLower(s) {
switch strings.ToLower(s) { case "k", "key", "keys":
case "k", "key", "keys": dictIt.iterMode = dictIterModeKeys
it.iterMode = dictIterModeKeys case "v", "value", "values":
case "v", "value", "values": dictIt.iterMode = dictIterModeValues
it.iterMode = dictIterModeValues case "i", "item", "items":
case "i", "item", "items": dictIt.iterMode = dictIterModeItems
it.iterMode = dictIterModeItems case "", "default":
case "", "default": dictIt.iterMode = dictIterModeKeys
it.iterMode = dictIterModeKeys default:
default: err = fmt.Errorf("invalid iteration mode %q", s)
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) { func NewMapIterator(m map[any]any) (it *DictIterator) {
@ -117,7 +129,7 @@ func (it *DictIterator) String() string {
if it.a != nil { if it.a != nil {
l = len(*it.a) l = len(*it.a)
} }
return fmt.Sprintf("$(#%d)", l) return fmt.Sprintf("$({#%d})", l)
} }
func (it *DictIterator) TypeName() string { func (it *DictIterator) TypeName() string {

View File

@ -1087,6 +1087,8 @@ The "base" builtin module provides functions for type checking and type conversi
.Other functions .Other functions
* <<_eval,eval()>> * <<_eval,eval()>>
* <<_set,set()>>
* <<_unset,unset()>>
* <<_var,var()>> * <<_var,var()>>
@ -1318,6 +1320,32 @@ This function allows you to define variables whose names must include special ch
`>>>` [blue]`var("gain%", var("$x")+1)` + `>>>` [blue]`var("gain%", var("$x")+1)` +
[green]`13` [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" ==== Module "fmt"
===== print() ===== print()
@ -1325,18 +1353,31 @@ This function allows you to define variables whose names must include special ch
===== println() ===== println()
==== Module "import" ==== Module "import"
Module actiovation: +
===== _import()_ ===== _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()_ ===== _importAll()_
==== Module "iterator" ==== Module "iterator"
===== run() ===== 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" ==== 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. 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() ===== add()
Syntax: + Syntax: +
`{4sp}add(<num-expr1>, <num-expr2>, ...) -> any` + `{4sp}add(<num-expr1>, <num-expr2>, ...) -> any` +

View File

@ -76,6 +76,12 @@ func isFile(filePath string) bool {
func searchAmongPath(filename string, dirList []string) (filePath string) { func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error var err error
suffix := SHAREDLIBRARY_EXTENSION
if strings.HasSuffix(filename, ".debug") {
suffix += ".debug"
}
for _, dir := range dirList { for _, dir := range dirList {
if dir, err = ExpandPath(dir); err != nil { if dir, err = ExpandPath(dir); err != nil {
continue continue
@ -84,6 +90,12 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
filePath = fullPath filePath = fullPath
break break
} }
subdir := strings.TrimSuffix(filename, suffix)
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
filePath = fullPath
break
}
} }
return 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 { type Iterator interface {
Typer Typer
fmt.Stringer
Next() (item any, err error) // must return io.EOF after the last item Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error) Current() (item any, err error)
Index() int Index() int

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 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 { func (it *ListIterator) String() string {
var l = 0 var l = 0
if it.a != nil { if it.a != nil {
l = len(*it.a) l = len(*it.a)
} }
return fmt.Sprintf("$(#%d)", l) return fmt.Sprintf("$([#%d])", l)
} }
func (it *ListIterator) TypeName() string { func (it *ListIterator) TypeName() string {

View File

@ -26,9 +26,9 @@ func newList(listAny []any) (list *ListType) {
func NewList(listAny []any) (list *ListType) { func NewList(listAny []any) (list *ListType) {
if listAny != nil { if listAny != nil {
ls := make(ListType, len(listAny)) ls := make(ListType, len(listAny))
// for i, item := range listAny { // for i, item := range listAny {
// ls[i] = item // ls[i] = item
// } // }
copy(ls, listAny) copy(ls, listAny)
list = &ls list = &ls
} }
@ -53,14 +53,14 @@ func ListFromStrings(stringList []string) (list *ListType) {
} }
func (ls *ListType) ToString(opt FmtOpt) (s string) { func (ls *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt) indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt) flags := GetFormatFlags(opt)
var sb strings.Builder var sb strings.Builder
sb.WriteByte('[') sb.WriteByte('[')
if len(*ls) > 0 { if len(*ls) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1) innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1) nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 { if flags&MultiLine != 0 {
sb.WriteByte('\n') sb.WriteByte('\n')
@ -129,6 +129,20 @@ func (ls *ListType) contains(t *ListType) (answer bool) {
return 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) { func (list *ListType) indexDeepSameCmp(target any) (index int) {
var eq bool var eq bool
var err error var err error
@ -193,3 +207,6 @@ func (list *ListType) setItem(index int64, value any) (err error) {
return 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) count, err = ImportInContextByGlobPattern(module)
} else { } else {
var moduleSpec any 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() { for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok { if module, ok := moduleSpec.(string); ok {
if ImportInContext(module) { 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) { func makePluginName(name string) (decorated string) {
var template string var template string
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") { if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
template = "expr-%s-plugin.so" template = "expr-%s-plugin%s"
} else { } 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 return
} }
@ -93,10 +93,12 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
func importPluginFromSearchPath(name any) (count int, err error) { func importPluginFromSearchPath(name any) (count int, err error) {
var moduleSpec any var moduleSpec any
var it Iterator
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH) dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count = 0 count = 0
it := NewAnyIterator(name) if it, err = NewIterator(name); err != nil {
return
}
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() { for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok { if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil { if err = importPlugin(dirList, module); err != nil {

View File

@ -210,14 +210,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.makeToken(SymQuote, ch) tk = scanner.makeToken(SymQuote, ch)
escape = false escape = false
} else { } else {
tk = scanner.fetchString(ch) tk = scanner.fetchString(ch, true)
} }
case '"': case '"':
if escape { if escape {
tk = scanner.makeToken(SymDoubleQuote, ch) tk = scanner.makeToken(SymDoubleQuote, ch)
escape = false escape = false
} else { } else {
tk = scanner.fetchString(ch) tk = scanner.fetchString(ch, true)
} }
case '`': case '`':
tk = scanner.makeToken(SymBackTick, ch) tk = scanner.makeToken(SymBackTick, ch)
@ -315,6 +315,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk.source += ")" tk.source += ")"
} else if next == '$' { } else if next == '$' {
tk = scanner.moveOn(SymDoubleDollar, ch, 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 { } else {
tk = scanner.makeToken(SymDollar, ch) tk = scanner.makeToken(SymDollar, ch)
} }
@ -590,7 +598,7 @@ func (scanner *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (
return return
} }
func (scanner *scanner) fetchString(termCh byte) (tk *Token) { func (scanner *scanner) fetchString(termCh byte, addQuote bool) (tk *Token) {
var err error var err error
var ch, prev byte var ch, prev byte
var sb strings.Builder var sb strings.Builder
@ -628,7 +636,11 @@ func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
} }
} else { } else {
txt := sb.String() 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 return
} }

View File

@ -135,6 +135,10 @@ func init() {
SymKwNot: {"not", symClassOperator, posInfix}, SymKwNot: {"not", symClassOperator, posInfix},
SymKwOr: {"or", symClassOperator, posInfix}, SymKwOr: {"or", symClassOperator, posInfix},
SymKwBut: {"but", 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}, SymKwFunc: {"func(", symClassDeclaration, posPrefix},
SymKwBuiltin: {"builtin", symClassOperator, posPrefix}, SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
SymKwPlugin: {"plugin", symClassOperator, posPrefix}, SymKwPlugin: {"plugin", symClassOperator, posPrefix},

View File

@ -119,6 +119,10 @@ const (
SymKwPlugin SymKwPlugin
SymKwIn SymKwIn
SymKwInclude SymKwInclude
SymKwMap
SymKwFilter
SymKwDigest
SymKwJoin
SymKwNil SymKwNil
SymKwUnset SymKwUnset
) )
@ -135,9 +139,13 @@ func init() {
"FUNC": SymKwFunc, "FUNC": SymKwFunc,
"IN": SymKwIn, "IN": SymKwIn,
"INCLUDE": SymKwInclude, "INCLUDE": SymKwInclude,
"MAP": SymKwMap,
"FILTER": SymKwFilter,
"NOT": SymKwNot, "NOT": SymKwNot,
"OR": SymKwOr, "OR": SymKwOr,
"NIL": SymKwNil, "NIL": SymKwNil,
"UNSET": SymKwUnset, "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]`}, /* 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}, /* 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"`}, /* 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", ".") //t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 1) runTestSuiteSpec(t, section, inputs, 10)
runTestSuite(t, section, inputs) // runTestSuite(t, section, inputs)
} }

View File

@ -5,6 +5,7 @@
package expr package expr
import ( import (
"io"
"testing" "testing"
) )
@ -26,10 +27,16 @@ func TestFuncOs(t *testing.T) {
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`}, /* 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}, /* 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]`}, /* 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", ".") // t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 2) // runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs) 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 expr Expr
var gotResult any var gotResult any
var gotErr error var gotErr error
var eq, eqDone bool
wantErr := getWantedError(input) wantErr := getWantedError(input)
@ -90,7 +91,18 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
gotResult, gotErr = expr.Eval(ctx) 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*/ { 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)) 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++;
it++ it++
`, int64(1), nil}, `, int64(1), nil},
/* 20 */ {`a=2; ${a}`, int64(2), nil},
/* 21 */ {`$_=2; $_`, int64(2), nil},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 18) // runTestSuiteSpec(t, section, inputs, 21)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }

View File

@ -59,7 +59,7 @@ func TestNewIterList2(t *testing.T) {
func TestNewIterList3(t *testing.T) { func TestNewIterList3(t *testing.T) {
list := []any{"a", "b", "c", "d"} list := []any{"a", "b", "c", "d"}
it := NewAnyIterator(list) it, _ := NewIterator(list)
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "a" { } else if item != "a" {
@ -71,7 +71,7 @@ func TestNewIterList3(t *testing.T) {
func TestNewIterList4(t *testing.T) { func TestNewIterList4(t *testing.T) {
list := any(nil) list := any(nil)
it := NewAnyIterator(list) it, _ := NewIterator(list)
if _, err := it.Next(); err != io.EOF { if _, err := it.Next(); err != io.EOF {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} }
@ -79,7 +79,7 @@ func TestNewIterList4(t *testing.T) {
func TestNewIterList5(t *testing.T) { func TestNewIterList5(t *testing.T) {
list := "123" list := "123"
it := NewAnyIterator(list) it, _ := NewIterator(list)
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "123" { } else if item != "123" {
@ -91,8 +91,8 @@ func TestNewIterList5(t *testing.T) {
func TestNewIterList6(t *testing.T) { func TestNewIterList6(t *testing.T) {
list := newListA("a", "b", "c", "d") list := newListA("a", "b", "c", "d")
it1 := NewAnyIterator(list) it1, _ := NewIterator(list)
it := NewAnyIterator(it1) it, _ := NewIterator(it1)
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "a" { } else if item != "a" {
@ -103,9 +103,9 @@ func TestNewIterList6(t *testing.T) {
} }
func TestNewString(t *testing.T) { func TestNewString(t *testing.T) {
list := "123" list := "123"
it := NewAnyIterator(list) it, _ := NewIterator(list)
if s := it.String(); s != "$(#1)" { if s := it.String(); s != "$([#1])" {
t.Errorf("expected $(#1), got %s", s) 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}, /* 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}, /* 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}, /* 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) 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]`}, /* 17 */ {`~true`, nil, `[1:2] prefix/postfix operator "~" do not support operand 'true' [bool]`},
/* 18 */ {`1^2`, int64(3), nil}, /* 18 */ {`1^2`, int64(3), nil},
/* 19 */ {`3^2`, int64(1), nil}, /* 19 */ {`3^2`, int64(1), nil},
/* 19 */ {`a=1; a^=2`, int64(3), nil}, /* 20 */ {`a=1; a^=2`, int64(3), nil},
/* 20 */ {`a=1; ++a`, int64(2), nil}, /* 21 */ {`a=1; ++a`, int64(2), nil},
/* 21 */ {`a=1; --a`, int64(0), 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", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 4) // runTestSuiteSpec(t, section, inputs, 28)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }

View File

@ -30,9 +30,29 @@ func TestPluginExists(t *testing.T) {
func TestMakePluginName(t *testing.T) { func TestMakePluginName(t *testing.T) {
name := "json" name := "json"
want := "expr-" + name + "-plugin.so" want := "expr-" + name + "-plugin" + SHAREDLIBRARY_EXTENSION
if got := makePluginName(name); got != want { if got := makePluginName(name); got != want {
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", 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") 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 ( const (
priNone termPriority = iota priNone termPriority = iota
priRange priRange
priIterOp // map, filter, digest, etc
priBut priBut
priAssign priAssign
priInsert 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 ( import (
"fmt" "fmt"
"os"
"os/user"
"path"
"reflect" "reflect"
"strings"
) )
func IsString(v any) (ok bool) { func IsString(v any) (ok bool) {
@ -234,37 +230,3 @@ func ForAll[T, V any](ts []T, fn func(T) V) []V {
} }
return result 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
}