Compare commits

..

39 Commits

Author SHA1 Message Date
camoroso fe5c8e9619 tests improved 2024-06-26 04:29:40 +02:00
camoroso 7164e8816c operator-bool.go: error messages improved 2024-06-26 04:28:53 +02:00
camoroso e0f3b028fc builtin-string.go: replaced ToInt() with ToGoInt() 2024-06-26 04:28:07 +02:00
camoroso 522b5983bd builtin-base.go: string() function added 2024-06-26 04:27:14 +02:00
camoroso f1e2163277 replaced ToInt() with ToGoInt() 2024-06-26 04:25:49 +02:00
camoroso bbdf498cf3 function.go: commented out some unused error functions 2024-06-26 04:23:48 +02:00
camoroso f75c991ed2 common-errors.go: commented out some unused error functions 2024-06-26 04:23:14 +02:00
camoroso a7836143cc utils.go: ToInt() renamed as ToGoInt() 2024-06-26 04:22:29 +02:00
camoroso ba9b9cb28f a lot oh changes to the test framework and new test files t_builtin-fmt_test.go and t_plugin_test.go added 2024-06-25 10:59:03 +02:00
camoroso ef1baa11a8 builtin-fmt.go: print() and println() can write data to a generic Writer. They fetch the writer from the control variable '_stdout'.
If _stdout is nil, they write to os.Stdout
2024-06-25 10:55:54 +02:00
camoroso cfddbd60b9 control.go: use of UnsafeSetVar() in place of SetVar(). SetCtrl() added 2024-06-25 10:53:05 +02:00
camoroso 0760141caf utils.go: Removed CloneMap(). fromGenericAny(): check v against nil 2024-06-25 10:48:31 +02:00
camoroso b9e780e659 ExprContext: removed Merge() method 2024-06-25 10:45:54 +02:00
camoroso e41ddc664c Improved closure context persistence. Now it is possibile to define counters like this func(base){func(){@base=base+1}} 2024-06-24 07:20:17 +02:00
camoroso 3b641ac793 Doc: little changes 2024-06-21 09:06:25 +02:00
camoroso a1a62b6794 Doc: little changes 2024-06-20 07:10:59 +02:00
camoroso a18d92534f Doc: little changes 2024-06-19 22:51:37 +02:00
camoroso ec0963e26f Doc: little changes 2024-06-19 22:46:35 +02:00
camoroso be992131b1 Doc: little changes 2024-06-19 22:25:38 +02:00
camoroso 0e3960321f Doc: little changes 2024-06-19 22:22:10 +02:00
camoroso 61d34fef7d Doc: little changes 2024-06-19 22:20:28 +02:00
camoroso 581f1585e6 Doc: embedded images 2024-06-19 22:04:13 +02:00
camoroso 531cb1c249 Doc: concepts 2024-06-19 09:38:02 +02:00
camoroso d1b468f35b t_list_test.go: new test cases added 2024-06-19 09:24:50 +02:00
camoroso ff9cf80c66 operator-index.go: ConstLastIndex is checked 2024-06-19 09:24:19 +02:00
camoroso d63b18fd76 parser.go: SymColon resets the firstToken flag; that is needed to specify range indeces that can define negative limits 2024-06-19 09:22:40 +02:00
camoroso 019470faf1 operator-range.go: Fixed priority bug; when range only has the left limit, right limit is set to ConstLastIndex constant 2024-06-19 09:20:02 +02:00
camoroso 302430d57d common-params.go: added the constant ConstLastIndex 2024-06-19 09:17:46 +02:00
camoroso 62ef0d699d token.go: new function IsOneOf() 2024-06-19 09:16:19 +02:00
camoroso 866de759dd file-reader.expr: simpler implementation 2024-06-17 14:07:39 +02:00
camoroso b1d6b6de44 refactored dict's item access 2024-06-17 14:06:33 +02:00
camoroso 7e357eea62 changed to comply new builtin-os-file.go's function names 2024-06-17 09:54:20 +02:00
camoroso d6b4c79736 operator-index.go: dict item access by generic keys implemented 2024-06-17 09:05:23 +02:00
camoroso d066344af8 builtin-os-file.go: changed read and write function names; added fileReadTextAll 2024-06-17 06:59:15 +02:00
camoroso f41dba069e plugin.go: support Liteide and VScode to debug executable file name 2024-06-17 06:57:47 +02:00
camoroso 703ecf6829 Added utility function GetLast() to context 2024-06-17 06:56:32 +02:00
camoroso ba479a1b99 Function call moved from operand-func.go to function.go 2024-06-17 06:54:50 +02:00
camoroso 24e6a293b0 common-params.go: ParamName added 2024-06-12 11:16:57 +02:00
camoroso 28f464c4dc plugins.go: If the main program's file name ends with '.debug', plugins will be loaded from file with name ending with '.debug' 2024-06-12 11:15:31 +02:00
49 changed files with 1469 additions and 693 deletions
+17 -19
View File
@@ -17,19 +17,31 @@ type exprFunctor struct {
// } // }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor { func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params, defCtx: ctx} var defCtx ExprContext
if ctx != nil {
// if ctx.GetParent() != nil {
// defCtx = ctx.Clone()
// defCtx.SetParent(ctx)
// } else {
defCtx = ctx
// }
}
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
func (functor *exprFunctor) GetDefinitionContext() ExprContext {
return functor.defCtx
} }
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
if functor.defCtx != nil { // if functor.defCtx != nil {
ctx.Merge(functor.defCtx) // ctx.Merge(functor.defCtx)
} // }
for i, p := range functor.params { for i, p := range functor.params {
if i < len(args) { if i < len(args) {
arg := args[i] arg := args[i]
if funcArg, ok := arg.(Functor); ok { if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1)
paramSpecs := funcArg.GetParams() paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs) ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
} else { } else {
@@ -42,17 +54,3 @@ func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (re
result, err = functor.expr.Eval(ctx) result, err = functor.expr.Eval(ctx)
return return
} }
func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
if err == nil {
if err = checkFunctionCall(ctx, funcName, &params); err == nil {
if v, err = ctx.Call(funcName, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
}
return
}
+28
View File
@@ -5,6 +5,7 @@
package expr package expr
import ( import (
"fmt"
"math" "math"
"strconv" "strconv"
) )
@@ -120,6 +121,32 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return return
} }
func stringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
result = strconv.FormatInt(v, 10)
case float64:
result = strconv.FormatFloat(v, 'g', -1, 64)
case bool:
if v {
result = "true"
} else {
result = "false"
}
case string:
result = v
case *FractionType:
result = v.ToString(0)
case Formatter:
result = v.ToString(0)
case fmt.Stringer:
result = v.String()
default:
err = ErrCantConvert(name, v, "string")
}
return
}
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) { switch v := args[0].(type) {
case int64: case int64:
@@ -175,6 +202,7 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams) ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams) ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams) ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams)
ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{ ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
NewFuncParam(ParamValue), NewFuncParam(ParamValue),
NewFuncParamFlagDef("denominator", PfDefault, 1), NewFuncParamFlagDef("denominator", PfDefault, 1),
+18 -3
View File
@@ -4,11 +4,26 @@
// builtin-fmt.go // builtin-fmt.go
package expr package expr
import "fmt" import (
"fmt"
"io"
"os"
)
func getStdout(ctx ExprContext) io.Writer {
var w io.Writer
if wany, exists := ctx.GetVar(ControlStdout); exists && wany != nil {
w, _ = wany.(io.Writer)
}
if w == nil {
w = os.Stdout
}
return w
}
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) { func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int var n int
if n, err = fmt.Print(args...); err == nil { if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil {
result = int64(n) result = int64(n)
} }
return return
@@ -16,7 +31,7 @@ func printFunc(ctx ExprContext, name string, args []any) (result any, err error)
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) { func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int var n int
if n, err = fmt.Println(args...); err == nil { if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil {
result = int64(n) result = int64(n)
} }
return return
+52 -16
View File
@@ -68,7 +68,7 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("createFile") err = errMissingFilePath(name)
} }
return return
} }
@@ -80,7 +80,7 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err
result = &osReader{fh: fh, reader: bufio.NewReader(fh)} result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
} }
} else { } else {
err = errMissingFilePath("openFile") err = errMissingFilePath(name)
} }
return return
} }
@@ -92,7 +92,7 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("appendFile") err = errMissingFilePath(name)
} }
return return
} }
@@ -118,13 +118,13 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("closeFileFunc", handle) err = errInvalidFileHandle(name, handle)
} }
result = err == nil result = err == nil
return return
} }
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
@@ -142,12 +142,12 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle) err = errInvalidFileHandle(name, invalidFileHandle)
} }
return return
} }
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
@@ -165,23 +165,50 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
limit = s[0] limit = s[0]
} }
if v, err = r.reader.ReadString(limit); err == nil { v, err = r.reader.ReadString(limit)
if len(v) > 0 && v[len(v)-1] == limit { if err == io.EOF {
err = nil
}
if len(v) > 0 {
if v[len(v)-1] == limit {
result = v[0 : len(v)-1] result = v[0 : len(v)-1]
} else { } else {
result = v result = v
} }
} }
if err == io.EOF {
err = nil
}
} else { } else {
invalidFileHandle = handle invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("readFileFunc", invalidFileHandle) err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
invalidFileHandle = args[0]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
var b []byte
b, err = io.ReadAll(r.reader)
result = string(b)
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
} }
return return
} }
@@ -190,21 +217,30 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileWrite", NewGolangFunctor(writeFileFunc), TypeInt, []ExprFuncParam{
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(TypeHandle),
})
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(TypeHandle),
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""), NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
}) })
ctx.RegisterFunc("fileRead", NewGolangFunctor(readFileFunc), TypeString, []ExprFuncParam{
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(TypeHandle),
NewFuncParamFlagDef("limitCh", PfDefault, "\n"), NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
}) })
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(TypeHandle),
}) })
} }
+2 -2
View File
@@ -66,11 +66,11 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0]) return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
} }
if start, err = ToInt(args[1], name+"()"); err != nil { if start, err = ToGoInt(args[1], name+"()"); err != nil {
return return
} }
if count, err = ToInt(args[2], name+"()"); err != nil { if count, err = ToGoInt(args[2], name+"()"); err != nil {
return return
} }
+6 -6
View File
@@ -36,15 +36,15 @@ func ErrFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s(): division by zero", funcName) return fmt.Errorf("%s(): division by zero", funcName)
} }
func ErrDivisionByZero() error { // func ErrDivisionByZero() error {
return fmt.Errorf("division by zero") // return fmt.Errorf("division by zero")
} // }
// --- Parameter errors // --- Parameter errors
func ErrMissingRequiredParameter(funcName, paramName string) error { // func ErrMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName) // return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
} // }
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error { func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName) return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
+6
View File
@@ -15,7 +15,13 @@ const (
ParamStart = "start" ParamStart = "start"
ParamEnd = "end" ParamEnd = "end"
ParamValue = "value" ParamValue = "value"
ParamName = "name"
ParamEllipsis = "..." ParamEllipsis = "..."
ParamFilepath = "filepath" ParamFilepath = "filepath"
ParamDirpath = "dirpath" ParamDirpath = "dirpath"
) )
// to be moved in its own source file
const (
ConstLastIndex = 0xFFFF_FFFF
)
+6
View File
@@ -41,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
} }
} }
} }
func exportObjectsToParent(sourceCtx ExprContext) {
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
exportObjects(parentCtx, sourceCtx)
}
}
+5 -2
View File
@@ -10,6 +10,7 @@ type Functor interface {
SetFunc(info ExprFunc) SetFunc(info ExprFunc)
GetFunc() ExprFunc GetFunc() ExprFunc
GetParams() []ExprFuncParam GetParams() []ExprFuncParam
GetDefinitionContext() ExprContext
} }
// ---- Function Param Info // ---- Function Param Info
@@ -31,22 +32,24 @@ type ExprFunc interface {
Functor() Functor Functor() Functor
Params() []ExprFuncParam Params() []ExprFuncParam
ReturnType() string ReturnType() string
PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext)
} }
// ----Expression Context // ----Expression Context
type ExprContext interface { type ExprContext interface {
Clone() ExprContext Clone() ExprContext
Merge(ctx ExprContext) // Merge(ctx ExprContext)
SetParent(ctx ExprContext) SetParent(ctx ExprContext)
GetParent() (ctx ExprContext) GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any) SetVar(varName string, value any)
UnsafeSetVar(varName string, value any) UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string) EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string) EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) (item ExprFunc, exists bool) GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error) Call(name string, args []any) (result any, err error)
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
RegisterFuncInfo(info ExprFunc) RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
} }
+10 -3
View File
@@ -11,6 +11,7 @@ const (
ControlBoolShortcut = "_bool_shortcut" ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path" ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context" ControlParentContext = "_parent_context"
ControlStdout = "_stdout"
) )
// Other control variables // Other control variables
@@ -23,11 +24,17 @@ const (
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr" init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
) )
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
current, _ = ctx.GetVar(name)
ctx.UnsafeSetVar(name, value)
return
}
func initDefaultVars(ctx ExprContext) { func initDefaultVars(ctx ExprContext) {
if _, exists := ctx.GetVar(ControlPreset); exists { if _, exists := ctx.GetVar(ControlPreset); exists {
return return
} }
ctx.SetVar(ControlPreset, true) ctx.UnsafeSetVar(ControlPreset, true)
ctx.SetVar(ControlBoolShortcut, true) ctx.UnsafeSetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlSearchPath, init_search_path) ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
} }
+284 -137
View File
@@ -9,6 +9,7 @@ Expressions calculator
:icons: font :icons: font
:icon-set: fi :icon-set: fi
:numbered: :numbered:
:data-uri:
//:table-caption: Tabella //:table-caption: Tabella
//:figure-caption: Diagramma //:figure-caption: Diagramma
:docinfo1: :docinfo1:
@@ -19,21 +20,52 @@ Expressions calculator
:rouge-style: gruvbox :rouge-style: gruvbox
// :rouge-style: colorful // :rouge-style: colorful
//:rouge-style: monokay //:rouge-style: monokay
// Work around to manage double-column in back-tick quotes
:2c: ::
toc::[] toc::[]
#TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)# #TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
== Expr == Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions. _Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology === Concepts and terminology
#TODO#
Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. _Expr_ package provides these macro functions:
* *_Scanner_* -- Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.
* *_Parser_* -- Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the _Expr_ syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other.
* *_Calculator_*. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.
image::expression-diagram.png[] 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.
==== 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_.
_Expr_ parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.
The result of each expression of a multi-expression is stored in an automatic variable named _last_. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.
==== Calculation context
All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called _context_.
The expression context is analogous to the stack-frame of other programming languages. When a function is called, a new context is allocated to store local definitions.
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 calling 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 createt 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_.
=== `dev-expr` test tool === `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development. 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.
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input. `dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
@@ -47,11 +79,10 @@ Here are some examples of execution.
# Type 'exit' or Ctrl+D to quit the program. # Type 'exit' or Ctrl+D to quit the program.
[user]$ ./dev-expr [user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it) dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0 Based on the Expr package v0.19.0
Type help to get the list of available commands Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help >>> help
--- REPL commands: --- REPL commands:
source -- Load a file as input source -- Load a file as input
@@ -61,6 +92,7 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
help -- Show command list help -- Show command list
ml -- Enable/Disable multi-line output ml -- Enable/Disable multi-line output
mods -- List builtin modules mods -- List builtin modules
output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
--- Command line options: --- Command line options:
-b <builtin> Import builtin modules. -b <builtin> Import builtin modules.
@@ -70,10 +102,11 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
-i Force REPL operation when all -e occurences have been processed -i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu -h, --help, help Show this help menu
-m, --modules List all builtin modules -m, --modules List all builtin modules
--noout Disable printing of expression results
-p Print prefix form -p Print prefix form
-t Print tree form <2> -t Print tree form <2>
-v, --version Show program version
>>> >>>
---- ----
<1> Only available for single fraction values <1> Only available for single fraction values
@@ -83,8 +116,8 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
[source,shell] [source,shell]
---- ----
[user]$ ./dev-expr [user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it) dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0 Based on the Expr package v0.19.0
Type help to get the list of available commands Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
@@ -101,21 +134,21 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
7 7
- -
6 6
>>> 1+2 but 5|2+0.5 <4> >>> 4+2 but 5|2+0.5 <4>
3 3
>>> 1+2; 5|2+0.5 <5> >>> 4+2; 5|2+0.5 <5>
3 3
>>> >>>
---- ----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary. <1> Number bases: 0x = _hexadecimal_, 0o = _octal_, 0b = _binary_.
<2> Fractions: numerator | denominator. <2> Fractions: _numerator_ | _denominator_.
<3> Activate multi-line output of fractions. <3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>. <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 of the previous single expression but this it is obtained with two separated calculations.
== Data types == Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists. _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.
=== Numbers === Numbers
_Expr_ supports three type of numbers: _Expr_ supports three type of numbers:
@@ -155,7 +188,7 @@ Value range: *-9223372036854775808* to *9223372036854775807*
| [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2 | [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5 | [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1 | [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
|=== |===
^(*)^ See also the _float division_ [blue]`./` below. ^(*)^ See also the _float division_ [blue]`./` below.
@@ -173,15 +206,20 @@ _dec-seq_ = _see-integer-literal-syntax_
.Examples .Examples
`>>>` [blue]`1.0` + `>>>` [blue]`1.0` +
[green]`1` + [green]`1`
`>>>` [blue]`0.123` + `>>>` [blue]`0.123` +
[green]`0.123` + [green]`0.123`
`>>>` [blue]`4.5e+3` + `>>>` [blue]`4.5e+3` +
[green]`4500` + [green]`4500`
`>>>` [blue]`4.5E-33` + `>>>` [blue]`4.5E-33` +
[green]`4.5e-33` + [green]`4.5e-33`
`>>>` [blue]`4.5E-3` + `>>>` [blue]`4.5E-3` +
[green]`0.0045` + [green]`0.0045`
`>>>` [blue]`4.5E10` + `>>>` [blue]`4.5E10` +
[green]`4.5e+10` [green]`4.5e+10`
@@ -211,35 +249,43 @@ _digit-seq_ = _see-integer-literal-syntax_
==== ====
.Examples .Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` + `>>>` [blue]`1 | 2` +
[green]`1|2` + [green]`1|2`
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ + `>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
[green]`2|3` + [green]`2|3`
`>>>` [blue]`1|2 + 2|3` + `>>>` [blue]`1|2 + 2|3` +
[green]`7|6` + [green]`7|6`
`>>>` [blue]`1|2 * 2|3` + `>>>` [blue]`1|2 * 2|3` +
[green]`1|3` + [green]`1|3`
`>>>` [blue]`1|2 / 1|3` + `>>>` [blue]`1|2 / 1|3` +
[green]`3|2` + [green]`3|2`
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ + `>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
[green]`1.5` + [green]`1.5`
`>>>` [blue]`-1|2` + `>>>` [blue]`-1|2` +
[green]`-1|2` + [green]`-1|2`
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ + `>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ + [red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_
`>>>` [blue]`1|(-2)` + `>>>` [blue]`1|(-2)` +
[green]`-1|2` [green]`-1|2`
// ----
Fractions can be used together with integers and floats in expressions. Fractions can be used together with integers and floats in expressions.
.Examples .Examples
`>>>` [blue]`1|2 + 5` + `>>>` [blue]`1|2 + 5` +
[green]`11|2` + [green]`11|2`
`>>>` [blue]`4 - 1|2` + `>>>` [blue]`4 - 1|2` +
[green]`7|2` + [green]`7|2`
`>>>` [blue]`1.0 + 1|2` + `>>>` [blue]`1.0 + 1|2` +
[green]`1.5` [green]`1.5`
@@ -250,12 +296,15 @@ Strings are character sequences enclosed between two double quote [blue]`"`.
.Examples .Examples
`>>>` [blue]`"I'm a string"` + `>>>` [blue]`"I'm a string"` +
[green]`I'm a string` + [green]`I'm a string`
`>>>` [blue]`"123abc?!"` + `>>>` [blue]`"123abc?!"` +
[green]`123abc?!` + [green]`123abc?!`
`>>>` [blue]`"123\nabc"` + `>>>` [blue]`"123\nabc"` +
[green]`123` + [green]`123` +
[green]`abc` + [green]`abc`
`>>>` [blue]`"123\tabc"` + `>>>` [blue]`"123\tabc"` +
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc` [green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
@@ -272,25 +321,36 @@ Some arithmetic operators can also be used with strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_ | [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|=== |===
The items of strings can be accessed using the dot `.` operator. The items of strings can be accessed using the square `[]` operator.
.Item access syntax .Item access syntax
==== ====
_item_ = _string-expr_ "**.**" _integer-expr_ *_item_* = _string-expr_ "**[**" _integer-expr_ "**]**"
====
.Sub-string syntax
====
*_sub-string_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
==== ====
.String examples .String examples
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ + `>>>` [blue]`s="abcd"` [gray]_// assign the string to variable s_ +
[green]`abc` + [green]`"abcd"`
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
[green]`b` + `>>>` [blue]`s[1]` [gray]_// char at position 1 (starting from 0)_ +
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ + [green]`"b"`
[green]`c` +
`>>>` [blue]`s.[-1]` [gray]_// char at position -1, the rightmost one_ +
[green]`"d"`
`>>>` [blue]`\#s` [gray]_// number of chars_ + `>>>` [blue]`\#s` [gray]_// number of chars_ +
[gren]`3` + [gren]`4`
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
[green]`3` [green]`3`
`>>>` [blue]`s[1:3]` [gray]_// chars from position 1 to position 3 excluded_ +
[grean]`"bc"`
=== Booleans === Booleans
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values. Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
@@ -334,14 +394,16 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
[CAUTION] [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 evaluated at all. 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 .Example
[source,go] [source,go]
---- ----
2 > (a=1) or (a=8) > 0; a // <1> 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.
==== ====
=== Lists === Lists
@@ -356,13 +418,17 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
.Examples .Examples
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ + `>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
[green]`[1, 2, 3]` + [green]`[1, 2, 3]`
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ + `>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
[green]`["one", "two", "three"]` + [green]`["one", "two", "three"]`
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ + `>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
[green]`["one", 2, false, 4.1]` + [green]`["one", 2, false, 4.1]`
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ + `>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
[green]`["one1", 14]` + [green]`["one1", 14]`
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ + `>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
[green]`[[1, "one"], [2, "two"]]` [green]`[[1, "one"], [2, "two"]]`
@@ -375,37 +441,52 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_ | [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_ | [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_ | [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_ | [blue]`[]` | _Item at index_ | Item at given position | [blue]`[1,2,3][1]` -> _2_
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ + | [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
[blue]`6 in [1,2,3]` -> _false_ [blue]`6 in [1,2,3]` -> _false_
|=== |===
The items of array can be accessed using the dot `.` operator. Array's items can be accessed using the index `[]` operator.
.Item access syntax .Item access syntax
==== ====
_item_ = _list-expr_ "**.**" _integer-expr_ *_item_* = _list-expr_ "**[**" _integer-expr_ "**]**"
====
.Sub-array (or slice of array) syntax
====
*_slice_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
==== ====
.Items of list .Items of list
`>>>` [blue]`[1,2,3].1` + `>>>` [blue]`[1,2,3].1` +
[green]`2` + [green]`2`
`>>>` [blue]`list=[1,2,3]; list.1` + `>>>` [blue]`list=[1,2,3]; list.1` +
[green]`2` + [green]`2`
`>>>` [blue]`["one","two","three"].1` + `>>>` [blue]`["one","two","three"].1` +
[green]`two` + [green]`two`
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` + `>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
[green]`two` + [green]`two`
`>>>` [blue]`list.(-1)` + `>>>` [blue]`list.(-1)` +
[green]`three` + [green]`three`
`>>>` [blue]`list.(10)` + `>>>` [blue]`list.(10)` +
[red]`Eval Error: [1:9] index 10 out of bounds` + [red]`Eval Error: [1:9] index 10 out of bounds`
`>>>` [blue]`#list` + `>>>` [blue]`#list` +
[green]`3` + [green]`3`
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
[green]`c` [green]`c`
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
[green]`["c", "d"]`
=== Dictionaries === Dictionaries
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
@@ -419,14 +500,11 @@ _empty-dict_ = "**{}**" +
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" + _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
==== ====
.Examples
`>>>` [blue]`{1:"one", 2:"two"}` +
[green]`{1: "one", 2: "two"}` +
`>>>` [blue]`{"one":1, "two": 2}` +
[green]`{"one": 1, "two": 2}` +
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
[green]`{"sum": 6, "prod": 6}`
.Item access syntax
====
*_item_* = _dict-expr_ "**[**" _key-expr_ "**]**"
====
.Dict operators .Dict operators
[cols="^2,^2,4,5"] [cols="^2,^2,4,5"]
@@ -434,11 +512,27 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
| Symbol | Operation | Description | Examples | Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_ | [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_ | [blue]`[]` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}["two"]` -> _2_
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ + | [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
[blue]`"six" in {"one":1, "two":2}` -> _false_ [blue]`"six" in {"one":1, "two":2}` -> _false_
|=== |===
.Examples
`>>>` [blue]`{1:"one", 2:"two"}` +
[green]`{1: "one", 2: "two"}`
`>>>` [blue]`{"one":1, "two": 2}` +
[green]`{"one": 1, "two": 2}`
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
[green]`{"sum": 6, "prod": 6}`
`>>>` [blue]`{"one":1, "two":2}["two"]` +
[green]`2`
`>>>` [blue]`d={"one":1, "two":2}; d["six"]=6; d` +
[green]`{"two": 2, "one": 1, "six": 6}`
== Variables == Variables
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_. _Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
@@ -454,17 +548,23 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
.Examples .Examples
`>>>` [blue]`a=1` + `>>>` [blue]`a=1` +
[green]`1` + [green]`1`
`>>>` [blue]`a_b=1+2` + `>>>` [blue]`a_b=1+2` +
[green]`1+2` + [green]`1+2`
`>>>` [blue]`a_b` + `>>>` [blue]`a_b` +
[green]`3` + [green]`3`
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
[green]`31.200000000000003` + `>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the typical approximation error of the float data-type_ +
[green]`31.200000000000003`
`>>>` [blue]`x = 1; y = 2*x` + `>>>` [blue]`x = 1; y = 2*x` +
[green]`2` + [green]`2`
`>>>` [blue]`_a=2` + `>>>` [blue]`_a=2` +
[red]`Parse Error: [1:2] unexpected token "_"` + [red]`Parse Error: [1:2] unexpected token "_"`
`>>>` [blue]`1=2` + `>>>` [blue]`1=2` +
[red]`Parse Error: assign operator ("=") must be preceded by a variable` [red]`Parse Error: assign operator ("=") must be preceded by a variable`
@@ -474,6 +574,11 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
=== [blue]`;` operator === [blue]`;` operator
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result. The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
.Mult-expression syntax
====
*_multi-expression_* = _expression_ {"**;**" _expression_ }
====
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_. An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions. IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
@@ -532,38 +637,47 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
.Examples .Examples
`>>>` [blue]`1 ? {"a"} : {"b"}` + `>>>` [blue]`1 ? {"a"} : {"b"}` +
[green]`b` + [green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
[green]`c' + `>>>` [blue]`10 ? {"a"} : {"b"} {2c} {"c"}` +
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` + [green]`c`
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` + `>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} {"c"}` +
[red]`Parse Error: [1:34] case list in default clause` + [green]`b`
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
[green]`b` + `>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} [10] {"c"}` +
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` + [red]`Parse Error: [1:34] case list in default clause`
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} {2c} {"c"}` +
[green]`b`
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} {2c} {"c"}` +
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"}` + `>>>` [blue]`10 ? {"a"} : {"b"}` +
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression` [red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
=== Variable default value [blue]`??` and [blue]`?=` === Variable default value [blue]`??` and [blue]`?=`
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression. The left operand of these two operators 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 evuated at all. IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
The [blue]`??` do not change the status of the left variable. The [blue]`??` operator do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable. The [blue]`?=` assigns the calculated value of the right expression to the left variable.
.Examples .Examples
`>>>` [blue]`var ?? (1+2)`' `>>>` [blue]`var ?? (1+2)`' +
[green]`3` + [green]`3`
`>>>` [blue]`var` + `>>>` [blue]`var` +
[red]`Eval Error: undefined variable or function "var"` + [red]`Eval Error: undefined variable or function "var"`
`>>>` [blue]`var ?= (1+2)` + `>>>` [blue]`var ?= (1+2)` +
[green]`3` + [green]`3`
`>>>` [blue]`var` +
`>>>` [blue]`var`
[green]`3` [green]`3`
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`. NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
@@ -576,57 +690,87 @@ The table below shows all supported operators by decreasing priorities.
|=== |===
| Priority | Operators | Position | Operation | Operands and results | Priority | Operators | Position | Operation | Operands and results
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_ .2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_ | [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_ .2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_ | [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_ .2+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_ | [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_ .1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_ .1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `!` -> _integer_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_ .3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_ | [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_ | [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_ .2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_ | [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_ .1+|*FRACT*| [blue]`\|` | _Infix_ | _Fraction_ | _integer_ `\|` _integer_ -> _fraction_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_ .5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_ | [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_ | [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_ | [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_ | [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_ .6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_ | [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_ | [blue]`+` | _Infix_ | _List-join_ | _list_ `+` _list_ -> _list_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_ | [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_ | [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_ | [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_ .8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_ | [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_ | [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_ | [blue]`>=` | _Infix_ | _Greater-equal_ | _comparable_ `>=` _comparable_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_ | [blue]`==` | _Infix_ | _Equal_ | _comparable_ `==` _comparable_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_ | [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_ | [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_ | [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_ .1+|*NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_ .2+|*AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_ | [blue]`&&` | _Infix_ | _And_ | _boolean_ `&&` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _list_
| [blue]`<<` | _Infix_ | _Back-insert_ | _list_ `<<` _any_ -> _list_
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|=== |===
^1^ Experimental
== Functions == Functions
Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_. Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures. * _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined. * _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
=== _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.
. Expr's function definition syntax
====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_param-list_] "**)**" "**{**" _multi-expression_ "**}**"
_param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ]
_required-param-list_ = _identifier_ { "**,**" _identifier_ }
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ }
_optional-param_ = _identifier_ "**=**" _any-expr_
====
.Examples
#TODO#
=== _Golang_ function definition
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe.
=== Function calls === Function calls
#TODO: function calls operations# #TODO: function calls operations#
=== Function definitions Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
#TODO: function definitions operations#
== Iterators
#TODO: function calls operations#
== Builtins == Builtins
#TODO: builtins# #TODO: builtins#
@@ -637,4 +781,7 @@ In _Expr_ functions compute values in a local context (scope) that do not make e
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value. [blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
== Plugins
#TODO: plugins#
+528 -212
View File
File diff suppressed because one or more lines are too long
+71 -15
View File
@@ -21,7 +21,7 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil { if functor.info != nil {
s = functor.info.ToString(opt) s = functor.info.ToString(opt)
} else { } else {
s = "func() {}" s = "func(){}"
} }
return s return s
} }
@@ -42,6 +42,10 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info return functor.info
} }
func (functor *baseFunctor) GetDefinitionContext() ExprContext {
return nil
}
// ---- Function Parameters // ---- Function Parameters
type paramFlags uint16 type paramFlags uint16
@@ -94,13 +98,15 @@ func (param *funcParamInfo) DefaultValue() any {
} }
// --- Functions // --- Functions
// funcInfo implements ExprFunc
type funcInfo struct { type funcInfo struct {
name string name string
minArgs int minArgs int
maxArgs int maxArgs int
functor Functor functor Functor
params []ExprFuncParam formalParams []ExprFuncParam
returnType string returnType string
} }
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) { func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
@@ -126,18 +132,18 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
} }
} }
info = &funcInfo{ info = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params, name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
} }
functor.SetFunc(info) functor.SetFunc(info)
return info, nil return info, nil
} }
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) { // func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
return newFuncInfo("unnamed", functor, returnType, params) // return newFuncInfo("unnamed", functor, returnType, params)
} // }
func (info *funcInfo) Params() []ExprFuncParam { func (info *funcInfo) Params() []ExprFuncParam {
return info.params return info.formalParams
} }
func (info *funcInfo) ReturnType() string { func (info *funcInfo) ReturnType() string {
@@ -152,8 +158,8 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
sb.WriteString(info.Name()) sb.WriteString(info.Name())
} }
sb.WriteByte('(') sb.WriteByte('(')
if info.params != nil { if info.formalParams != nil {
for i, p := range info.params { for i, p := range info.formalParams {
if i > 0 { if i > 0 {
sb.WriteString(", ") sb.WriteString(", ")
} }
@@ -180,7 +186,7 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
} else { } else {
sb.WriteString(TypeAny) sb.WriteString(TypeAny)
} }
sb.WriteString(" {}") sb.WriteString("{}")
return sb.String() return sb.String()
} }
@@ -199,3 +205,53 @@ func (info *funcInfo) MaxArgs() int {
func (info *funcInfo) Functor() Functor { func (info *funcInfo) Functor() Functor {
return info.functor return info.functor
} }
func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
ctx = defCtx.Clone()
ctx.SetParent(defCtx)
} else {
ctx = parentCtx.Clone()
ctx.SetParent(parentCtx)
}
return
}
func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) {
passedCount := len(*varActualParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i := passedCount; i < len(info.formalParams); i++ {
p := info.formalParams[i]
if !p.IsDefault() {
break
}
*varActualParams = append(*varActualParams, p.DefaultValue())
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varActualParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varActualParams))
}
if err == nil {
ctx = info.AllocContext(parentCtx)
}
return
}
// ----- Call a function ---
func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) {
if info, exists, _ := GetFuncInfo(parentCtx, name); exists {
var ctx ExprContext
if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil {
functor := info.Functor()
result, err = functor.Invoke(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+3 -3
View File
@@ -26,21 +26,21 @@ func NewListIterator(list *ListType, args []any) (it *ListIterator) {
} }
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1} it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 { if argc >= 1 {
if i, err := ToInt(args[0], "start index"); err == nil { if i, err := ToGoInt(args[0], "start index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.start = i it.start = i
} }
if argc >= 2 { if argc >= 2 {
if i, err := ToInt(args[1], "stop index"); err == nil { if i, err := ToGoInt(args[1], "stop index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.stop = i it.stop = i
} }
if argc >= 3 { if argc >= 3 {
if i, err := ToInt(args[2], "step"); err == nil { if i, err := ToGoInt(args[2], "step"); err == nil {
if i < 0 { if i < 0 {
i = -i i = -i
} }
+2 -35
View File
@@ -6,7 +6,6 @@ package expr
import ( import (
"errors" "errors"
"fmt"
) )
// -------- function call term // -------- function call term
@@ -22,35 +21,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) { func evalFuncCall(ctx ExprContext, self *term) (v any, err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsDefault() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
params := make([]any, len(self.children), len(self.children)+5) params := make([]any, len(self.children), len(self.children)+5)
for i, tree := range self.children { for i, tree := range self.children {
@@ -62,11 +33,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
} }
if err == nil { if err == nil {
if err = checkFunctionCall(ctx, name, &params); err == nil { v, err = CallFunction(ctx, name, params)
if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
} }
return return
} }
+4 -8
View File
@@ -37,9 +37,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
func newAndTerm(tk *Token) (inst *term) { func newAndTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priAnd, priority: priAnd,
@@ -88,7 +86,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
} }
if leftBool, lok := ToBool(leftValue); !lok { if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool) err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", TypeName(leftValue))
return return
} else if !leftBool { } else if !leftBool {
v = false v = false
@@ -106,9 +104,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
func newOrTerm(tk *Token) (inst *term) { func newOrTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priOr, priority: priOr,
@@ -157,7 +153,7 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
} }
if leftBool, lok := ToBool(leftValue); !lok { if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool) err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", TypeName(leftValue))
return return
} else if leftBool { } else if leftBool {
v = true v = true
+31 -4
View File
@@ -23,7 +23,7 @@ func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) { func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int var v int
if v, err = ToInt((*indexList)[0], "index expression"); err == nil { if v, err = ToGoInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue { if v < 0 && v >= -maxValue {
v = maxValue + v v = maxValue + v
} }
@@ -40,11 +40,14 @@ func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex
v, _ := ((*indexList)[0]).(*intPair) v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a startIndex = v.a
endIndex = v.b endIndex = v.b
if endIndex == ConstLastIndex {
endIndex = maxValue
}
if startIndex < 0 && startIndex >= -maxValue { if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex startIndex = maxValue + startIndex
} }
if endIndex < 0 && endIndex >= -maxValue { if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex + 1 endIndex = maxValue + endIndex
} }
if startIndex < 0 || startIndex > maxValue { if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex) err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
@@ -87,13 +90,14 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = string(unboxedValue[index]) v = string(unboxedValue[index])
} }
case *DictType: case *DictType:
var ok bool /* var ok bool
var indexValue any var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil { if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok { if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue) err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
} }
} } */
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -113,6 +117,29 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
} else if IsDict(leftValue) {
d := leftValue.(*DictType)
/* var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}*/
v, err = getDictItem(d, indexTerm, indexList, rightValue)
}
return
}
func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue any) (v any, err error) {
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
} }
return return
} }
+1 -1
View File
@@ -36,7 +36,7 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
} else if it, ok := childValue.(Iterator); ok { } else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) { if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil) count, _ := extIt.CallOperation(countName, nil)
v, _ = ToInt(count, "") v, _ = ToGoInt(count, "")
} else { } else {
v = int64(it.Index() + 1) v = int64(it.Index() + 1)
} }
+2 -2
View File
@@ -29,7 +29,7 @@ func newRangeTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priDot, priority: priRange,
evalFunc: evalRange, evalFunc: evalRange,
} }
} }
@@ -47,7 +47,7 @@ func evalRange(ctx ExprContext, self *term) (v any, err error) {
if leftValue, err = self.children[0].compute(ctx); err != nil { if leftValue, err = self.children[0].compute(ctx); err != nil {
return return
} }
rightValue = int64(-1) rightValue = int64(ConstLastIndex)
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { } else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return return
} }
+13 -1
View File
@@ -325,6 +325,14 @@ func couldBeACollection(t *term) bool {
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
} }
// func areSymbolsOutOfCtx(tk *Token, ctxTerm *term, syms ...Symbol) bool {
// var areOut = false
// if ctxTerm != nil {
// areOut = tk.IsOneOf(syms)
// }
// return areOut
// }
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) { func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil var selectorTerm *term = nil
var currentTerm *term = nil var currentTerm *term = nil
@@ -332,7 +340,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
tree = NewAst() tree = NewAst()
firstToken := true firstToken := true
// lastSym := SymUnknown // lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() { for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() {
if tk.Sym == SymComment { if tk.Sym == SymComment {
continue continue
} }
@@ -438,6 +446,10 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} else { } else {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
} }
if tk.IsSymbol(SymColon) {
// Colon outside a selector term acts like a separator
firstToken = true
}
default: default:
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
} }
+14 -1
View File
@@ -6,7 +6,9 @@ package expr
import ( import (
"fmt" "fmt"
"os"
"plugin" "plugin"
"strings"
) )
var pluginRegister map[string]*plugin.Plugin var pluginRegister map[string]*plugin.Plugin
@@ -25,6 +27,17 @@ func pluginExists(name string) (exists bool) {
return return
} }
func makePluginName(name string) (decorated string) {
var template string
if execName, err := os.Executable(); err != nil || strings.Index(execName, "debug") < 0 {
template = "expr-%s-plugin.so"
} else {
template = "expr-%s-plugin.so.debug"
}
decorated = fmt.Sprintf(template, name)
return
}
func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err error) { func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err error) {
var filePath string var filePath string
var p *plugin.Plugin var p *plugin.Plugin
@@ -33,7 +46,7 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
var importFunc func(ExprContext) var importFunc func(ExprContext)
var ok bool var ok bool
decoratedName := fmt.Sprintf("expr-%s-plugin.so", name) decoratedName := makePluginName(name)
if filePath, err = makeFilepath(decoratedName, dirList); err != nil { if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
return return
+16 -77
View File
@@ -7,7 +7,7 @@ package expr
import ( import (
"fmt" "fmt"
"slices" "slices"
// "strings" // "strings"
) )
type SimpleStore struct { type SimpleStore struct {
@@ -36,87 +36,21 @@ func (ctx *SimpleStore) GetParent() ExprContext {
} }
func (ctx *SimpleStore) Clone() ExprContext { func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{ clone := &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, filterRefName), varStore: CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName), funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
} }
return clone
} }
func (ctx *SimpleStore) Merge(src ExprContext) { // func (ctx *SimpleStore) Merge(src ExprContext) {
for _, name := range src.EnumVars(filterRefName) { // for _, name := range src.EnumVars(filterRefName) {
ctx.varStore[name], _ = src.GetVar(name) // ctx.varStore[name], _ = src.GetVar(name)
} // }
for _, name := range src.EnumFuncs(filterRefName) { // for _, name := range src.EnumFuncs(filterRefName) {
ctx.funcStore[name], _ = src.GetFuncInfo(name) // ctx.funcStore[name], _ = src.GetFuncInfo(name)
} // }
} // }
/*
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(nil) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func varsCtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, indent)
return sb.String()
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}")
}
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
sb.WriteByte('\n')
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
*/
func (ctx *SimpleStore) ToString(opt FmtOpt) string { func (ctx *SimpleStore) ToString(opt FmtOpt) string {
dict := ctx.ToDict() dict := ctx.ToDict()
@@ -165,6 +99,11 @@ func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
return return
} }
func (ctx *SimpleStore) GetLast() (v any) {
v = ctx.varStore["last"]
return
}
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) { func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value) // fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value ctx.varStore[varName] = value
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_bool_test.go
package expr
import (
"errors"
"testing"
)
func TestBool(t *testing.T) {
section := "Bool"
inputs := []inputType{
/* 1 */ {`true`, true, nil},
/* 2 */ {`false`, false, nil},
/* 3 */ {`not false`, true, nil},
/* 4 */ {`not 1`, false, nil},
/* 5 */ {`not "true"`, false, nil},
/* 6 */ {`not "false"`, false, nil},
/* 7 */ {`not ""`, true, nil},
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
/* 9 */ {`true and false`, false, nil},
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
/* 11 */ {`[] and false`, nil, errors.New(`got list as left operand type of 'AND' operator, it must be bool`)},
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, true, nil},
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
func TestBoolNoShortcut(t *testing.T) {
section := "Bool-NoShortcut"
inputs := []inputType{
/* 1 */ {`true`, true, nil},
/* 2 */ {`false`, false, nil},
/* 3 */ {`not false`, true, nil},
/* 4 */ {`not 1`, false, nil},
/* 5 */ {`not "true"`, false, nil},
/* 6 */ {`not "false"`, false, nil},
/* 7 */ {`not ""`, true, nil},
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
/* 9 */ {`true and false`, false, nil},
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
/* 11 */ {`[] and false`, nil, errors.New(`[1:7] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "AND"`)},
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
/* 14 */ {`[] or false`, nil, errors.New(`[1:6] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
ctx := NewSimpleStore()
current := SetCtrl(ctx, ControlBoolShortcut, false)
// runCtxTestSuiteSpec(t, ctx, section, inputs, 1)
runCtxTestSuite(t, ctx, section, inputs)
SetCtrl(ctx, ControlBoolShortcut, current)
}
+1 -1
View File
@@ -63,5 +63,5 @@ func TestFuncBase(t *testing.T) {
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 2) // parserTestSpec(t, section, inputs, 2)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-fmt.go
package expr
import (
// "errors"
"bytes"
"fmt"
"testing"
)
func TestFuncFmt(t *testing.T) {
section := "Builtin-Fmt"
inputs := []inputType{
/* 1 */ {`builtin "fmt"; print("ciao")`, int64(4), nil},
/* 2 */ {`builtin "fmt"; println(" ciao")`, int64(6), nil},
}
//t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}
func TestFmt(t *testing.T) {
section := "Builtin-Fmt"
text := "ciao mondo"
inputs := []inputType{
/* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil},
}
// t.Setenv("EXPR_PATH", ".")
var b bytes.Buffer
ctx := NewSimpleStore()
currentStdout := SetCtrl(ctx, ControlStdout, &b)
runCtxTestSuite(t, ctx, section, inputs)
SetCtrl(ctx, ControlStdout, currentStdout)
if b.String() != text+"\n" {
t.Errorf("println(): Got: %q, Want: %q", b.String(), text+"\n")
}
}
+1 -1
View File
@@ -20,5 +20,5 @@ func TestFuncImport(t *testing.T) {
t.Setenv("EXPR_PATH", "test-resources") t.Setenv("EXPR_PATH", "test-resources")
// parserTestSpec(t, section, inputs, 69) // parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+1 -1
View File
@@ -25,5 +25,5 @@ func TestFuncMathArith(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69) // parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+12 -5
View File
@@ -16,14 +16,21 @@ func TestFuncOs(t *testing.T) {
/* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil}, /* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)}, /* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil}, /* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWrite(handle, "bye-bye"); fileClose(handle)`, true, nil}, /* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileRead(handle, "-"); fileClose(handle);word`, "bye", nil}, /* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileRead(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)}, /* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, errors.New(`fileReadText(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; fileWrite(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)}, /* 8 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
/* 9 */ {`builtin "os.file"; handle=fileOpen()`, nil, errors.New(`fileOpen(): too few params -- expected 1, got 0`)},
/* 10 */ {`builtin "os.file"; handle=fileOpen(123)`, nil, errors.New(`fileOpen(): missing or invalid file path`)},
/* 11 */ {`builtin "os.file"; handle=fileCreate(123)`, nil, errors.New(`fileCreate(): missing or invalid file path`)},
/* 12 */ {`builtin "os.file"; handle=fileAppend(123)`, nil, errors.New(`fileAppend(): missing or invalid file path`)},
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, errors.New(`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, errors.New(`fileReadTextAll(): invalid file handle 123 [int64]`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69) // parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+2 -2
View File
@@ -64,6 +64,6 @@ func TestFuncString(t *testing.T) {
//t.Setenv("EXPR_PATH", ".") //t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 19) // runTestSuiteSpec(t, section, inputs, 19)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_common_test.go
package expr
import (
"reflect"
"strings"
"testing"
)
type inputType struct {
source string
wantResult any
wantErr error
}
func runCtxTestSuiteSpec(t *testing.T, ctx ExprContext, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, ctx, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func runTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
runCtxTestSuiteSpec(t, nil, section, inputs, spec...)
}
func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, ctx, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func runTestSuite(t *testing.T, section string, inputs []inputType) {
runCtxTestSuite(t, nil, section, inputs)
/*
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, nil, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
*/
}
func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStore()
}
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
good = false
}
}
return
}
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
} else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
}
+1
View File
@@ -31,6 +31,7 @@ func TestDictParser(t *testing.T) {
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil}, /* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil}, /* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)}, /* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
} }
succeeded := 0 succeeded := 0
+2 -2
View File
@@ -14,7 +14,7 @@ func TestExpr(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil}, /* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil}, /* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileRead(f); fileClose(f); line`, "uno", nil}, /* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil}, /* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil}, /* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {` /* 6 */ {`
@@ -33,5 +33,5 @@ func TestExpr(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3) // parserTestSpec(t, section, inputs, 3)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+1 -1
View File
@@ -34,7 +34,7 @@ func TestFractionsParser(t *testing.T) {
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)}, /* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)}, /* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
} }
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
func TestFractionToStringSimple(t *testing.T) { func TestFractionToStringSimple(t *testing.T) {
+5 -3
View File
@@ -29,13 +29,15 @@ func TestFuncs(t *testing.T) {
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil}, /* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)}, /* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)}, /* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
/* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil},
/* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil},
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)}, // /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17) // runTestSuiteSpec(t, section, inputs, 17)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
func dummy(ctx ExprContext, name string, args []any) (result any, err error) { func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
@@ -44,7 +46,7 @@ func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
func TestFunctionToStringSimple(t *testing.T) { func TestFunctionToStringSimple(t *testing.T) {
source := NewGolangFunctor(dummy) source := NewGolangFunctor(dummy)
want := "func() {}" want := "func(){}"
got := source.ToString(0) got := source.ToString(0)
if got != want { if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want) t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
+1 -1
View File
@@ -23,5 +23,5 @@ func TestCollections(t *testing.T) {
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5) // parserTestSpec(t, section, inputs, 5)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+4 -4
View File
@@ -14,13 +14,13 @@ func TestIteratorParser(t *testing.T) {
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil}, /* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil}, /* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil}, /* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil}, /* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil}, /* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil}, /* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil}, /* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
} }
// inputs1 := []inputType{ // inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil}, // /* 1 */ {`0?{}`, nil, nil},
// } // }
parserTest(t, "Iterator", inputs) runTestSuite(t, "Iterator", inputs)
} }
+5 -3
View File
@@ -49,12 +49,14 @@ func TestListParser(t *testing.T) {
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, errors.New(`index 5 out of bounds (0, 1)`)}, /* 35 */ {`L=[1,2]; L[5]=9; L`, nil, errors.New(`index 5 out of bounds (0, 1)`)},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, errors.New(`[1:12] index/key specification expected, got [] [list]`)}, /* 36 */ {`L=[1,2]; L[]=9; L`, nil, errors.New(`[1:12] index/key specification expected, got [] [list]`)},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is nil`)}, /* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is nil`)},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil}, /* 38 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil}, /* 39 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
/* 41 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17) // parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+1 -82
View File
@@ -6,17 +6,9 @@ package expr
import ( import (
"errors" "errors"
"reflect"
"strings"
"testing" "testing"
) )
type inputType struct {
source string
wantResult any
wantErr error
}
func TestGeneralParser(t *testing.T) { func TestGeneralParser(t *testing.T) {
section := "Parser" section := "Parser"
@@ -148,78 +140,5 @@ func TestGeneralParser(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 102) // parserTestSpec(t, section, inputs, 102)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
}
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleStore()
parser := NewParser()
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
good = false
}
}
return
}
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
} else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
} }
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_plugin_test.go
package expr
import (
"testing"
)
func _TestImportPlugin(t *testing.T) {
if err := importPlugin([]string{"test-resources"}, "json"); err != nil {
t.Errorf("importPlugin() failed: %v", err)
}
}
func TestPluginExists(t *testing.T) {
name := "json"
exists := pluginExists(name)
t.Logf("pluginExists(%v): %v", name, exists)
}
func TestMakePluginName(t *testing.T) {
name := "json"
want := "expr-" + name + "-plugin.so"
if got := makePluginName(name); got != want {
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", name, got, want)
}
}
+1 -1
View File
@@ -48,5 +48,5 @@ func TestRelational(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 31) // parserTestSpec(t, section, inputs, 31)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+1 -1
View File
@@ -17,5 +17,5 @@ func TestStringsParser(t *testing.T) {
/* 5 */ {`"abc"[1]`, `b`, nil}, /* 5 */ {`"abc"[1]`, `b`, nil},
/* 6 */ {`#"abc"`, int64(3), nil}, /* 6 */ {`#"abc"`, int64(3), nil},
} }
parserTest(t, "String", inputs) runTestSuite(t, "String", inputs)
} }
+2 -2
View File
@@ -16,6 +16,6 @@ func TestSomething(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 1) // runTestSuiteSpec(t, section, inputs, 1)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+12 -2
View File
@@ -6,6 +6,7 @@ package expr
import ( import (
"errors" "errors"
"reflect"
"testing" "testing"
) )
@@ -97,7 +98,7 @@ func TestToIntOk(t *testing.T) {
wantValue := int(64) wantValue := int(64)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := ToInt(source, "test") gotValue, gotErr := ToGoInt(source, "test")
if gotErr != nil || gotValue != wantValue { if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v", t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
@@ -110,7 +111,7 @@ func TestToIntErr(t *testing.T) {
wantValue := 0 wantValue := 0
wantErr := errors.New(`test expected integer, got uint64 (64)`) wantErr := errors.New(`test expected integer, got uint64 (64)`)
gotValue, gotErr := ToInt(source, "test") gotValue, gotErr := ToGoInt(source, "test")
if gotErr.Error() != wantErr.Error() || gotValue != wantValue { if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v", t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
@@ -154,3 +155,12 @@ func TestAnyInteger(t *testing.T) {
} }
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed) t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
} }
func TestCopyMap(t *testing.T) {
source := map[string]int{"one": 1, "two": 2, "three": 3}
dest := make(map[string]int)
result := CopyMap(dest, source)
if !reflect.DeepEqual(result, source) {
t.Errorf("utils.CopyMap() failed")
}
}
Binary file not shown.
Binary file not shown.
+10 -11
View File
@@ -1,23 +1,22 @@
builtin ["os.file", "base"]; builtin ["os.file", "base"];
readInt=func(fh){ readInt=func(fh){
line=fileRead(fh); line=fileReadText(fh);
line ? [nil] {nil} :: {int(line)} line ? [nil] {nil} :: {int(line)}
}; };
ds={ ds={
"init":func(filename){ "init":func(filename){
fh=fileOpen(filename); fh=fileOpen(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current }; fh ? [nil] {nil} :: { @current=readInt(fh) };
fh fh
}, },
"current":func(){ "current":func(){
prev current
}, },
"next":func(fh){ "next":func(fh){
current ? @current=readInt(fh);
[nil] {current} current
:: {@prev=current; @current=readInt(fh) but current}
}, },
"clean":func(fh){ "clean":func(fh){
fileClose(fh) fileClose(fh)
@@ -25,9 +24,9 @@ ds={
} }
//;f=$(ds, "int.list") //;f=$(ds, "int.list")
/*
;f++ //;f++
;f++ //;f++
;f++ //;f++
*/ //*/
//;add(f) //;add(f)
+5 -1
View File
@@ -60,7 +60,11 @@ func (tk *Token) IsError() bool {
} }
func (tk *Token) IsTerm(termSymbols []Symbol) bool { func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0) return tk.IsEos() || tk.IsError() || tk.IsOneOf(termSymbols)
}
func (tk *Token) IsOneOf(termSymbols []Symbol) bool {
return termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0
} }
func (tk *Token) IsSymbol(sym Symbol) bool { func (tk *Token) IsSymbol(sym Symbol) bool {
+24 -22
View File
@@ -135,23 +135,25 @@ func anyInteger(v any) (i int64, ok bool) {
} }
func fromGenericAny(v any) (exprAny any, ok bool) { func fromGenericAny(v any) (exprAny any, ok bool) {
if exprAny, ok = v.(bool); ok { if v != nil {
return if exprAny, ok = v.(bool); ok {
} return
if exprAny, ok = v.(string); ok { }
return if exprAny, ok = v.(string); ok {
} return
if exprAny, ok = anyInteger(v); ok { }
return if exprAny, ok = anyInteger(v); ok {
} return
if exprAny, ok = anyFloat(v); ok { }
return if exprAny, ok = anyFloat(v); ok {
} return
if exprAny, ok = v.(*DictType); ok { }
return if exprAny, ok = v.(*DictType); ok {
} return
if exprAny, ok = v.(*ListType); ok { }
return if exprAny, ok = v.(*ListType); ok {
return
}
} }
return return
} }
@@ -176,10 +178,10 @@ func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
return dest return dest
} }
func CloneMap[K comparable, V any](source map[K]V) map[K]V { // func CloneMap[K comparable, V any](source map[K]V) map[K]V {
dest := make(map[K]V, len(source)) // dest := make(map[K]V, len(source))
return CopyMap(dest, source) // return CopyMap(dest, source)
} // }
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V { func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
// fmt.Printf("--- Clone with filter %p\n", filter) // fmt.Printf("--- Clone with filter %p\n", filter)
@@ -201,7 +203,7 @@ func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (a
return CopyFilteredMap(dest, source, filter) return CopyFilteredMap(dest, source, filter)
} }
func ToInt(value any, description string) (i int, err error) { func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok { if valueInt64, ok := value.(int64); ok {
i = int(valueInt64) i = int(valueInt64)
} else if valueInt, ok := value.(int); ok { } else if valueInt, ok := value.(int); ok {