Compare commits
15 Commits
b6952444ab
...
f100adead3
| Author | SHA1 | Date | |
|---|---|---|---|
| f100adead3 | |||
| 7d2cf1e687 | |||
| 49728307f3 | |||
| 20dc502438 | |||
| ce7bfc5e3f | |||
| 6e98bdd16b | |||
| 6ee365bacc | |||
| 20d8236325 | |||
| c39ee7cec0 | |||
| 9e4252173b | |||
| 2b80ba6789 | |||
| d7247f97c5 | |||
| 02df7f1c1f | |||
| b6b09b2fb1 | |||
| 0677180456 |
@ -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
149
builtin-os-file-iter.go
Normal 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")
|
||||
// }
|
||||
@ -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() {
|
||||
|
||||
@ -13,6 +13,7 @@ const (
|
||||
TypeFileHandle = "file-handle"
|
||||
TypeInt = "integer"
|
||||
TypeItem = "item"
|
||||
TypeIterator = "iterator"
|
||||
TypeNumber = "number"
|
||||
TypePair = "pair"
|
||||
TypeString = "string"
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()>>
|
||||
|
||||
|
||||
@ -1318,6 +1320,32 @@ This function allows you to define variables whose names must include special ch
|
||||
`>>>` [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"
|
||||
|
||||
===== print()
|
||||
@ -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` +
|
||||
|
||||
@ -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
25
iter-factory.go
Normal 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
|
||||
}
|
||||
@ -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
9
lib-ext-darwin.go
Normal 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
9
lib-ext-linux.go
Normal 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
9
lib-ext-windows.go
Normal 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"
|
||||
@ -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 {
|
||||
|
||||
31
list-type.go
31
list-type.go
@ -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)
|
||||
}
|
||||
|
||||
@ -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
70
operator-digest.go
Normal 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
72
operator-filter.go
Normal 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
65
operator-join.go
Normal 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
66
operator-map.go
Normal 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)
|
||||
}
|
||||
12
plugins.go
12
plugins.go
@ -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 {
|
||||
|
||||
20
scanner.go
20
scanner.go
@ -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
|
||||
}
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
118
t_utils-unix_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
1
term.go
1
term.go
@ -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
48
utils-unix.go
Normal 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
18
utils-windows.go
Normal 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
|
||||
}
|
||||
38
utils.go
38
utils.go
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user