Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f100adead3 | |||
| 7d2cf1e687 | |||
| 49728307f3 | |||
| 20dc502438 | |||
| ce7bfc5e3f | |||
| 6e98bdd16b | |||
| 6ee365bacc | |||
| 20d8236325 | |||
| c39ee7cec0 | |||
| 9e4252173b | |||
| 2b80ba6789 | |||
| d7247f97c5 | |||
| 02df7f1c1f | |||
| b6b09b2fb1 | |||
| 0677180456 | |||
| b6952444ab | |||
| 6c3a071a02 | |||
| d7d03b4af8 | |||
| 49c5cb6a22 | |||
| 53d805a832 | |||
| b99ad5def1 | |||
| 0d44b8697b | |||
| 1a7e537921 | |||
| 807df0f3a8 | |||
| d7dd628fc9 | |||
| 1f57ba28dd | |||
| 92bd366380 | |||
| 7b93c5b4ac | |||
| 3ba8194ddb |
@ -144,4 +144,4 @@ Variables and functions can be added to a context both programmatically and ad a
|
||||
|
||||
== Expressions syntax
|
||||
|
||||
See #TODO link to doc/Expr.html#
|
||||
See https://cdn.paas.portale-stac.it/howto/go-pkg/expr/doc/Expr.html[Expr documentation] for a complete reference of the expression syntax and available functions.
|
||||
|
||||
@ -72,6 +72,10 @@ func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, er
|
||||
result = v
|
||||
case string:
|
||||
result = len(v) > 0
|
||||
case *ListType:
|
||||
result = len(*v) > 0
|
||||
case *DictType:
|
||||
result = len(*v) > 0
|
||||
default:
|
||||
err = ErrCantConvert(name, v, "bool")
|
||||
}
|
||||
@ -95,6 +99,8 @@ func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err
|
||||
if i, err = strconv.Atoi(v); err == nil {
|
||||
result = int64(i)
|
||||
}
|
||||
case *FractionType:
|
||||
result = int64(v.num / v.den)
|
||||
default:
|
||||
err = ErrCantConvert(name, v, "int")
|
||||
}
|
||||
@ -230,6 +236,35 @@ func varFunc(ctx ExprContext, name string, args map[string]any) (result any, err
|
||||
return
|
||||
}
|
||||
|
||||
func setFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||
var varName string
|
||||
var ok bool
|
||||
|
||||
if varName, ok = args[ParamName].(string); !ok {
|
||||
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
|
||||
}
|
||||
|
||||
if result, ok = args[ParamValue]; ok {
|
||||
ctx.GetParent().UnsafeSetVar(varName, result)
|
||||
} else {
|
||||
err = ErrWrongParamType(name, ParamValue, TypeAny, args[ParamValue])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func unsetFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||
var varName string
|
||||
var ok bool
|
||||
|
||||
if varName, ok = args[ParamName].(string); !ok {
|
||||
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
|
||||
} else {
|
||||
ctx.GetParent().DeleteVar(varName)
|
||||
result = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//// import
|
||||
|
||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||
@ -264,6 +299,16 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||
NewFuncParam(ParamName),
|
||||
NewFuncParamFlagDef(ParamValue, PfDefault, nil),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("set", NewGolangFunctor(setFunc), TypeAny, []ExprFuncParam{
|
||||
NewFuncParam(ParamName),
|
||||
NewFuncParam(ParamValue),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("unset", NewGolangFunctor(unsetFunc), TypeAny, []ExprFuncParam{
|
||||
NewFuncParam(ParamName),
|
||||
NewFuncParam(ParamValue),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@ -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"
|
||||
|
||||
221
dict-iterator.go
Normal file
221
dict-iterator.go
Normal file
@ -0,0 +1,221 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// dict-iterator.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dictIterMode int
|
||||
|
||||
const (
|
||||
dictIterModeKeys dictIterMode = iota
|
||||
dictIterModeValues
|
||||
dictIterModeItems
|
||||
)
|
||||
|
||||
type DictIterator struct {
|
||||
a *DictType
|
||||
count int
|
||||
index int
|
||||
keys []any
|
||||
iterMode dictIterMode
|
||||
}
|
||||
|
||||
type sortType int
|
||||
|
||||
const (
|
||||
sortTypeNone sortType = iota
|
||||
sortTypeAsc
|
||||
sortTypeDesc
|
||||
sortTypeDefault = sortTypeAsc
|
||||
)
|
||||
|
||||
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
|
||||
it.keys = make([]any, 0, len(m))
|
||||
|
||||
if sort == sortTypeNone {
|
||||
for keyAny := range m {
|
||||
it.keys = append(it.keys, keyAny)
|
||||
}
|
||||
} else {
|
||||
scalarMap := make(map[string]any, len(m))
|
||||
scalerKeys := make([]string, 0, len(m))
|
||||
for keyAny := range m {
|
||||
keyStr := fmt.Sprint(keyAny)
|
||||
scalarMap[keyStr] = keyAny
|
||||
scalerKeys = append(scalerKeys, keyStr)
|
||||
}
|
||||
|
||||
switch sort {
|
||||
case sortTypeAsc:
|
||||
slices.Sort(scalerKeys)
|
||||
case sortTypeDesc:
|
||||
slices.Sort(scalerKeys)
|
||||
slices.Reverse(scalerKeys)
|
||||
}
|
||||
for _, keyStr := range scalerKeys {
|
||||
it.keys = append(it.keys, scalarMap[keyStr])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
|
||||
var sortType = sortTypeNone
|
||||
var s string
|
||||
var argAny any
|
||||
|
||||
dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
|
||||
if len(args) > 0 {
|
||||
argAny = args[0]
|
||||
} else {
|
||||
argAny = "default"
|
||||
}
|
||||
if s, err = ToGoString(argAny, "sort type"); err == nil {
|
||||
switch strings.ToLower(s) {
|
||||
case "a", "asc":
|
||||
sortType = sortTypeAsc
|
||||
case "d", "desc":
|
||||
sortType = sortTypeDesc
|
||||
case "n", "none", "nosort", "no-sort":
|
||||
sortType = sortTypeNone
|
||||
case "", "default":
|
||||
sortType = sortTypeDefault
|
||||
default:
|
||||
err = fmt.Errorf("invalid sort type %q", s)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if len(args) > 1 {
|
||||
argAny = args[1]
|
||||
} else {
|
||||
argAny = "default"
|
||||
}
|
||||
|
||||
if s, err = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dictIt.makeKeys(*dict, sortType)
|
||||
return dictIt, err
|
||||
}
|
||||
|
||||
func NewMapIterator(m map[any]any) (it *DictIterator) {
|
||||
it = &DictIterator{a: (*DictType)(&m), count: 0, index: -1, keys: nil}
|
||||
it.makeKeys(m, sortTypeNone)
|
||||
return
|
||||
}
|
||||
|
||||
func (it *DictIterator) String() string {
|
||||
var l = 0
|
||||
if it.a != nil {
|
||||
l = len(*it.a)
|
||||
}
|
||||
return fmt.Sprintf("$({#%d})", l)
|
||||
}
|
||||
|
||||
func (it *DictIterator) TypeName() string {
|
||||
return "DictIterator"
|
||||
}
|
||||
|
||||
func (it *DictIterator) HasOperation(name string) bool {
|
||||
// yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
|
||||
yes := slices.Contains([]string{NextName, ResetName, IndexName, CountName, CurrentName, CleanName, KeyName, ValueName}, name)
|
||||
return yes
|
||||
}
|
||||
|
||||
func (it *DictIterator) 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
|
||||
case KeyName:
|
||||
if it.index >= 0 && it.index < len(it.keys) {
|
||||
v = it.keys[it.index]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
case ValueName:
|
||||
if it.index >= 0 && it.index < len(it.keys) {
|
||||
a := *(it.a)
|
||||
v = a[it.keys[it.index]]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
default:
|
||||
err = errNoOperation(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (it *DictIterator) Current() (item any, err error) {
|
||||
if it.index >= 0 && it.index < len(it.keys) {
|
||||
switch it.iterMode {
|
||||
case dictIterModeKeys:
|
||||
item = it.keys[it.index]
|
||||
case dictIterModeValues:
|
||||
a := *(it.a)
|
||||
item = a[it.keys[it.index]]
|
||||
case dictIterModeItems:
|
||||
a := *(it.a)
|
||||
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
|
||||
item = newList(pair)
|
||||
}
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (it *DictIterator) Next() (item any, err error) {
|
||||
it.index++
|
||||
if item, err = it.Current(); err != io.EOF {
|
||||
it.count++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (it *DictIterator) Index() int {
|
||||
return it.index
|
||||
}
|
||||
|
||||
func (it *DictIterator) Count() int {
|
||||
return it.count
|
||||
}
|
||||
|
||||
func (it *DictIterator) Reset() error {
|
||||
it.index = -1
|
||||
it.count = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *DictIterator) Clean() error {
|
||||
return nil
|
||||
}
|
||||
759
doc/Expr.adoc
759
doc/Expr.adoc
@ -20,15 +20,24 @@ Expressions calculator
|
||||
:rouge-style: gruvbox
|
||||
// :rouge-style: colorful
|
||||
//:rouge-style: monokay
|
||||
// Work around to manage double-column in back-tick quotes
|
||||
// Workaround to manage double-column in back-tick quotes
|
||||
:2c: ::
|
||||
// Workaround to manage double-plus in back-tick quotes
|
||||
:plusplus: ++
|
||||
// Workaround to manage asterisk in back-tick quotes
|
||||
:star: *
|
||||
|
||||
:sp:  
|
||||
:2sp:   
|
||||
:4sp:     
|
||||
|
||||
|
||||
toc::[]
|
||||
|
||||
#TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
|
||||
#TODO: Work in progress (last update on 2026/04/21, 6:49 p.m.)#
|
||||
|
||||
== Expr
|
||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||
_Expr_ is a GO package that can analyze, interpret and calculate expressions.
|
||||
|
||||
|
||||
=== Concepts and terminology
|
||||
@ -42,7 +51,7 @@ Expressions are texts containing sequences of operations represented by a syntax
|
||||
image::expression-diagram.png[]
|
||||
|
||||
==== Variables
|
||||
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.
|
||||
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions by simply specifying the name of the variable as an operand.
|
||||
|
||||
==== Multi-expression
|
||||
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
|
||||
@ -58,9 +67,40 @@ The expression context is analogous to the stack-frame of other programming lang
|
||||
|
||||
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
|
||||
|
||||
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
|
||||
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned earlier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
|
||||
|
||||
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
|
||||
Imported functions are registered in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
|
||||
|
||||
===== Inspecting contexts
|
||||
_Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.
|
||||
|
||||
An interactive tool could like `dev-expr` (see <<_dev-expr_test_tool>>) can be used to inspect contexts interactively.
|
||||
|
||||
|
||||
.Example: inspecting contexts
|
||||
`>>>` [blue]`$$` +
|
||||
[green]`{"variables": {"ls": "[10, 20, 30]", "it": "$(#3)", "last": "-1"}, "functions": {"about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}", "ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}"}}` +
|
||||
|
||||
`>>>` [gray]_// Let use the *ml* command to activate multi-line output of contexts, which is more readable._ +
|
||||
`>>>` [blue]`ml` +
|
||||
`>>>` [blue]`$$` +
|
||||
[green]`{` +
|
||||
[green]`{2sp}"variables": {` +
|
||||
[green]`{4sp}"last": {"variables": {"last": "-1", "ls": "[10, 20, 30]", "it": "$(#3)"}, "functions": {"ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}", "about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}"}},` +
|
||||
[green]`{4sp}"ls": [10, 20, 30],` +
|
||||
[green]`{4sp}"it": $(#3)` +
|
||||
[green]`{2sp}},` +
|
||||
[green]`{2sp}"functions": {` +
|
||||
[green]`{4sp}"ctrlList": ctrlList():list-of-strings{},` +
|
||||
[green]`{4sp}"envGet": envGet(name):string{},` +
|
||||
[green]`{4sp}"envSet": envSet(name, value):string{},` +
|
||||
[green]`{4sp}"about": about():string{},` +
|
||||
[green]`{4sp}"bin": bin(value, digits=8):integer{},` +
|
||||
[green]`{4sp}"ctrl": ctrl(prop, value):any{}` +
|
||||
[green]`{2sp}}` +
|
||||
[green]`}`
|
||||
|
||||
In order to inspect the global context issue the [blue]`$$global` operator.
|
||||
|
||||
=== `dev-expr` test tool
|
||||
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
|
||||
@ -145,7 +185,7 @@ dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoro
|
||||
<2> Fractions: _numerator_ : _denominator_.
|
||||
<3> Activate multi-line output of fractions.
|
||||
<4> But operator, see <<_but_operator>>.
|
||||
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
|
||||
<5> Multi-expression: the same result as the previous single expression, but this time it is obtained with two separate calculations.
|
||||
|
||||
== Data types
|
||||
_Expr_ has its type system which is a subset of Golang's type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.
|
||||
@ -240,10 +280,11 @@ _Expr_ also supports fractions. Fraction literals are made with two integers sep
|
||||
|
||||
.Fraction literal syntax
|
||||
====
|
||||
*_fraction_* = [__sign__] (_num-den-spec_ "**:**" _float-spec_) +
|
||||
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
|
||||
_sign_ = "**+**" | "**-**" +
|
||||
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
|
||||
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
|
||||
_num-den-spec_ = _digit-seq_ "**:**" _digit-seq_ +
|
||||
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _repetend_ "**)**" +
|
||||
_repetend_ = _dec-seq_ +
|
||||
_dec-seq_ = _see-integer-literal-syntax_ +
|
||||
_digit-seq_ = _see-integer-literal-syntax_
|
||||
====
|
||||
@ -276,6 +317,8 @@ _digit-seq_ = _see-integer-literal-syntax_
|
||||
`>>>` [blue]`1:(-2)` +
|
||||
[green]`-1:2`
|
||||
|
||||
`>>>` [blue]`1.(3)` // 1.33333... +
|
||||
[green]`4:3`
|
||||
|
||||
Fractions can be used together with integers and floats in expressions.
|
||||
|
||||
@ -395,12 +438,13 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
|
||||
[CAUTION]
|
||||
====
|
||||
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.
|
||||
|
||||
.Example
|
||||
[source,go]
|
||||
----
|
||||
2 > (a=1) or (a=8) > 0; a // <1>
|
||||
----
|
||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a conseguence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||
|
||||
|
||||
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
|
||||
@ -492,10 +536,10 @@ Array's items can be accessed using the index `[]` operator.
|
||||
[green]`3`
|
||||
|
||||
.Examples: Element insertion
|
||||
`>>>` [blue]`"first" >> list` +
|
||||
`>>>` [blue]`"first" +> list` +
|
||||
[green]`["first", "one", "six", "three"]`
|
||||
|
||||
`>>>` [blue]`list << "last"` +
|
||||
`>>>` [blue]`list <+ "last"` +
|
||||
[green]`["first", "one", "six", "three", "last"]`
|
||||
|
||||
.Examples: Element in list
|
||||
@ -509,7 +553,7 @@ Array's items can be accessed using the index `[]` operator.
|
||||
`>>>` [blue]`[1,2,3] + ["one", "two", "three"]` +
|
||||
[green]`[1, 2, 3, "one", "two", "three"]`
|
||||
|
||||
`>>>` [blue]`[1,2,3,4] - [2,4]` +
|
||||
`>>>` [blue]`[1,2,3,2,4] - [2,4]` +
|
||||
[green]`[1, 3]`
|
||||
|
||||
|
||||
@ -570,7 +614,7 @@ _Expr_, like most programming languages, supports variables. A variable is an id
|
||||
.Variable literal syntax
|
||||
====
|
||||
*_variable_* = _identifier_ "*=*" _any-value_ +
|
||||
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
|
||||
_identifier_ = _alpha_ {_alpha_|_dec-digit_|"*_*"} +
|
||||
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
|
||||
====
|
||||
|
||||
@ -695,7 +739,7 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
|
||||
|
||||
|
||||
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
|
||||
The left operand of first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
|
||||
The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
|
||||
|
||||
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
|
||||
|
||||
@ -841,12 +885,12 @@ Functions in _Expr_ are very similar to functions available in many programming
|
||||
|
||||
|
||||
=== _Expr_ function definition
|
||||
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters and passing paramters by name.
|
||||
An expr-function is identified and referenced by its name. It can have zero or more parameter. expr-functions also support optional parameters and passing paramters by name.
|
||||
|
||||
.Expr's function definition syntax
|
||||
====
|
||||
*_function-definition_* = _identifier_ "**=**" "**func(**" [_formal-param-list_] "**)**" "**{**" _multi-expression_ "**}**" +
|
||||
_formal-param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
|
||||
_formal-param-list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
|
||||
_required-param-list_ = _identifier_ { "**,**" _identifier_ } +
|
||||
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } +
|
||||
_optional-param_ = _param-name_ "**=**" _any-expr_ +
|
||||
@ -858,7 +902,7 @@ _param-name_ = _identifier_
|
||||
`>>>` [blue]`sum = func(a, b){ a + b }` +
|
||||
[green]`sum(a, b):any{}`
|
||||
|
||||
^(\*)^ Since the plus, *+*, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
|
||||
^(*)^ Since the plus, **+**, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
|
||||
|
||||
`>>>` [gray]_// A more complex example: recursive calculation of the n-th value of Fibonacci's sequence_ +
|
||||
`>>>` [blue]`fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }` +
|
||||
@ -959,9 +1003,9 @@ _param-name_ = _identifier_
|
||||
|
||||
|
||||
=== Function context
|
||||
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and un related variables.
|
||||
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and unrelated variables.
|
||||
|
||||
Clone variables are normal local variables. The only diffence will appear when the defining function terminate, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
|
||||
Clone variables are normal local variables. The only diffence will appear when the defining function ends, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
|
||||
|
||||
.Example
|
||||
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
|
||||
@ -978,24 +1022,671 @@ NOTE: The clone modifier [blue]`@` does not make a variable a reference variable
|
||||
The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too.
|
||||
|
||||
.Example
|
||||
`>>>` [blue]`g = func(@p) {2+@p}`
|
||||
g(@p):any{}`
|
||||
`>>>` [blue]`g(9)`
|
||||
11`
|
||||
`>>>` [blue]`p
|
||||
9
|
||||
`>>>` [blue]`g = func(@p) {2+@p}` +
|
||||
[green]`g(@p):any{}` +
|
||||
`>>>` [blue]`g(9)` +
|
||||
[green]`11` +
|
||||
`>>>` [blue]`p` +
|
||||
[green]`9`
|
||||
====
|
||||
|
||||
== Iterators
|
||||
#TODO: function calls operations#
|
||||
|
||||
== Builtins
|
||||
#TODO: builtins#
|
||||
Builtins are collection of function dedicated to specific domains of application. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make builtins available in _Expr_ contextes, it is required to activate the builtin module in which they are defined.
|
||||
|
||||
=== Builtin functions
|
||||
There are currently several builtin modules. More builtin modules will be added in the future.
|
||||
|
||||
.Available builtin modules
|
||||
* <<_module_base,*base*>>: Base expression tools like isNil(), int(), etc.
|
||||
* <<_module_fmt,*fmt*>>: String and console formatting functions
|
||||
* <<_module_import,*import*>>: Functions <<_import,import()>> and <<_include,include()>>
|
||||
* <<_module_iterator,*iterator*>>: Iterator helper functions
|
||||
* <<_module_math_arith,*math.arith*>>: Functions <<_add,add()>> and <<_mul,mul()>>
|
||||
* <<_module_os_file,*os.file*>>: Operating system file functions
|
||||
* <<_module_string,*string*>>: string utilities
|
||||
|
||||
Builtins activation is done by using the [blue]`BUILTIN` operator. All modules except "base" must be explicitly enabled. The syntax is as follows.
|
||||
|
||||
.Builtin activation syntax
|
||||
====
|
||||
*_builtin-activation_* = [blue]`BUILTIN` (_builtin-name_ | _list-of-builtin-names_) +
|
||||
_builtin-name_ = _string_ +
|
||||
_list-of-builtin-names_ = **[** _string_ { "**,**" _string_ } **]**
|
||||
====
|
||||
|
||||
The following example shows how to activate the builtin module "math.arith" and then use the function add() defined in that module.
|
||||
|
||||
.Example: using built functions
|
||||
`>>>` [blue]`BUILTIN "math.arith"` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`add(5, 3, -2)` +
|
||||
[green]`6`
|
||||
|
||||
TIP: To avoid the need to activate builtin modules one by one, it is possible to activate all builtin modules at once by using the [blue]`BUILTIN "*"` syntax.
|
||||
|
||||
=== Builtin modules
|
||||
|
||||
==== Module "base"
|
||||
The "base" builtin module provides functions for type checking and type conversion. These functions are always available in _Expr_ contexts without the need to activate the "base" module.
|
||||
|
||||
.Checking functions
|
||||
* <<_isbool,isBool()>>
|
||||
* <<_isdict,isDict()>>
|
||||
* <<_isfloat,isFloat()>>
|
||||
* <<_isfract,isFract()>>
|
||||
* <<_islist,isList()>>
|
||||
* <<_isnil,isNil()>>
|
||||
* <<_isrational,isRational()>>
|
||||
* <<_isstring,isString()>>
|
||||
|
||||
.Conversion functions
|
||||
* <<_bool,bool()>>
|
||||
* <<_int,int()>>
|
||||
* <<_dec,dec()>>
|
||||
* <<_string,string()>>
|
||||
* <<_fract,fract()>>
|
||||
|
||||
.Other functions
|
||||
* <<_eval,eval()>>
|
||||
* <<_set,set()>>
|
||||
* <<_unset,unset()>>
|
||||
* <<_var,var()>>
|
||||
|
||||
|
||||
===== isBool()
|
||||
Syntax: `isBool(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is boolean, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isBool(true)` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isBool(3==2)` +
|
||||
[green]`true`
|
||||
|
||||
===== isDict()
|
||||
Syntax: `isDict(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is dictionary, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isDict({})` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isDict({1: "one", 2: "two"})` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isDict(1:"one")` +
|
||||
[red]`Eval Error: denominator must be integer, got string (one)`
|
||||
|
||||
===== isFloat()
|
||||
Syntax: `isFloat(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is float, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isFloat(4.)` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isFloat(4)` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`isFloat("2.1")` +
|
||||
[green]`false`
|
||||
|
||||
===== isFract()
|
||||
Syntax: `isFract(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is fraction, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isFract(4.5)` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`isFract(4:5)` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isFract(4)` +
|
||||
[green]**`false`** +
|
||||
`>>>` [blue]`isFract(1.(3))` +
|
||||
[green]`true`
|
||||
|
||||
===== isList()
|
||||
Syntax: `isList(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is list, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isList([])` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isList([1, "2"])` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isList(1,2)` +
|
||||
[red]`Eval Error: isList(): too many params -- expected 1, got 2`
|
||||
|
||||
===== isNil()
|
||||
Syntax: `isNil(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is nil, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isNil(nil)` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isNil(1)` +
|
||||
[green]`false`
|
||||
|
||||
===== isRational()
|
||||
Syntax: `isRational(<expr>) -> bool` +
|
||||
Returns _true_ if the value type of _<expr>_ is fraction or int, false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isRational(4.5)` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`isRational(4:5)` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isRational(4)` +
|
||||
[green]**`true`** +
|
||||
`>>>` [blue]`isRational(1.(3))` +
|
||||
[green]`true`
|
||||
|
||||
===== isString()
|
||||
Syntax: `isString(<expr>) -> bool` +
|
||||
Returns a boolean value , false otherwise.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`isString("ciao")` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`isString(2)` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`isString(2+"2")` +
|
||||
[green]`true`
|
||||
|
||||
===== bool()
|
||||
Syntax: `bool(<expr>) -> bool` +
|
||||
Returns a _boolean_ value consisent to the value of the expression.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`bool(1)` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`bool(0)` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`bool("")` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`bool([])` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`bool([1])` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`bool({})` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`bool({1: "one"})` +
|
||||
[green]`true`
|
||||
|
||||
===== int()
|
||||
Syntax: `int(<expr>) -> int` +
|
||||
Returns an _integer_ value consistent with the value of the expression.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`int(2)` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`int("2")` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`int("0x1")` +
|
||||
[red]`Eval Error: strconv.Atoi: parsing "0x1": invalid syntax` +
|
||||
`>>>` [blue]`int(0b10)` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`int(0o2)` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`int(0x2)` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`int(1.8)` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`int(5:2)` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`int([])` +
|
||||
[red]`Eval Error: int(): can't convert list to int` +
|
||||
`>>>` [blue]`int(true)` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`int(false)` +
|
||||
[green]`0`
|
||||
|
||||
|
||||
===== dec()
|
||||
Syntax: `dec(<expr>) -> float` +
|
||||
Returns a _float_ value consistent with the value of the expression.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`dec(2)` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`dec(2.1)` +
|
||||
[green]`2.1` +
|
||||
`>>>` [blue]`dec(2.3(1))` +
|
||||
[green]`2.311111111111111` +
|
||||
`>>>` [blue]`dec("3.14")` +
|
||||
[green]`3.14` +
|
||||
`>>>` [blue]`dec(3:4)` +
|
||||
[green]`0.75` +
|
||||
`>>>` [blue]`dec(true)` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`dec(false)` +
|
||||
[green]`0`
|
||||
|
||||
===== string()
|
||||
Syntax: `string(<expr>) -> string` +
|
||||
Returns a _string_ value consistent with the value of the expression.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`string(2)` +
|
||||
[green]`"2"` +
|
||||
`>>>` [blue]`string(0.8)` +
|
||||
[green]`"0.8"` +
|
||||
`>>>` [blue]`string([1,2])` +
|
||||
[green]`"[1, 2]"` +
|
||||
`>>>` [blue]`string({1: "one", 2: "two"})` +
|
||||
[green]`"{1: "one", 2: "two"}"` +
|
||||
`>>>` [blue]`string(2:5)` +
|
||||
[green]`"2:5"` +
|
||||
`>>>` [blue]`string(3==2)` +
|
||||
[green]`"false"`
|
||||
|
||||
===== fract()
|
||||
Syntax: `fract(<expr>) -> fraction` +
|
||||
Returns a _fraction_ value consistent with the value of the expression.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`fract(2)` +
|
||||
[green]`2:1` +
|
||||
`>>>` [blue]`fract(2.5)` +
|
||||
[green]`5:2` +
|
||||
`>>>` [blue]`fract("2.5")` +
|
||||
[green]`5:2` +
|
||||
`>>>` [blue]`fract(1.(3))` +
|
||||
[green]`4:3` +
|
||||
`>>>` [blue]`fract([2])` +
|
||||
[red]`Eval Error: fract(): can't convert list to float` +
|
||||
`>>>` [blue]`fract(false)` +
|
||||
[green]`0:1` +
|
||||
`>>>` [blue]`fract(true)` +
|
||||
[green]`1:1`
|
||||
|
||||
===== eval()
|
||||
Syntax: `eval(<string-expr>) -> any` +
|
||||
Computes and returns the value of the [.underline]#string# expression.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`eval( "2 + fract(1.(3))" )` +
|
||||
[green]`10:3`
|
||||
|
||||
===== var()
|
||||
Syntax: +
|
||||
`{4sp}var(<string-expr>, <expr>) -> any` +
|
||||
`{4sp}var(<string-expr>) -> any`
|
||||
|
||||
This function allows you to define variables whose names must include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`var("$x", 3+9)` +
|
||||
[green]`12` +
|
||||
`>>>` [blue]`var("$x")` +
|
||||
[green]`12` +
|
||||
`>>>` [blue]`var("gain%", var("$x"))` +
|
||||
[green]`12` +
|
||||
`>>>` [blue]`var("gain%", var("$x")+1)` +
|
||||
[green]`13`
|
||||
|
||||
===== set
|
||||
Syntax: +
|
||||
`{4sp}set(<string-expr>, <expr>) -> any`
|
||||
|
||||
This function allows you to set the value of a variable whose name can include special characters. The first parameter is the name of the variable and the second parameter is the new value to assign to that variable.
|
||||
|
||||
It is equivalent to the first form of the var() function, but it is more explicit about the intent of changing the value of an existing variable.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`set("$x", 100)` +
|
||||
[green]`100` +
|
||||
`>>>` [blue]`var("$x")` +
|
||||
[green]`100` +
|
||||
|
||||
===== unset()
|
||||
Syntax: +
|
||||
`{4sp}unset(<string-expr>) -> any`
|
||||
|
||||
This function allows you to unset a variable whose name can include special characters. The parameter is the name of the variable to unset.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`unset("$x")` +
|
||||
[green]`nil` +
|
||||
`>>>` [blue]`var("$x")` +
|
||||
[red]`Eval Error: var(): unknown variable "$x"`
|
||||
|
||||
==== Module "fmt"
|
||||
|
||||
===== print()
|
||||
|
||||
===== println()
|
||||
|
||||
==== Module "import"
|
||||
Module actiovation: +
|
||||
|
||||
===== _import()_
|
||||
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` +
|
||||
`{4sp}add(<list-of-num-expr>]) -> any` +
|
||||
`{4sp}add(<iterator-over-num-values>) -> any`
|
||||
|
||||
Returns the sum of the values of the parameters. The parameters can be of any numeric type for which the [blue]`+` operator is defined. The result type depends on the types of the parameters. If all parameters are of the same type, the result is of that type. If the parameters are of different types, the result is of the type that can represent all the parameter types without loss of information. For example, if the parameters are a mix of integers and floats, the result is a float. If the parameters are a mix of number types, the result has the type of the most general one.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`builtin "math.arith"` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`add(1,2,3)` +
|
||||
[green]`6` +
|
||||
`>>>` [blue]`add(1.1,2.1,3.1)` +
|
||||
[green]`6.300000000000001` +
|
||||
`>>>` [blue]`add("1","2","3")` +
|
||||
[red]`Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected` +
|
||||
`>>>` [blue]`add(1:3, 2:3, 3:3)` +
|
||||
[green]`2:1` +
|
||||
`>>>` [blue]`add(1, "2")` +
|
||||
[red]`Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected` +
|
||||
`>>>` [blue]`add([1,2,3])` +
|
||||
[green]`6` +
|
||||
`>>>` [blue]`iterator=$([1,2,3]); add(iterator)` +
|
||||
[green]`6` +
|
||||
`>>>` [blue]`add($([1,2,3]))` +
|
||||
[green]`6`
|
||||
|
||||
===== mul()
|
||||
Syntax: +
|
||||
`{4sp}mul(<num-expr1>, <num-expr2>, ...) -> any` +
|
||||
`{4sp}mul(<list-of-num-expr>]) -> any` +
|
||||
`{4sp}mul(<iterator-over-num-values>) -> any`
|
||||
|
||||
Same as <<_add,add()>> but returns the product of the values of the parameters.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`builtin "math.arith"` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`mul(2,3,4)` +
|
||||
[green]`24`
|
||||
|
||||
==== Module "os.file"
|
||||
|
||||
===== fileOpen()
|
||||
|
||||
===== fileAppend()
|
||||
|
||||
===== fileCreate()
|
||||
|
||||
===== fileClose()
|
||||
|
||||
===== fileWriteText()
|
||||
|
||||
===== fileReadText()
|
||||
|
||||
===== fileReadTextAll()
|
||||
|
||||
|
||||
==== Module "string"
|
||||
|
||||
===== strJoin()
|
||||
Syntax: +
|
||||
`{4sp}strJoin(<separator>, <item> ...) -> string`
|
||||
|
||||
Returns a string obtained by concatenating the string items (sarting from the second argument), separated by the string value of the separator.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strJoin(", ", "one", "two", "three")` +
|
||||
[green]`"one, two, three"` +
|
||||
`>>>` [blue]`strJoin(", ", ["one", "two", "three"])` +
|
||||
[green]`"one, two, three"`
|
||||
|
||||
===== strSub()
|
||||
Syntax: +
|
||||
`{4sp}strSub(<string>, <start-index>=0, <count>=-1) -> string`
|
||||
|
||||
Returns a substring of the specified string starting at the specified index and having the specified length. If the start index is negative, it is interpreted as an offset from the end of the string. If the count is negative, it means to take all characters until the end of the string.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strSub("Hello, world!", 7, 5)` +
|
||||
[green]`"world"` +
|
||||
`>>>` [blue]`strSub("Hello, world!", -6, 5)` +
|
||||
[green]`"world"` +
|
||||
`>>>` [blue]`strSub("Hello, world!", 7)` +
|
||||
[green]`"world!"` +
|
||||
`>>>` [blue]`strSub("Hello, world!", -6)` +
|
||||
[green]`"world!"`
|
||||
|
||||
===== strSplit()
|
||||
Syntax: +
|
||||
`{4sp}strSplit(<string>, <separator>="", <count>=-1) -> list`
|
||||
|
||||
Returns a list of substrings obtained by splitting the specified string using the specified separator. If the separator is an empty string, the string is split into individual characters. If the count is negative, it means to split all occurrences of the separator.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strSplit("one, two, three", ", ")` +
|
||||
[green]`["one", "two", "three"]` +
|
||||
`>>>` [blue]`strSplit("one, two, three", ", ", 2)` +
|
||||
[green]`["one", "two, three"]` +
|
||||
`>>>` [blue]`strSplit("one, two, three")` +
|
||||
[green]`["o", "n", "e", ",", " ", "t", "w", "o", ",", " ", "t", "h", "r", "e", "e"]`
|
||||
|
||||
===== strTrim()
|
||||
Syntax: +
|
||||
`{4sp}strTrim(<string>) -> string`
|
||||
|
||||
Returns a string obtained by removing leading and trailing whitespace characters from the specified string.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strTrim(" Hello, world! ")` +
|
||||
[green]`"Hello, world!"`
|
||||
|
||||
===== strStartsWith()
|
||||
Syntax: +
|
||||
`{4sp}strStartsWith(<string>, <prefix> ...) -> bool`
|
||||
|
||||
Returns a boolean indicating whether the specified string starts with any of the given prefixes.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strStartsWith("Hello, world!", "Hello")` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`strStartsWith("Hello, world!", "world")` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`strStartsWith("Hello, world!", "Hi", "He")` +
|
||||
[green]`true`
|
||||
|
||||
|
||||
===== strEndsWith()
|
||||
Syntax: +
|
||||
`{4sp}strEndsWith(<string>, <suffix> ...) -> bool`
|
||||
|
||||
Returns a boolean indicating whether the specified string ends with any of the given suffixes.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strEndsWith("Hello, world!", "world!")` +
|
||||
[green]`true` +
|
||||
`>>>` [blue]`strEndsWith("Hello, world!", "Hello")` +
|
||||
[green]`false` +
|
||||
`>>>` [blue]`strEndsWith("Hello, world!", "Hi", "world!")` +
|
||||
[green]`true`
|
||||
|
||||
|
||||
===== strUpper()
|
||||
Syntax: +
|
||||
`{4sp}strUpper(<string>) -> string`
|
||||
|
||||
Returns a string obtained by converting all characters of the specified string to uppercase.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strUpper("Hello, world!")` +
|
||||
[green]`"HELLO, WORLD!"`
|
||||
|
||||
===== strLower()
|
||||
Syntax: +
|
||||
`{4sp}strLower(<string>) -> string`
|
||||
|
||||
Returns a string obtained by converting all characters of the specified string to lowercase.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`strLower("Hello, world!")` +
|
||||
[green]`"hello, world!"`
|
||||
|
||||
== Iterators
|
||||
Iterators are objects that can be used to traverse collections, such as lists and dictionaries. They are created by providing a _data-source_ object, the collection, in a `$(<data-source>)` expression. Once an iterator is created, it can be used to access the elements of the collection one by one.
|
||||
|
||||
In general, data-sources are objects that can be iterated over. They are defined as dictionaries having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The _next_ function must return a special value, [blue]_nil_, when there are no more elements to iterate over.
|
||||
|
||||
Lists and dictionaries are implicit data-sources. The syntax for creating an iterator is as follows.
|
||||
|
||||
.Iterator creation syntax
|
||||
====
|
||||
*_iterator_* = "**$(**" _data-source_ "**)**" +
|
||||
_data-source_ = _explicit_ | _list-spec_ | _dict-spec_ | _custom-data-source_
|
||||
|
||||
_explicit_ = _any-expr_ { "**,**" _any-expr_ }
|
||||
|
||||
_list-spec_ = _list_ _range-options_ +
|
||||
_list_ = "**[**" _any-expr_ { "**,**" _any-expr_ } "**]**" +
|
||||
_range-options_ = [ "**,**" _start-index_ [ "**,**" _end-index_ [ "**,**" _step_ ]]] +
|
||||
_start-index_, _end-index_, _step_ = _integer-expr_
|
||||
|
||||
_dict-spec_ = _dict_ _dict-options_ +
|
||||
_dict_ = "**{**" _key-value-pair_ { "**,**" _key-value-pair_ } "**}**" +
|
||||
_key-value-pair_ = _scalar-value_ "**:**" _any-expr_ +
|
||||
_scalar-value_ = _string_ | _number_ | _boolean_ +
|
||||
_dict-options_ = [ "**,**" _sort-order_ [ "**,**" _iter-mode_ ] ] +
|
||||
_sort-order_ = _asc-order_ | _desc-order_ | _no-sort_ | _default-order_ +
|
||||
_asc-order_ = "**asc**" | "**a**" +
|
||||
_desc-order_ = "**desc**" | "**d**" +
|
||||
_no-sort_ = "**none**" | "**nosort**" | "**no-sort**" | "**n**" +
|
||||
_default-order_ = "**default**" | "" +
|
||||
_iter-mode_ = _keys-iter_ | _values-iter_ | _items-iter_ | _default-iter_ +
|
||||
_keys-iter_ = "**key**" | "**keys**" | "**k**" +
|
||||
_values-iter_ = "**value**" | "**values**" | "**v**" +
|
||||
_items-iter_ = "**item**" | "**items**" | "**i**" +
|
||||
_default-iter_ = "**default**" | ""
|
||||
|
||||
_custom-data-source_ = _dict_ having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator.
|
||||
====
|
||||
|
||||
NOTE: Currently, _default-order_ is the same as _asc-order_. In the future, it will be possible to specify a custom sorting function to define the default order.
|
||||
|
||||
NOTE: Currently, _default-iter_ is the same as _keys-iter_. In the future, it will be possible to specify a custom iteration function to define the default iteration mode.
|
||||
|
||||
.Example: iterator over an explicit list of elements
|
||||
`>>>` [blue]`it = $("A", "B", "C")` +
|
||||
[green]`$(#3)` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"A"` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"B"` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"C"` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[red]`Eval Error: EOF`
|
||||
|
||||
.Example: iterator over a list
|
||||
`>>>` [blue]`it = $(["one", "two", "three"])` +
|
||||
[green]`$(#3)` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"one"` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"two"` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"three"` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[red]`Eval Error: EOF`
|
||||
|
||||
When creating a list-type iterator expression, you can specify a range of indices and a step to iterate over a subset of the list.
|
||||
|
||||
Indexing starts at 0. If the start index is not specified, it defaults to 0. If the end index is not specified, it defaults to the index of the last element of the list. If the step is not specified, it defaults to 1.
|
||||
|
||||
Negative indexes are allowed. They are interpreted as offsets from the end of the list. For example, an end index of -1 means the index of the last element of the list, an end index of -2 means the index of the second to last element of the list, and so on.
|
||||
|
||||
Negative steps are also allowed. They are interpreted as reverse iteration. For example, a step of -1 means to iterate over the list in reverse order.
|
||||
|
||||
.Example: iterator over a list with index range and step
|
||||
`>>>` [blue]`it = $([1, 2, 3, 4, 5], 1, 4, 2)` +
|
||||
[green]`$(#5)` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`4` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[red]`Eval Error: EOF`
|
||||
|
||||
.Example: iterator over a list in reverse order
|
||||
`>>>` [blue]`it = $([1, 2, 3], -1, 0, -1)` +
|
||||
[green]`$(#3)` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`3` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`2` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`1` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[red]`Eval Error: EOF`
|
||||
|
||||
|
||||
=== Operators on iterators
|
||||
The above example shows the use of the [blue]`{plusplus}` operator to get the next element of an iterator. The [blue]`{plusplus}` operator is a postfix operator that can be used with iterators. It returns the next element of the collection and updates the state of the iterator. When there are no more elements to iterate over, it returns the error [red]_Eval Error: EOF_.
|
||||
|
||||
|
||||
After the first use of the [blue]`{plusplus}` operator, the prefixed operato [blue]`{star}` operator can be used to get the current element of the collection without updating the state of the iterator. When there are no more elements to iterate over, it returns the error [red]_Eval Error: EOF_. Same error is returned if the [blue]`{star}` operator is used before the first use of the [blue]`{plusplus}` operator.
|
||||
|
||||
.Example: using the [blue]`{star}` operator
|
||||
`>>>` [blue]`it = $(["one", "two", "three"])` +
|
||||
[green]`$(#3)` +
|
||||
`>>>` [blue]`{star}it` +
|
||||
[red]`Eval Error: EOF` +
|
||||
`>>>` [blue]`it{plusplus}` +
|
||||
[green]`"one"` +
|
||||
`>>>` [blue]`{star}it` +
|
||||
[green]`"one"`
|
||||
|
||||
==== Named operators
|
||||
Named operators are operators that are identified by a name instead of a symbol. They are implicitly defined and can be called using a special syntax. For example, the [blue]`{plusplus}` has the equivalent named operator [blue]`.next`.
|
||||
|
||||
.Available named operators
|
||||
* *_.next_*: same as [blue]`{plusplus}`.
|
||||
* *_.current_*: same as [blue]`{star}`.
|
||||
* *_.reset_*: resets the state of the iterator to the initial state.
|
||||
* *_.count_*: returns the number of elements in the iterator already visited.
|
||||
* *_.index_*: returns the index of the current element in the iterator. Before the first use of the [blue]`{plusplus}` operator, it returns the error [red]_-1_.
|
||||
|
||||
TIP: Iterators built on custom data-sources can provide additional named operators, depending on the functionality they want to expose. For example, an iterator over a list could provide a named operator called [blue]`.reverse` that returns the next element of the collection in reverse order.
|
||||
|
||||
.Example: using the named operators
|
||||
`>>>` [blue]`it = $(["one", "two", "three"])` +
|
||||
[green]`$(#3)` +
|
||||
`>>>` [blue]`it.next` +
|
||||
[green]`"one"` +
|
||||
`>>>` [blue]`it.current` +
|
||||
[green]`"one"` +
|
||||
`>>>` [blue]`it.next` +
|
||||
[green]`"two"` +
|
||||
`>>>` [blue]`it.reset` +
|
||||
[green]`<nil>` +
|
||||
`>>>` [blue]`it.current` +
|
||||
[red]`Eval Error: EOF` +
|
||||
`>>>` [blue]`it.next` +
|
||||
[green]`"one"`
|
||||
|
||||
=== Iterator over custom data-sources
|
||||
It is possible to create iterators over custom data-sources by defining a dictionary that has the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.
|
||||
|
||||
#TODO: custom data-sources#
|
||||
|
||||
=== [blue]_import()_
|
||||
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
|
||||
|
||||
|
||||
== Plugins
|
||||
|
||||
1109
doc/Expr.html
1109
doc/Expr.html
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
@ -21,22 +21,25 @@ const (
|
||||
CountName = "count"
|
||||
FilterName = "filter"
|
||||
MapName = "map"
|
||||
KeyName = "key"
|
||||
ValueName = "value"
|
||||
)
|
||||
|
||||
type Iterator interface {
|
||||
Typer
|
||||
fmt.Stringer
|
||||
Next() (item any, err error) // must return io.EOF after the last item
|
||||
Current() (item any, err error)
|
||||
Index() int
|
||||
Count() int
|
||||
HasOperation(name string) bool
|
||||
CallOperation(name string, args map[string]any) (value any, err error)
|
||||
}
|
||||
|
||||
type ExtIterator interface {
|
||||
Iterator
|
||||
Reset() error
|
||||
Clean() error
|
||||
HasOperation(name string) bool
|
||||
CallOperation(name string, args map[string]any) (value any, err error)
|
||||
}
|
||||
|
||||
func errNoOperation(name string) error {
|
||||
|
||||
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 {
|
||||
@ -149,12 +134,12 @@ func (it *ListIterator) Count() int {
|
||||
return it.count
|
||||
}
|
||||
|
||||
func (it *ListIterator) Reset() (error) {
|
||||
func (it *ListIterator) Reset() error {
|
||||
it.index = it.start - it.step
|
||||
it.count = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *ListIterator) Clean() (error) {
|
||||
func (it *ListIterator) Clean() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -86,37 +86,49 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
|
||||
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
|
||||
if ds != nil {
|
||||
var dc *dataCursor
|
||||
dcCtx := ctx.Clone()
|
||||
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
|
||||
var args []any
|
||||
var resource any
|
||||
if len(opTerm.children) > 1 {
|
||||
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
|
||||
if len(ds) > 0 {
|
||||
var dc *dataCursor
|
||||
dcCtx := ctx.Clone()
|
||||
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
|
||||
var args []any
|
||||
var resource any
|
||||
if len(opTerm.children) > 1 {
|
||||
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
args = []any{}
|
||||
}
|
||||
|
||||
actualParams := bindActualParams(initFunc, args)
|
||||
|
||||
initCtx := ctx.Clone()
|
||||
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
|
||||
return
|
||||
}
|
||||
exportObjects(dcCtx, initCtx)
|
||||
dc = NewDataCursor(dcCtx, ds, resource)
|
||||
} else {
|
||||
args = []any{}
|
||||
dc = NewDataCursor(dcCtx, ds, nil)
|
||||
}
|
||||
|
||||
actualParams := bindActualParams(initFunc, args)
|
||||
|
||||
initCtx := ctx.Clone()
|
||||
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
|
||||
return
|
||||
}
|
||||
exportObjects(dcCtx, initCtx)
|
||||
dc = NewDataCursor(dcCtx, ds, resource)
|
||||
v = dc
|
||||
} else {
|
||||
dc = NewDataCursor(dcCtx, ds, nil)
|
||||
if dictIt, ok := firstChildValue.(*DictType); ok {
|
||||
var args []any
|
||||
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
|
||||
v, err = NewDictIterator(dictIt, args)
|
||||
}
|
||||
} else {
|
||||
err = opTerm.children[0].Errorf("the data-source must be a dictionary")
|
||||
}
|
||||
}
|
||||
|
||||
v = dc
|
||||
} else if list, ok := firstChildValue.(*ListType); ok {
|
||||
var args []any
|
||||
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||
}
|
||||
|
||||
if it, ok := childValue.(Iterator); ok {
|
||||
var namePrefix string
|
||||
v, err = it.Next()
|
||||
|
||||
if opTerm.children[0].symbol() == SymVariable {
|
||||
namePrefix = opTerm.children[0].source()
|
||||
}
|
||||
ctx.UnsafeSetVar(namePrefix+"_index", it.Index())
|
||||
if c, err1 := it.Current(); err1 == nil {
|
||||
ctx.UnsafeSetVar(namePrefix+"_current", c)
|
||||
}
|
||||
|
||||
if it.HasOperation(KeyName) {
|
||||
if k, err1 := it.CallOperation(KeyName, nil); err1 == nil {
|
||||
ctx.UnsafeSetVar(namePrefix+"_key", k)
|
||||
}
|
||||
}
|
||||
if it.HasOperation(ValueName) {
|
||||
if v1, err1 := it.CallOperation(ValueName, nil); err1 == nil {
|
||||
ctx.UnsafeSetVar(namePrefix+"_value", v1)
|
||||
}
|
||||
}
|
||||
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||
v = childValue
|
||||
i, _ := childValue.(int64)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,16 +52,21 @@ func TestFuncBase(t *testing.T) {
|
||||
/* 38 */ {`bool(1.0)`, true, nil},
|
||||
/* 39 */ {`bool("1")`, true, nil},
|
||||
/* 40 */ {`bool(false)`, false, nil},
|
||||
/* 41 */ {`bool([1])`, nil, `bool(): can't convert list to bool`},
|
||||
/* 42 */ {`dec(false)`, float64(0), nil},
|
||||
/* 43 */ {`dec(1:2)`, float64(0.5), nil},
|
||||
/* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
|
||||
/* 45 */ {`eval("a=3"); a`, int64(3), nil},
|
||||
/* 41 */ {`bool([1])`, true, nil},
|
||||
/* 42 */ {`bool([])`, false, nil},
|
||||
/* 43 */ {`bool({})`, false, nil},
|
||||
/* 44 */ {`bool({1:"one"})`, true, nil},
|
||||
/* 45 */ {`dec(false)`, float64(0), nil},
|
||||
/* 46 */ {`dec(1:2)`, float64(0.5), nil},
|
||||
/* 47 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
|
||||
/* 48 */ {`eval("a=3"); a`, int64(3), nil},
|
||||
/* 49 */ {`int(5:2)`, int64(2), nil},
|
||||
|
||||
// /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
|
||||
}
|
||||
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// runTestSuiteSpec(t, section, inputs, 45)
|
||||
// runTestSuiteSpec(t, section, inputs, 49)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,8 +27,15 @@ func TestIteratorParser(t *testing.T) {
|
||||
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
|
||||
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
|
||||
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
|
||||
/* 19 */ {`it=$({1:"one",2:"two",3:"three"}); it++`, int64(1), 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},
|
||||
/* 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, 18)
|
||||
// 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
|
||||
}
|
||||
47
utils.go
47
utils.go
@ -6,11 +6,7 @@ package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IsString(v any) (ok bool) {
|
||||
@ -218,6 +214,15 @@ func ToGoInt(value any, description string) (i int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ToGoString(value any, description string) (s string, err error) {
|
||||
if s, ok := value.(string); ok {
|
||||
return s, nil
|
||||
} else {
|
||||
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
||||
result := make([]V, len(ts))
|
||||
for i, t := range ts {
|
||||
@ -225,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