Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 531cb1c249 | |||
| d1b468f35b | |||
| ff9cf80c66 | |||
| d63b18fd76 | |||
| 019470faf1 | |||
| 302430d57d | |||
| 62ef0d699d | |||
| 866de759dd | |||
| b1d6b6de44 | |||
| 7e357eea62 | |||
| d6b4c79736 | |||
| d066344af8 | |||
| f41dba069e | |||
| 703ecf6829 | |||
| ba479a1b99 | |||
| 24e6a293b0 | |||
| 28f464c4dc | |||
| 9fb611aa20 | |||
| 56d6d06d15 | |||
| d9f7e5b1ad | |||
| 0f54e01ef3 | |||
| 63f5db00b3 | |||
| 9745a5d909 | |||
| 0bb4c96481 | |||
| 1757298eb4 | |||
| 54041552d4 | |||
| 5302907dcf | |||
| eb4b17f078 |
@@ -42,3 +42,17 @@ 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, ¶ms); err == nil {
|
||||||
|
// if v, err = ctx.Call(funcName, params); err == nil {
|
||||||
|
// exportObjects(parentCtx, ctx)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|||||||
+6
-6
@@ -67,7 +67,7 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
case string:
|
case string:
|
||||||
result = len(v) > 0
|
result = len(v) > 0
|
||||||
default:
|
default:
|
||||||
err = errCantConvert(name, v, "bool")
|
err = ErrCantConvert(name, v, "bool")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|||||||
result = int64(i)
|
result = int64(i)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = errCantConvert(name, v, "int")
|
err = ErrCantConvert(name, v, "int")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|||||||
case *FractionType:
|
case *FractionType:
|
||||||
result = v.toFloat()
|
result = v.toFloat()
|
||||||
default:
|
default:
|
||||||
err = errCantConvert(name, v, "float")
|
err = ErrCantConvert(name, v, "float")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -127,9 +127,9 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
var ok bool
|
var ok bool
|
||||||
if den, ok = args[1].(int64); !ok {
|
if den, ok = args[1].(int64); !ok {
|
||||||
err = errExpectedGot(name, "integer", args[1])
|
err = ErrExpectedGot(name, "integer", args[1])
|
||||||
} else if den == 0 {
|
} else if den == 0 {
|
||||||
err = errFuncDivisionByZero(name)
|
err = ErrFuncDivisionByZero(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -148,7 +148,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
case *FractionType:
|
case *FractionType:
|
||||||
result = v
|
result = v
|
||||||
default:
|
default:
|
||||||
err = errCantConvert(name, v, "float")
|
err = ErrCantConvert(name, v, "float")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-19
@@ -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("openFile")
|
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,46 +165,82 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportOsFuncs(ctx ExprContext) {
|
func ImportOsFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("openFile", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
|
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
|
||||||
NewFuncParam(ParamFilepath),
|
NewFuncParam(ParamFilepath),
|
||||||
})
|
})
|
||||||
ctx.RegisterFunc("appendFile", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
|
|
||||||
|
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
|
||||||
NewFuncParam(ParamFilepath),
|
NewFuncParam(ParamFilepath),
|
||||||
})
|
})
|
||||||
ctx.RegisterFunc("createFile", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
|
|
||||||
|
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
|
||||||
NewFuncParam(ParamFilepath),
|
NewFuncParam(ParamFilepath),
|
||||||
})
|
})
|
||||||
ctx.RegisterFunc("writeFile", 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("readFile", 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("closeFile", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
|
|
||||||
|
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
|
||||||
NewFuncParam(TypeHandle),
|
NewFuncParam(TypeHandle),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-16
@@ -21,7 +21,7 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
|
|||||||
if s, ok := v.(string); ok {
|
if s, ok := v.(string); ok {
|
||||||
sb.WriteString(s)
|
sb.WriteString(s)
|
||||||
} else {
|
} else {
|
||||||
err = errExpectedGot(funcName, TypeString, v)
|
err = ErrExpectedGot(funcName, TypeString, v)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,13 +45,13 @@ func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
|||||||
} else if it, ok := args[1].(Iterator); ok {
|
} else if it, ok := args[1].(Iterator); ok {
|
||||||
result, err = doJoinStr(name, sep, it)
|
result, err = doJoinStr(name, sep, it)
|
||||||
} else {
|
} else {
|
||||||
err = errInvalidParameterValue(name, ParamParts, args[1])
|
err = ErrInvalidParameterValue(name, ParamParts, args[1])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
|
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = errWrongParamType(name, ParamSeparator, TypeString, args[0])
|
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0])
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,14 +63,14 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
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 = ToInt(args[1], name+"()"); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if count, err = toInt(args[2], name+"()"); err != nil {
|
if count, err = ToInt(args[2], name+"()"); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return nil, errWrongParamType(name, ParamSource, TypeString, args[0])
|
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
}
|
}
|
||||||
result = strings.TrimSpace(source)
|
result = strings.TrimSpace(source)
|
||||||
return
|
return
|
||||||
@@ -104,7 +104,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
|
|||||||
result = false
|
result = false
|
||||||
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return result, errWrongParamType(name, ParamSource, TypeString, args[0])
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
}
|
}
|
||||||
for i, targetSpec := range args[1:] {
|
for i, targetSpec := range args[1:] {
|
||||||
if target, ok := targetSpec.(string); ok {
|
if target, ok := targetSpec.(string); ok {
|
||||||
@@ -127,7 +127,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
|
|||||||
result = false
|
result = false
|
||||||
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return result, errWrongParamType(name, ParamSource, TypeString, args[0])
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
}
|
}
|
||||||
for i, targetSpec := range args[1:] {
|
for i, targetSpec := range args[1:] {
|
||||||
if target, ok := targetSpec.(string); ok {
|
if target, ok := targetSpec.(string); ok {
|
||||||
@@ -150,7 +150,7 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return result, errWrongParamType(name, ParamSource, TypeString, args[0])
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if sep, ok = args[1].(string); !ok {
|
if sep, ok = args[1].(string); !ok {
|
||||||
@@ -182,34 +182,34 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
|
|
||||||
// Import above functions in the context
|
// Import above functions in the context
|
||||||
func ImportStringFuncs(ctx ExprContext) {
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("joinStr", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{
|
ctx.RegisterFunc("strJoin", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{
|
||||||
NewFuncParam(ParamSeparator),
|
NewFuncParam(ParamSeparator),
|
||||||
NewFuncParamFlag(ParamItem, PfRepeat),
|
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.RegisterFunc("subStr", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{
|
ctx.RegisterFunc("strSub", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{
|
||||||
NewFuncParam(ParamSource),
|
NewFuncParam(ParamSource),
|
||||||
NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)),
|
NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)),
|
||||||
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
|
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.RegisterFunc("splitStr", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{
|
ctx.RegisterFunc("strSplit", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{
|
||||||
NewFuncParam(ParamSource),
|
NewFuncParam(ParamSource),
|
||||||
NewFuncParamFlagDef(ParamSeparator, PfDefault, ""),
|
NewFuncParamFlagDef(ParamSeparator, PfDefault, ""),
|
||||||
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
|
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.RegisterFunc("trimStr", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{
|
ctx.RegisterFunc("strTrim", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{
|
||||||
NewFuncParam(ParamSource),
|
NewFuncParam(ParamSource),
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.RegisterFunc("startsWithStr", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
|
ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
|
||||||
NewFuncParam(ParamSource),
|
NewFuncParam(ParamSource),
|
||||||
NewFuncParam(ParamPrefix),
|
NewFuncParam(ParamPrefix),
|
||||||
NewFuncParamFlag("other "+ParamPrefix, PfRepeat),
|
NewFuncParamFlag("other "+ParamPrefix, PfRepeat),
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.RegisterFunc("endsWithStr", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
|
ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
|
||||||
NewFuncParam(ParamSource),
|
NewFuncParam(ParamSource),
|
||||||
NewFuncParam(ParamSuffix),
|
NewFuncParam(ParamSuffix),
|
||||||
NewFuncParamFlag("other "+ParamSuffix, PfRepeat),
|
NewFuncParamFlag("other "+ParamSuffix, PfRepeat),
|
||||||
|
|||||||
+14
-14
@@ -8,7 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
|
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
|
||||||
if maxArgs < 0 {
|
if maxArgs < 0 {
|
||||||
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
|
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
|
||||||
} else {
|
} else {
|
||||||
@@ -17,39 +17,39 @@ func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func errTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
|
func ErrTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
|
||||||
err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
|
err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- General errors
|
// --- General errors
|
||||||
|
|
||||||
func errCantConvert(funcName string, value any, kind string) error {
|
func ErrCantConvert(funcName string, value any, kind string) error {
|
||||||
return fmt.Errorf("%s(): can't convert %s to %s", funcName, typeName(value), kind)
|
return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errExpectedGot(funcName string, kind string, value any) error {
|
func ErrExpectedGot(funcName string, kind string, value any) error {
|
||||||
return fmt.Errorf("%s() expected %s, got %s (%v)", funcName, kind, typeName(value), value)
|
return fmt.Errorf("%s(): expected %s, got %s (%v)", funcName, kind, TypeName(value), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errFuncDivisionByZero(funcName string) error {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||||
return fmt.Errorf("%s() the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, typeName(paramValue), paramValue)
|
return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -41,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exportObjectsToParent(sourceCtx ExprContext) {
|
||||||
|
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
|
||||||
|
exportObjects(parentCtx, sourceCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type ExprContext interface {
|
|||||||
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)
|
||||||
|
|||||||
+50
-29
@@ -12,6 +12,12 @@ import (
|
|||||||
|
|
||||||
type DictType map[any]any
|
type DictType map[any]any
|
||||||
|
|
||||||
|
func MakeDict() (dict *DictType) {
|
||||||
|
d := make(DictType)
|
||||||
|
dict = &d
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func newDict(dictAny map[any]*term) (dict *DictType) {
|
func newDict(dictAny map[any]*term) (dict *DictType) {
|
||||||
var d DictType
|
var d DictType
|
||||||
if dictAny != nil {
|
if dictAny != nil {
|
||||||
@@ -26,42 +32,52 @@ func newDict(dictAny map[any]*term) (dict *DictType) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
|
func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
|
||||||
sb.WriteString(strings.Repeat("\t", indent))
|
indent := GetFormatIndent(opt)
|
||||||
sb.WriteString("{\n")
|
flags := GetFormatFlags(opt)
|
||||||
|
//sb.WriteString(strings.Repeat(" ", indent))
|
||||||
|
sb.WriteByte('{')
|
||||||
|
|
||||||
first := true
|
if len(*dict) > 0 {
|
||||||
for name, value := range *dict {
|
innerOpt := MakeFormatOptions(flags, indent+1)
|
||||||
if first {
|
nest := strings.Repeat(" ", indent+1)
|
||||||
first = false
|
sb.WriteByte('\n')
|
||||||
} else {
|
|
||||||
sb.WriteByte(',')
|
|
||||||
sb.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
first := true
|
||||||
if key, ok := name.(string); ok {
|
for name, value := range *dict {
|
||||||
sb.WriteString(string('"') + key + string('"'))
|
if first {
|
||||||
} else {
|
first = false
|
||||||
sb.WriteString(fmt.Sprintf("%v", name))
|
} else {
|
||||||
}
|
sb.WriteByte(',')
|
||||||
sb.WriteString(": ")
|
sb.WriteByte('\n')
|
||||||
if f, ok := value.(Formatter); ok {
|
}
|
||||||
sb.WriteString(f.ToString(MultiLine))
|
|
||||||
} else if _, ok = value.(Functor); ok {
|
sb.WriteString(nest)
|
||||||
sb.WriteString("func(){}")
|
if key, ok := name.(string); ok {
|
||||||
} else {
|
sb.WriteString(string('"') + key + string('"'))
|
||||||
sb.WriteString(fmt.Sprintf("%v", value))
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", name))
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(f.ToString(innerOpt))
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
sb.WriteString("func(){}")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(strings.Repeat(" ", indent))
|
||||||
}
|
}
|
||||||
sb.WriteString(strings.Repeat("\t", indent))
|
sb.WriteString("}")
|
||||||
sb.WriteString("\n}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dict *DictType) ToString(opt FmtOpt) string {
|
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
if opt&MultiLine != 0 {
|
flags := GetFormatFlags(opt)
|
||||||
dict.toMultiLine(&sb, 0)
|
if flags&MultiLine != 0 {
|
||||||
|
dict.toMultiLine(&sb, opt)
|
||||||
} else {
|
} else {
|
||||||
sb.WriteByte('{')
|
sb.WriteByte('{')
|
||||||
first := true
|
first := true
|
||||||
@@ -124,7 +140,12 @@ func (dict *DictType) merge(second *DictType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dict *DictType) setItem(key any, value any) (err error) {
|
func (dict *DictType) setItem(key any, value any) (err error) {
|
||||||
(*dict)[key]=value
|
(*dict)[key] = value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
type DictFormat interface {
|
||||||
|
ToDict() *DictType
|
||||||
|
}
|
||||||
|
|||||||
+60
-26
@@ -22,18 +22,45 @@ Expressions calculator
|
|||||||
|
|
||||||
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/17, 16:31 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.
|
`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 +74,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 +87,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,9 +97,10 @@ 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
|
||||||
>>>
|
>>>
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -83,8 +111,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 +129,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:
|
||||||
@@ -272,25 +300,31 @@ 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)_ +
|
`>>>` [blue]`s[1]` [gray]_// char at position 1 (starting from 0)_ +
|
||||||
[green]`b` +
|
[green]`"b"` +
|
||||||
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
|
`>>>` [blue]`s.[-1]` [gray]_// char at position -1, the rightmost one_ +
|
||||||
[green]`c` +
|
[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.
|
||||||
|
|||||||
+99
-33
@@ -535,7 +535,13 @@ pre.rouge .ss {
|
|||||||
<ul class="sectlevel1">
|
<ul class="sectlevel1">
|
||||||
<li><a href="#_expr">1. Expr</a>
|
<li><a href="#_expr">1. Expr</a>
|
||||||
<ul class="sectlevel2">
|
<ul class="sectlevel2">
|
||||||
<li><a href="#_concepts_and_terminology">1.1. Concepts and terminology</a></li>
|
<li><a href="#_concepts_and_terminology">1.1. Concepts and terminology</a>
|
||||||
|
<ul class="sectlevel3">
|
||||||
|
<li><a href="#_variables">1.1.1. Variables</a></li>
|
||||||
|
<li><a href="#_multi_expression">1.1.2. Multi-expression</a></li>
|
||||||
|
<li><a href="#_calculation_context">1.1.3. Calculation context</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li><a href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></li>
|
<li><a href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@@ -554,7 +560,7 @@ pre.rouge .ss {
|
|||||||
<li><a href="#_dictionaries">2.5. Dictionaries</a></li>
|
<li><a href="#_dictionaries">2.5. Dictionaries</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#_variables">3. Variables</a></li>
|
<li><a href="#_variables_2">3. Variables</a></li>
|
||||||
<li><a href="#_other_operations">4. Other operations</a>
|
<li><a href="#_other_operations">4. Other operations</a>
|
||||||
<ul class="sectlevel2">
|
<ul class="sectlevel2">
|
||||||
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
|
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
|
||||||
@@ -585,7 +591,7 @@ pre.rouge .ss {
|
|||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<!-- toc disabled -->
|
<!-- toc disabled -->
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><mark>TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)</mark></p>
|
<p><mark>TODO: Work in progress (last update on 2024/06/17, 16:31 a.m.)</mark></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -598,18 +604,67 @@ pre.rouge .ss {
|
|||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_concepts_and_terminology"><a class="anchor" href="#_concepts_and_terminology"></a><a class="link" href="#_concepts_and_terminology">1.1. Concepts and terminology</a></h3>
|
<h3 id="_concepts_and_terminology"><a class="anchor" href="#_concepts_and_terminology"></a><a class="link" href="#_concepts_and_terminology">1.1. Concepts and terminology</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><mark>TODO</mark></p>
|
<p>Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. <em>Expr</em> package provides these macro functions:</p>
|
||||||
|
</div>
|
||||||
|
<div class="ulist">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p><strong><em>Scanner</em></strong> — Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong><em>Parser</em></strong> — Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the <em>Expr</em> 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.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong><em>Calculator</em></strong>. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="imageblock">
|
<div class="imageblock">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img src="expression-diagram.png" alt="expression diagram">
|
<img src="expression-diagram.png" alt="expression diagram">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sect3">
|
||||||
|
<h4 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">1.1.1. Variables</a></h4>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><em>Expr</em> 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.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sect3">
|
||||||
|
<h4 id="_multi_expression"><a class="anchor" href="#_multi_expression"></a><a class="link" href="#_multi_expression">1.1.2. Multi-expression</a></h4>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>An input text valid for <em>Expr</em> can contain more than an expression. Expressions are separated by <code class="blue">;</code> (semicolon). When an input contains two or more expressions it is called <em>multi-expression</em>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><em>Expr</em> 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.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The result of each expression of a multi-expression is stored in an automatic variable named <em>last</em>. 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.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sect3">
|
||||||
|
<h4 id="_calculation_context"><a class="anchor" href="#_calculation_context"></a><a class="link" href="#_calculation_context">1.1.3. Calculation context</a></h4>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called <em>context</em>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>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.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>Function contexts are created by cloning the calling context. More details on this topic are given later in this document.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><em>Expr</em> creates and keeps a inner <em>global context</em> 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 <em>main context</em>. 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 <em>function context</em>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>Imported functions are registerd in the <em>global context</em>. When an expression first calls an imported function, that function is linked to the current context; this can be the <em>main context</em> or a <em>function context</em>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_dev_expr_test_tool"><a class="anchor" href="#_dev_expr_test_tool"></a><a class="link" href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></h3>
|
<h3 id="_dev_expr_test_tool"><a class="anchor" href="#_dev_expr_test_tool"></a><a class="link" href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><code>dev-expr</code> 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, <code>dev-expr</code> provides an important aid for quickly testing of new features during their development.</p>
|
<p><code>dev-expr</code> 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, <code>dev-expr</code> provided an important aid for quickly testing of new features during their development.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><code>dev-expr</code> can work as a <em>REPL</em>, <em><strong>R</strong>ead-<strong>E</strong>xecute-<strong>P</strong>rint-<strong>L</strong>oop</em>, or it can process expression acquired from files or standard input.</p>
|
<p><code>dev-expr</code> can work as a <em>REPL</em>, <em><strong>R</strong>ead-<strong>E</strong>xecute-<strong>P</strong>rint-<strong>L</strong>oop</em>, or it can process expression acquired from files or standard input.</p>
|
||||||
@@ -626,11 +681,10 @@ pre.rouge .ss {
|
|||||||
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Type 'exit' or Ctrl+D to quit the program.</span>
|
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Type 'exit' or Ctrl+D to quit the program.</span>
|
||||||
|
|
||||||
<span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
|
<span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
|
||||||
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
|
dev-expr <span class="nt">--</span> Expressions calculator v1.10.0<span class="o">(</span>build 14<span class="o">)</span>,2024/06/17 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
|
||||||
Based on the Expr package v0.10.0
|
Based on the Expr package v0.19.0
|
||||||
Type <span class="nb">help </span>to get the list of available commands
|
Type <span class="nb">help </span>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
|
||||||
|
|
||||||
<span class="o">>>></span> <span class="nb">help</span>
|
<span class="o">>>></span> <span class="nb">help</span>
|
||||||
<span class="nt">---</span> REPL commands:
|
<span class="nt">---</span> REPL commands:
|
||||||
<span class="nb">source</span> <span class="nt">--</span> Load a file as input
|
<span class="nb">source</span> <span class="nt">--</span> Load a file as input
|
||||||
@@ -640,6 +694,7 @@ pre.rouge .ss {
|
|||||||
<span class="nb">help</span> <span class="nt">--</span> Show <span class="nb">command </span>list
|
<span class="nb">help</span> <span class="nt">--</span> Show <span class="nb">command </span>list
|
||||||
ml <span class="nt">--</span> Enable/Disable multi-line output
|
ml <span class="nt">--</span> Enable/Disable multi-line output
|
||||||
mods <span class="nt">--</span> List <span class="nb">builtin </span>modules
|
mods <span class="nt">--</span> List <span class="nb">builtin </span>modules
|
||||||
|
output <span class="nt">--</span> Enable/Disable printing expression results. Options <span class="s1">'on'</span>, <span class="s1">'off'</span>, <span class="s1">'status'</span>
|
||||||
|
|
||||||
<span class="nt">---</span> Command line options:
|
<span class="nt">---</span> Command line options:
|
||||||
<span class="nt">-b</span> <<span class="nb">builtin</span><span class="o">></span> Import <span class="nb">builtin </span>modules.
|
<span class="nt">-b</span> <<span class="nb">builtin</span><span class="o">></span> Import <span class="nb">builtin </span>modules.
|
||||||
@@ -649,9 +704,10 @@ pre.rouge .ss {
|
|||||||
<span class="nt">-i</span> Force REPL operation when all <span class="nt">-e</span> occurences have been processed
|
<span class="nt">-i</span> Force REPL operation when all <span class="nt">-e</span> occurences have been processed
|
||||||
<span class="nt">-h</span>, <span class="nt">--help</span>, <span class="nb">help </span>Show this <span class="nb">help </span>menu
|
<span class="nt">-h</span>, <span class="nt">--help</span>, <span class="nb">help </span>Show this <span class="nb">help </span>menu
|
||||||
<span class="nt">-m</span>, <span class="nt">--modules</span> List all <span class="nb">builtin </span>modules
|
<span class="nt">-m</span>, <span class="nt">--modules</span> List all <span class="nb">builtin </span>modules
|
||||||
|
<span class="nt">--noout</span> Disable printing of expression results
|
||||||
<span class="nt">-p</span> Print prefix form
|
<span class="nt">-p</span> Print prefix form
|
||||||
<span class="nt">-t</span> Print tree form <i class="conum" data-value="2"></i><b>(2)</b>
|
<span class="nt">-t</span> Print tree form <i class="conum" data-value="2"></i><b>(2)</b>
|
||||||
|
<span class="nt">-v</span>, <span class="nt">--version</span> Show program version
|
||||||
<span class="o">>>></span></code></pre>
|
<span class="o">>>></span></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -671,8 +727,8 @@ pre.rouge .ss {
|
|||||||
<div class="title">REPL examples</div>
|
<div class="title">REPL examples</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
|
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
|
||||||
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
|
dev-expr <span class="nt">--</span> Expressions calculator v1.10.0<span class="o">(</span>build 14<span class="o">)</span>,2024/06/17 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
|
||||||
Based on the Expr package v0.10.0
|
Based on the Expr package v0.19.0
|
||||||
Type <span class="nb">help </span>to get the list of available commands
|
Type <span class="nb">help </span>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
|
||||||
|
|
||||||
@@ -689,9 +745,9 @@ pre.rouge .ss {
|
|||||||
7
|
7
|
||||||
-
|
-
|
||||||
6
|
6
|
||||||
<span class="o">>>></span> 1+2 but 5|2+0.5 <i class="conum" data-value="4"></i><b>(4)</b>
|
<span class="o">>>></span> 4+2 but 5|2+0.5 <i class="conum" data-value="4"></i><b>(4)</b>
|
||||||
3
|
3
|
||||||
<span class="o">>>></span> 1+2<span class="p">;</span> 5|2+0.5 <i class="conum" data-value="5"></i><b>(5)</b>
|
<span class="o">>>></span> 4+2<span class="p">;</span> 5|2+0.5 <i class="conum" data-value="5"></i><b>(5)</b>
|
||||||
3
|
3
|
||||||
<span class="o">>>></span></code></pre>
|
<span class="o">>>></span></code></pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -700,11 +756,11 @@ pre.rouge .ss {
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="conum" data-value="1"></i><b>1</b></td>
|
<td><i class="conum" data-value="1"></i><b>1</b></td>
|
||||||
<td>Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.</td>
|
<td>Number bases: 0x = <em>hexadecimal</em>, 0o = <em>octal</em>, 0b = <em>binary</em>.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="conum" data-value="2"></i><b>2</b></td>
|
<td><i class="conum" data-value="2"></i><b>2</b></td>
|
||||||
<td>Fractions: numerator | denominator.</td>
|
<td>Fractions: <em>numerator</em> | <em>denominator</em>.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="conum" data-value="3"></i><b>3</b></td>
|
<td><i class="conum" data-value="3"></i><b>3</b></td>
|
||||||
@@ -727,7 +783,7 @@ pre.rouge .ss {
|
|||||||
<h2 id="_data_types"><a class="anchor" href="#_data_types"></a><a class="link" href="#_data_types">2. Data types</a></h2>
|
<h2 id="_data_types"><a class="anchor" href="#_data_types"></a><a class="link" href="#_data_types">2. Data types</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>Expr</em> supports numerical, string, relational, boolean expressions, and mixed-type lists.</p>
|
<p><em>Expr</em> 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.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_numbers"><a class="anchor" href="#_numbers"></a><a class="link" href="#_numbers">2.1. Numbers</a></h3>
|
<h3 id="_numbers"><a class="anchor" href="#_numbers"></a><a class="link" href="#_numbers">2.1. Numbers</a></h3>
|
||||||
@@ -1011,28 +1067,38 @@ pre.rouge .ss {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The items of strings can be accessed using the dot <code>.</code> operator.</p>
|
<p>The items of strings can be accessed using the square <code>[]</code> operator.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 4. Item access syntax</div>
|
<div class="title">Example 4. Item access syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>item</em> = <em>string-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
|
<p><em>item</em> = <em>string-expr</em> "<strong>[</strong>" <em>integer-expr</em> "<strong>]</strong>"</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="exampleblock">
|
||||||
|
<div class="title">Example 5. Sub string syntax</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><em>sub-string</em> = <em>string-expr</em> "<strong>[</strong>" <em>integer-expr</em> "<strong>:</strong>" <em>integer-expr</em> "<strong>]</strong>"</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<div class="title">String examples</div>
|
<div class="title">String examples</div>
|
||||||
<p><code>>>></code> <code class="blue">s="abc"</code> <em class="gray">// assign the string to variable s</em><br>
|
<p><code>>>></code> <code class="blue">s="abcd"</code> <em class="gray">// assign the string to variable s</em><br>
|
||||||
<code class="green">abc</code><br>
|
<code class="green">"abcd"</code><br>
|
||||||
<code>>>></code> <code class="blue">s.1</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
|
<code>>>></code> <code class="blue">s[1]</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
|
||||||
<code class="green">b</code><br>
|
<code class="green">"b"</code><br>
|
||||||
<code>>>></code> <code class="blue">s.(-1)</code> <em class="gray">// char at position -1, the rightmost one</em><br>
|
<code>>>></code> <code class="blue">s.[-1]</code> <em class="gray">// char at position -1, the rightmost one</em><br>
|
||||||
<code class="green">c</code><br>
|
<code class="green">"d"</code><br>
|
||||||
<code>>>></code> <code class="blue">#s</code> <em class="gray">// number of chars</em><br>
|
<code>>>></code> <code class="blue">#s</code> <em class="gray">// number of chars</em><br>
|
||||||
<code class="gren">3</code><br>
|
<code class="gren">4</code><br>
|
||||||
<code>>>></code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
|
<code>>>></code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
|
||||||
<code class="green">3</code></p>
|
<code class="green">3</code><br>
|
||||||
|
<code>>>></code> <code class="blue">s[1:3]</code> <em class="gray">// chars from position 1 to position 3 excluded</em><br>
|
||||||
|
<code class="grean">"bc"</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
@@ -1179,7 +1245,7 @@ pre.rouge .ss {
|
|||||||
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions. Internally, <em>Expr</em>'s lists are Go arrays.</p>
|
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions. Internally, <em>Expr</em>'s lists are Go arrays.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 5. List literal syntax</div>
|
<div class="title">Example 6. List literal syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><strong><em>list</em></strong> = <em>empty-list</em> | <em>non-empty-list</em><br>
|
<p><strong><em>list</em></strong> = <em>empty-list</em> | <em>non-empty-list</em><br>
|
||||||
@@ -1261,7 +1327,7 @@ pre.rouge .ss {
|
|||||||
<p>The items of array can be accessed using the dot <code>.</code> operator.</p>
|
<p>The items of array can be accessed using the dot <code>.</code> operator.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 6. Item access syntax</div>
|
<div class="title">Example 7. Item access syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
|
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
|
||||||
@@ -1297,7 +1363,7 @@ pre.rouge .ss {
|
|||||||
<p>Dictionary literals are sequences of pairs separated by comma <code class="blue">,</code> enclosed between brace brackets.</p>
|
<p>Dictionary literals are sequences of pairs separated by comma <code class="blue">,</code> enclosed between brace brackets.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 7. Dict literal syntax</div>
|
<div class="title">Example 8. Dict literal syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><strong><em>dict</em></strong> = <em>empty-dict</em> | <em>non-empty-dict</em><br>
|
<p><strong><em>dict</em></strong> = <em>empty-dict</em> | <em>non-empty-dict</em><br>
|
||||||
@@ -1357,13 +1423,13 @@ pre.rouge .ss {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect1">
|
<div class="sect1">
|
||||||
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
|
<h2 id="_variables_2"><a class="anchor" href="#_variables_2"></a><a class="link" href="#_variables_2">3. Variables</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>Expr</em>, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
|
<p><em>Expr</em>, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 8. Variable literal syntax</div>
|
<div class="title">Example 9. Variable literal syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><strong><em>variable</em></strong> = <em>identifier</em> "<strong>=</strong>" <em>any-value</em><br>
|
<p><strong><em>variable</em></strong> = <em>identifier</em> "<strong>=</strong>" <em>any-value</em><br>
|
||||||
@@ -1884,7 +1950,7 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div id="footer-text">
|
<div id="footer-text">
|
||||||
Last updated 2024-06-03 06:26:03 +0200
|
Last updated 2024-06-19 09:33:41 +0200
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
builtin ["os.file", "base"];
|
|
||||||
|
|
||||||
readInt=func(fh){
|
|
||||||
line=readFile(fh);
|
|
||||||
line ? [nil] {nil} :: {int(line)}
|
|
||||||
};
|
|
||||||
|
|
||||||
ds={
|
|
||||||
"init":func(filename){
|
|
||||||
fh=openFile(filename);
|
|
||||||
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
|
|
||||||
fh
|
|
||||||
},
|
|
||||||
"current":func(){
|
|
||||||
prev
|
|
||||||
},
|
|
||||||
"next":func(fh){
|
|
||||||
current ?
|
|
||||||
[nil] {current}
|
|
||||||
:: {@prev=current; @current=readInt(fh) but current}
|
|
||||||
},
|
|
||||||
"clean":func(fh){
|
|
||||||
closeFile(fh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//;f=$(ds, "int.list")
|
|
||||||
/*
|
|
||||||
;f++
|
|
||||||
;f++
|
|
||||||
;f++
|
|
||||||
*/
|
|
||||||
//;add(f)
|
|
||||||
+14
-2
@@ -6,7 +6,7 @@ package expr
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type FmtOpt uint16
|
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TTY FmtOpt = 1 << iota
|
TTY FmtOpt = 1 << iota
|
||||||
@@ -30,6 +30,18 @@ func TruncateString(s string) (trunc string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
|
||||||
|
return FmtOpt(indent<<16) | flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatFlags(opt FmtOpt) FmtOpt {
|
||||||
|
return opt & 0xFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatIndent(opt FmtOpt) int {
|
||||||
|
return int(opt >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
ToString(options FmtOpt) string
|
ToString(options FmtOpt) string
|
||||||
}
|
}
|
||||||
@@ -51,7 +63,7 @@ type Typer interface {
|
|||||||
TypeName() string
|
TypeName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func typeName(v any) (name string) {
|
func TypeName(v any) (name string) {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
name = "nil"
|
name = "nil"
|
||||||
} else if typer, ok := v.(Typer); ok {
|
} else if typer, ok := v.(Typer); ok {
|
||||||
|
|||||||
+4
-4
@@ -70,7 +70,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
|
|||||||
}
|
}
|
||||||
for _, c := range dec[0:lsd] {
|
for _, c := range dec[0:lsd] {
|
||||||
if c < '0' || c > '9' {
|
if c < '0' || c > '9' {
|
||||||
return nil, errExpectedGot("fract", "digit", c)
|
return nil, ErrExpectedGot("fract", "digit", c)
|
||||||
}
|
}
|
||||||
num = num*10 + int64(c-'0')
|
num = num*10 + int64(c-'0')
|
||||||
den = den * 10
|
den = den * 10
|
||||||
@@ -81,7 +81,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
|
|||||||
mul := int64(1)
|
mul := int64(1)
|
||||||
for _, c := range subParts[0] {
|
for _, c := range subParts[0] {
|
||||||
if c < '0' || c > '9' {
|
if c < '0' || c > '9' {
|
||||||
return nil, errExpectedGot("fract", "digit", c)
|
return nil, ErrExpectedGot("fract", "digit", c)
|
||||||
}
|
}
|
||||||
num = num*10 + int64(c-'0')
|
num = num*10 + int64(c-'0')
|
||||||
sub = sub*10 + int64(c-'0')
|
sub = sub*10 + int64(c-'0')
|
||||||
@@ -94,7 +94,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
|
|||||||
p := subParts[1][0 : len(subParts[1])-1]
|
p := subParts[1][0 : len(subParts[1])-1]
|
||||||
for _, c := range p {
|
for _, c := range p {
|
||||||
if c < '0' || c > '9' {
|
if c < '0' || c > '9' {
|
||||||
return nil, errExpectedGot("fract", "digit", c)
|
return nil, ErrExpectedGot("fract", "digit", c)
|
||||||
}
|
}
|
||||||
num = num*10 + int64(c-'0')
|
num = num*10 + int64(c-'0')
|
||||||
den = den*10 + 9
|
den = den*10 + 9
|
||||||
@@ -212,7 +212,7 @@ func anyToFract(v any) (f *FractionType, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f == nil {
|
if f == nil {
|
||||||
err = errExpectedGot("fract", TypeFraction, v)
|
err = ErrExpectedGot("fract", TypeFraction, v)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-2
@@ -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() {<body>}"
|
s = "func() {}"
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
|
|||||||
} else {
|
} else {
|
||||||
sb.WriteString(TypeAny)
|
sb.WriteString(TypeAny)
|
||||||
}
|
}
|
||||||
sb.WriteString(" {<body>}")
|
sb.WriteString(" {}")
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,3 +199,42 @@ func (info *funcInfo) MaxArgs() int {
|
|||||||
func (info *funcInfo) Functor() Functor {
|
func (info *funcInfo) Functor() Functor {
|
||||||
return info.functor
|
return info.functor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- Call a function ---
|
||||||
|
|
||||||
|
func checkFunctionCall(ctx ExprContext, name string, varParams *[]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 CallFunction(parentCtx ExprContext, name string, params []any) (result any, err error) {
|
||||||
|
ctx := cloneContext(parentCtx)
|
||||||
|
ctx.SetParent(parentCtx)
|
||||||
|
|
||||||
|
if err = checkFunctionCall(ctx, name, ¶ms); err == nil {
|
||||||
|
result, err = ctx.Call(name, params)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ const (
|
|||||||
|
|
||||||
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
||||||
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
|
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
|
||||||
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, typeName(paramValue))
|
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, TypeName(paramValue))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -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 := ToInt(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 := ToInt(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 := ToInt(args[2], "step"); err == nil {
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
i = -i
|
i = -i
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-6
@@ -52,16 +52,24 @@ func ListFromStrings(stringList []string) (list *ListType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
||||||
|
indent := GetFormatIndent(opt)
|
||||||
|
flags := GetFormatFlags(opt)
|
||||||
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteByte('[')
|
sb.WriteByte('[')
|
||||||
if len(*ls) > 0 {
|
if len(*ls) > 0 {
|
||||||
if opt&MultiLine != 0 {
|
innerOpt := MakeFormatOptions(flags, indent+1)
|
||||||
sb.WriteString("\n ")
|
nest := strings.Repeat(" ", indent+1)
|
||||||
|
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(nest)
|
||||||
}
|
}
|
||||||
for i, item := range []any(*ls) {
|
for i, item := range []any(*ls) {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
if opt&MultiLine != 0 {
|
if flags&MultiLine != 0 {
|
||||||
sb.WriteString(",\n ")
|
sb.WriteString(",\n")
|
||||||
|
sb.WriteString(nest)
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(", ")
|
sb.WriteString(", ")
|
||||||
}
|
}
|
||||||
@@ -70,17 +78,20 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
|||||||
sb.WriteByte('"')
|
sb.WriteByte('"')
|
||||||
sb.WriteString(s)
|
sb.WriteString(s)
|
||||||
sb.WriteByte('"')
|
sb.WriteByte('"')
|
||||||
|
} else if formatter, ok := item.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(innerOpt))
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(fmt.Sprintf("%v", item))
|
sb.WriteString(fmt.Sprintf("%v", item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opt&MultiLine != 0 {
|
if flags&MultiLine != 0 {
|
||||||
sb.WriteByte('\n')
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(strings.Repeat(" ", indent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.WriteByte(']')
|
sb.WriteByte(']')
|
||||||
s = sb.String()
|
s = sb.String()
|
||||||
if opt&Truncate != 0 && len(s) > TruncateSize {
|
if flags&Truncate != 0 && len(s) > TruncateSize {
|
||||||
s = TruncateString(s)
|
s = TruncateString(s)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
+2
-35
@@ -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, ¶ms); err == nil {
|
v, err = CallFunction(ctx, name, params)
|
||||||
if v, err = ctx.Call(name, params); err == nil {
|
|
||||||
exportObjects(parentCtx, ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, va
|
|||||||
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
|
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
|
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
|
||||||
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, typeName(keyListValue))
|
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if keyValue = (*keyList)[0]; keyValue == nil {
|
if keyValue = (*keyList)[0]; keyValue == nil {
|
||||||
@@ -41,7 +41,7 @@ func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, va
|
|||||||
if index, ok := keyValue.(int64); ok {
|
if index, ok := keyValue.(int64); ok {
|
||||||
err = collection.setItem(index, value)
|
err = collection.setItem(index, value)
|
||||||
} else {
|
} else {
|
||||||
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, typeName(keyValue))
|
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
|
||||||
}
|
}
|
||||||
case *DictType:
|
case *DictType:
|
||||||
err = collection.setItem(keyValue, value)
|
err = collection.setItem(keyValue, value)
|
||||||
|
|||||||
+9
-9
@@ -25,7 +25,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b, ok := toBool(rightValue); ok {
|
if b, ok := ToBool(rightValue); ok {
|
||||||
v = !b
|
v = !b
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
err = self.errIncompatibleType(rightValue)
|
||||||
@@ -65,8 +65,8 @@ func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftBool, lok = toBool(leftValue)
|
leftBool, lok = ToBool(leftValue)
|
||||||
rightBool, rok = toBool(rightValue)
|
rightBool, rok = ToBool(rightValue)
|
||||||
|
|
||||||
if lok && rok {
|
if lok && rok {
|
||||||
v = leftBool && rightBool
|
v = leftBool && rightBool
|
||||||
@@ -87,13 +87,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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 %T as left operand type of 'and' operator, it must be bool", leftBool)
|
||||||
return
|
return
|
||||||
} else if !leftBool {
|
} else if !leftBool {
|
||||||
v = false
|
v = false
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if rightBool, rok := toBool(rightValue); rok {
|
if rightBool, rok := ToBool(rightValue); rok {
|
||||||
v = rightBool
|
v = rightBool
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
@@ -134,8 +134,8 @@ func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftBool, lok = toBool(leftValue)
|
leftBool, lok = ToBool(leftValue)
|
||||||
rightBool, rok = toBool(rightValue)
|
rightBool, rok = ToBool(rightValue)
|
||||||
|
|
||||||
if lok && rok {
|
if lok && rok {
|
||||||
v = leftBool || rightBool
|
v = leftBool || rightBool
|
||||||
@@ -156,13 +156,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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 %T as left operand type of 'or' operator, it must be bool", leftBool)
|
||||||
return
|
return
|
||||||
} else if leftBool {
|
} else if leftBool {
|
||||||
v = true
|
v = true
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if rightBool, rok := toBool(rightValue); rok {
|
if rightBool, rok := ToBool(rightValue); rok {
|
||||||
v = rightBool
|
v = rightBool
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("expected string at item nr %d, got %s", it.Index()+1, typeName(moduleSpec))
|
err = self.Errorf("expected string at item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -31,7 +31,9 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sourceCtx != nil {
|
if sourceCtx != nil {
|
||||||
if formatter, ok := sourceCtx.(Formatter); ok {
|
if formatter, ok := sourceCtx.(DictFormat); ok {
|
||||||
|
v = formatter.ToDict()
|
||||||
|
} else if formatter, ok := sourceCtx.(Formatter); ok {
|
||||||
v = formatter.ToString(0)
|
v = formatter.ToString(0)
|
||||||
} else {
|
} else {
|
||||||
// keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
// keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||||
|
|||||||
+31
-4
@@ -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 = ToInt((*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
@@ -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, _ = ToInt(count, "")
|
||||||
} else {
|
} else {
|
||||||
v = int64(it.Index() + 1)
|
v = int64(it.Index() + 1)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-46
@@ -5,9 +5,7 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"plugin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//-------- plugin term
|
//-------- plugin term
|
||||||
@@ -22,48 +20,6 @@ func newPluginTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func importPlugin(ctx ExprContext, dirList []string, name string) (err error) {
|
|
||||||
var filePath string
|
|
||||||
var p *plugin.Plugin
|
|
||||||
var sym plugin.Symbol
|
|
||||||
var moduleName string
|
|
||||||
var importFunc func(ctx ExprContext)
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
decoratedName := fmt.Sprintf("expr-%s-plugin.so", name)
|
|
||||||
|
|
||||||
if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if p, err = plugin.Open(filePath); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if sym, err = p.Lookup("MODULE_NAME"); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if moduleName = *sym.(*string); moduleName == "" {
|
|
||||||
err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if sym, err = p.Lookup("ImportFuncs"); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if importFunc, ok = sym.(func(ExprContext)); !ok {
|
|
||||||
err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
registerPlugin(moduleName, p)
|
|
||||||
importFunc(globalCtx)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalPlugin(ctx ExprContext, self *term) (v any, err error) {
|
func evalPlugin(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
var moduleSpec any
|
var moduleSpec any
|
||||||
@@ -77,12 +33,12 @@ func evalPlugin(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
it := NewAnyIterator(childValue)
|
it := NewAnyIterator(childValue)
|
||||||
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
||||||
if module, ok := moduleSpec.(string); ok {
|
if module, ok := moduleSpec.(string); ok {
|
||||||
if err = importPlugin(ctx, dirList, module); err != nil {
|
if err = importPlugin(dirList, module); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("expected string as item nr %d, got %s", it.Index()+1, typeName(moduleSpec))
|
err = self.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@ func newNotEqualTerm(tk *Token) (inst *term) {
|
|||||||
|
|
||||||
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
|
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if v, err = evalEqual(ctx, self); err == nil {
|
if v, err = evalEqual(ctx, self); err == nil {
|
||||||
b, _ := toBool(v)
|
b, _ := ToBool(v)
|
||||||
v = !b
|
v = !b
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+77
@@ -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,81 @@ 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) {
|
||||||
|
var filePath string
|
||||||
|
var p *plugin.Plugin
|
||||||
|
var sym plugin.Symbol
|
||||||
|
var moduleName string
|
||||||
|
var importFunc func(ExprContext)
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
decoratedName := makePluginName(name)
|
||||||
|
|
||||||
|
if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, err = plugin.Open(filePath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sym, err = p.Lookup("MODULE_NAME"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleName = *sym.(*string); moduleName == "" {
|
||||||
|
err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sym, err = p.Lookup("DEPENDENCIES"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deps := *sym.(*[]string); len(deps) > 0 {
|
||||||
|
// var count int
|
||||||
|
if err = loadModules(dirList, deps); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sym, err = p.Lookup("ImportFuncs"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if importFunc, ok = sym.(func(ExprContext)); !ok {
|
||||||
|
err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPlugin(moduleName, p)
|
||||||
|
importFunc(globalCtx)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadModules(dirList []string, moduleNames []string) (err error) {
|
||||||
|
for _, name := range moduleNames {
|
||||||
|
if err1 := importPlugin(dirList, name); err1 != nil {
|
||||||
|
if !ImportInContext(name) {
|
||||||
|
err = err1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
pluginRegister = make(map[string]*plugin.Plugin)
|
pluginRegister = make(map[string]*plugin.Plugin)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -178,13 +178,18 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
tk = self.makeToken(SymDot, ch)
|
tk = self.makeToken(SymDot, ch)
|
||||||
}
|
}
|
||||||
case '\'':
|
case '\'':
|
||||||
tk = self.makeToken(SymQuote, ch)
|
if escape {
|
||||||
|
tk = self.makeToken(SymQuote, ch)
|
||||||
|
escape = false
|
||||||
|
} else {
|
||||||
|
tk = self.fetchString(ch)
|
||||||
|
}
|
||||||
case '"':
|
case '"':
|
||||||
if escape {
|
if escape {
|
||||||
tk = self.makeToken(SymDoubleQuote, ch)
|
tk = self.makeToken(SymDoubleQuote, ch)
|
||||||
escape = false
|
escape = false
|
||||||
} else {
|
} else {
|
||||||
tk = self.fetchString()
|
tk = self.fetchString(ch)
|
||||||
}
|
}
|
||||||
case '`':
|
case '`':
|
||||||
tk = self.makeToken(SymBackTick, ch)
|
tk = self.makeToken(SymBackTick, ch)
|
||||||
@@ -533,7 +538,7 @@ func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchString() (tk *Token) {
|
func (self *scanner) fetchString(termCh byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch, prev byte
|
var ch, prev byte
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
@@ -554,7 +559,7 @@ func (self *scanner) fetchString() (tk *Token) {
|
|||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
prev = 0
|
prev = 0
|
||||||
} else if ch == '"' {
|
} else if ch == termCh {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
prev = ch
|
prev = ch
|
||||||
|
|||||||
+53
-3
@@ -7,7 +7,7 @@ package expr
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
// "strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SimpleStore struct {
|
type SimpleStore struct {
|
||||||
@@ -51,6 +51,7 @@ func (ctx *SimpleStore) Merge(src ExprContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
sb.WriteString("vars: {\n")
|
sb.WriteString("vars: {\n")
|
||||||
first := true
|
first := true
|
||||||
@@ -77,7 +78,7 @@ func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.WriteString(strings.Repeat("\t", indent))
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
sb.WriteString("\n}\n")
|
sb.WriteString("\n}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func varsCtxToString(ctx ExprContext, indent int) string {
|
func varsCtxToString(ctx ExprContext, indent int) string {
|
||||||
@@ -106,21 +107,70 @@ func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
|||||||
sb.WriteString(fmt.Sprintf("%v", value))
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.WriteString("\n}\n")
|
sb.WriteString("\n}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
varsCtxToBuilder(&sb, ctx, 0)
|
varsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
sb.WriteByte('\n')
|
||||||
funcsCtxToBuilder(&sb, ctx, 0)
|
funcsCtxToBuilder(&sb, ctx, 0)
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||||
|
dict := ctx.ToDict()
|
||||||
|
return dict.ToString(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) varsToDict(dict *DictType) *DictType {
|
||||||
|
names := ctx.EnumVars(nil)
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range ctx.EnumVars(nil) {
|
||||||
|
value, _ := ctx.GetVar(name)
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
(*dict)[name] = f.ToString(0)
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
(*dict)[name] = "func(){}"
|
||||||
|
} else {
|
||||||
|
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) funcsToDict(dict *DictType) *DictType {
|
||||||
|
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range names {
|
||||||
|
value, _ := ctx.GetFuncInfo(name)
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
(*dict)[name] = formatter.ToString(0)
|
||||||
|
} else {
|
||||||
|
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) ToDict() (dict *DictType) {
|
||||||
|
dict = MakeDict()
|
||||||
|
(*dict)["variables"] = ctx.varsToDict(MakeDict())
|
||||||
|
(*dict)["functions"] = ctx.funcsToDict(MakeDict())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||||
v, exists = ctx.varStore[varName]
|
v, exists = ctx.varStore[varName]
|
||||||
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
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// t_func-base_test.go
|
// t_builtin-base_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFuncBase(t *testing.T) {
|
func TestFuncBase(t *testing.T) {
|
||||||
section := "Func-Base"
|
section := "Builtin-Base"
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`isNil(nil)`, true, nil},
|
/* 1 */ {`isNil(nil)`, true, nil},
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-import_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncImport(t *testing.T) {
|
||||||
|
section := "Builtin-Import"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`builtin "import"; import("./test-resources/test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 3 */ {`builtin "import"; importAll("./test-resources/test-funcs.expr"); six()`, int64(6), nil},
|
||||||
|
/* 4 */ {`builtin "import"; import("./test-resources/sample-export-all.expr"); six()`, int64(6), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", "test-resources")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 69)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// t_func-math-arith_test.go
|
// t_builtin-math-arith_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFuncMathArith(t *testing.T) {
|
func TestFuncMathArith(t *testing.T) {
|
||||||
section := "Func-Math-Arith"
|
section := "Builtin-Math-Arith"
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
|
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
|
||||||
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
|
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-os-file_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncOs(t *testing.T) {
|
||||||
|
section := "Builtin-OS-File"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`builtin "os.file"`, int64(1), 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`)},
|
||||||
|
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); 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=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
|
||||||
|
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, errors.New(`fileReadText(): invalid file handle`)},
|
||||||
|
/* 7 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 69)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-string_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncString(t *testing.T) {
|
||||||
|
section := "Builtin-String"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`builtin "string"; strJoin("-", "one", "two", "three")`, "one-two-three", nil},
|
||||||
|
/* 2 */ {`builtin "string"; strJoin("-", ["one", "two", "three"])`, "one-two-three", nil},
|
||||||
|
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin("-", ls)`, "one-two-three", nil},
|
||||||
|
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, errors.New(`strJoin(): the "separator" parameter must be a string, got a integer (1)`)},
|
||||||
|
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, errors.New(`strJoin(): expected string, got integer (2)`)},
|
||||||
|
/* 6 */ {`builtin "string"; "<"+strTrim(" bye bye ")+">"`, "<bye bye>", nil},
|
||||||
|
/* 7 */ {`builtin "string"; strSub("0123456789", 1,2)`, "12", nil},
|
||||||
|
/* 8 */ {`builtin "string"; strSub("0123456789", -3,2)`, "78", nil},
|
||||||
|
/* 9 */ {`builtin "string"; strSub("0123456789", -3)`, "789", nil},
|
||||||
|
/* 10 */ {`builtin "string"; strSub("0123456789")`, "0123456789", nil},
|
||||||
|
/* 11 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "012")`, true, nil},
|
||||||
|
/* 12 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "0125")`, false, nil},
|
||||||
|
/* 13 */ {`builtin "string"; strStartsWith("0123456789")`, nil, errors.New(`strStartsWith(): too few params -- expected 2 or more, got 1`)},
|
||||||
|
/* 14 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "789")`, true, nil},
|
||||||
|
/* 15 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "0125")`, false, nil},
|
||||||
|
/* 16 */ {`builtin "string"; strEndsWith("0123456789")`, nil, errors.New(`strEndsWith(): too few params -- expected 2 or more, got 1`)},
|
||||||
|
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
|
||||||
|
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, errors.New(`strJoin(): expected string, got integer (1)`)},
|
||||||
|
/* 19 */ {`builtin "string"; strJoin()`, nil, errors.New(`strJoin(): too few params -- expected 1 or more, got 0`)},
|
||||||
|
|
||||||
|
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
||||||
|
}
|
||||||
|
funcs: {
|
||||||
|
add(any=0 ...) -> number,
|
||||||
|
dec(any) -> decimal,
|
||||||
|
endsWithStr(source, suffix) -> boolean,
|
||||||
|
fract(any, denominator=1) -> fraction,
|
||||||
|
import( ...) -> any,
|
||||||
|
importAll( ...) -> any,
|
||||||
|
int(any) -> integer,
|
||||||
|
isBool(any) -> boolean,
|
||||||
|
isDec(any) -> boolean,
|
||||||
|
isDict(any) -> boolean,
|
||||||
|
isFloat(any) -> boolean,
|
||||||
|
isFract(any) -> boolean,
|
||||||
|
isInt(any) -> boolean,
|
||||||
|
isList(any) -> boolean,
|
||||||
|
isNil(any) -> boolean,
|
||||||
|
isString(any) -> boolean,
|
||||||
|
joinStr(separator, item="" ...) -> string,
|
||||||
|
mul(any=1 ...) -> number,
|
||||||
|
splitStr(source, separator="", count=-1) -> list of string,
|
||||||
|
startsWithStr(source, prefix) -> boolean,
|
||||||
|
subStr(source, start=0, count=-1) -> string,
|
||||||
|
trimStr(source) -> string
|
||||||
|
}
|
||||||
|
`, nil},*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 19)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
+2
-1
@@ -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
|
||||||
@@ -110,7 +111,7 @@ func TestDictToStringMultiLine(t *testing.T) {
|
|||||||
var good bool
|
var good bool
|
||||||
section := "dict-ToString-ML"
|
section := "dict-ToString-ML"
|
||||||
want := `{
|
want := `{
|
||||||
"first": 1
|
"first": 1
|
||||||
}`
|
}`
|
||||||
args := map[any]*term{
|
args := map[any]*term{
|
||||||
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
|||||||
+1
-1
@@ -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=openFile("test-file.txt"); line=readFile(f); closeFile(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 */ {`
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// t_func-import_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFuncImport(t *testing.T) {
|
|
||||||
section := "Func-Import"
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 3 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
|
|
||||||
/* 4 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Setenv("EXPR_PATH", ".")
|
|
||||||
|
|
||||||
// parserTestSpec(t, section, inputs, 69)
|
|
||||||
parserTest(t, section, inputs)
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// t_func-os_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFuncOs(t *testing.T) {
|
|
||||||
section := "Func-OS"
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`builtin "os.file"`, int64(1), nil},
|
|
||||||
/* 2 */ {`builtin "os.file"; handle=openFile("/etc/hosts"); closeFile(handle)`, true, nil},
|
|
||||||
/* 3 */ {`builtin "os.file"; handle=openFile("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
|
|
||||||
/* 4 */ {`builtin "os.file"; handle=createFile("/tmp/dummy"); closeFile(handle)`, true, nil},
|
|
||||||
/* 5 */ {`builtin "os.file"; handle=appendFile("/tmp/dummy"); writeFile(handle, "bye-bye"); closeFile(handle)`, true, nil},
|
|
||||||
/* 6 */ {`builtin "os.file"; handle=openFile("/tmp/dummy"); word=readFile(handle, "-"); closeFile(handle);word`, "bye", nil},
|
|
||||||
/* 7 */ {`builtin "os.file"; word=readFile(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)},
|
|
||||||
/* 7 */ {`builtin "os.file"; writeFile(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)},
|
|
||||||
}
|
|
||||||
|
|
||||||
// t.Setenv("EXPR_PATH", ".")
|
|
||||||
|
|
||||||
// parserTestSpec(t, section, inputs, 69)
|
|
||||||
parserTest(t, section, inputs)
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// t_func-string_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFuncString(t *testing.T) {
|
|
||||||
section := "Func-String"
|
|
||||||
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
|
|
||||||
/* 2 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
|
|
||||||
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
|
|
||||||
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a integer (1)`)},
|
|
||||||
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got integer (2)`)},
|
|
||||||
/* 6 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
|
|
||||||
/* 7 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
|
|
||||||
/* 8 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
|
|
||||||
/* 9 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
|
|
||||||
/* 10 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
|
|
||||||
/* 11 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
|
|
||||||
/* 12 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
|
||||||
/* 13 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`startsWithStr(): too few params -- expected 2 or more, got 1`)},
|
|
||||||
/* 14 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
|
|
||||||
/* 15 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
|
||||||
/* 16 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`endsWithStr(): too few params -- expected 2 or more, got 1`)},
|
|
||||||
/* 17 */ {`builtin "string"; splitStr("one-two-three", "-")`, newListA("one", "two", "three"), nil},
|
|
||||||
/* 18 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got integer (1)`)},
|
|
||||||
/* 19 */ {`builtin "string"; joinStr()`, nil, errors.New(`joinStr(): too few params -- expected 1 or more, got 0`)},
|
|
||||||
|
|
||||||
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
|
||||||
}
|
|
||||||
funcs: {
|
|
||||||
add(any=0 ...) -> number,
|
|
||||||
dec(any) -> decimal,
|
|
||||||
endsWithStr(source, suffix) -> boolean,
|
|
||||||
fract(any, denominator=1) -> fraction,
|
|
||||||
import( ...) -> any,
|
|
||||||
importAll( ...) -> any,
|
|
||||||
int(any) -> integer,
|
|
||||||
isBool(any) -> boolean,
|
|
||||||
isDec(any) -> boolean,
|
|
||||||
isDict(any) -> boolean,
|
|
||||||
isFloat(any) -> boolean,
|
|
||||||
isFract(any) -> boolean,
|
|
||||||
isInt(any) -> boolean,
|
|
||||||
isList(any) -> boolean,
|
|
||||||
isNil(any) -> boolean,
|
|
||||||
isString(any) -> boolean,
|
|
||||||
joinStr(separator, item="" ...) -> string,
|
|
||||||
mul(any=1 ...) -> number,
|
|
||||||
splitStr(source, separator="", count=-1) -> list of string,
|
|
||||||
startsWithStr(source, prefix) -> boolean,
|
|
||||||
subStr(source, start=0, count=-1) -> string,
|
|
||||||
trimStr(source) -> string
|
|
||||||
}
|
|
||||||
`, nil},*/
|
|
||||||
}
|
|
||||||
|
|
||||||
//t.Setenv("EXPR_PATH", ".")
|
|
||||||
|
|
||||||
// parserTestSpec(t, section, inputs, 19)
|
|
||||||
parserTest(t, section, inputs)
|
|
||||||
}
|
|
||||||
+1
-1
@@ -44,7 +44,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() {<body>}"
|
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)
|
||||||
|
|||||||
+9
-9
@@ -8,15 +8,15 @@ import "testing"
|
|||||||
|
|
||||||
func TestIteratorParser(t *testing.T) {
|
func TestIteratorParser(t *testing.T) {
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
|
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
|
||||||
/* 2 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
|
/* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
|
||||||
/* 3 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
|
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
|
||||||
/* 4 */ {`include "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 "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 "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 "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 "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 "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{
|
||||||
|
|||||||
+4
-2
@@ -49,8 +49,10 @@ 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", ".")
|
||||||
|
|||||||
+1
-1
@@ -203,7 +203,7 @@ func doTest(t *testing.T, section string, input *inputType, count int) (good boo
|
|||||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||||
|
|
||||||
if !eq /*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))
|
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
|
||||||
good = false
|
good = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ func TestScanner(t *testing.T) {
|
|||||||
/* 22 */ {"@", SymAt, nil, nil},
|
/* 22 */ {"@", SymAt, nil, nil},
|
||||||
/* 23 */ {`#`, SymHash, nil, nil},
|
/* 23 */ {`#`, SymHash, nil, nil},
|
||||||
/* 24 */ {`%`, SymPercent, nil, nil},
|
/* 24 */ {`%`, SymPercent, nil, nil},
|
||||||
/* 25 */ {`'`, SymQuote, nil, nil},
|
/* 25 */ {`\'`, SymQuote, nil, nil},
|
||||||
/* 26 */ {`\"`, SymDoubleQuote, nil, nil},
|
/* 26 */ {`\"`, SymDoubleQuote, nil, nil},
|
||||||
/* 27 */ {`_`, SymUndescore, nil, nil},
|
/* 27 */ {`_`, SymUndescore, nil, nil},
|
||||||
/* 28 */ {`<>`, SymLessGreater, nil, nil},
|
/* 28 */ {`<>`, SymLessGreater, nil, nil},
|
||||||
|
|||||||
+5
-5
@@ -63,7 +63,7 @@ func TestIsString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count++
|
count++
|
||||||
b, ok := toBool(true)
|
b, ok := ToBool(true)
|
||||||
if !(ok && b) {
|
if !(ok && b) {
|
||||||
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
|
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||||
failed++
|
failed++
|
||||||
@@ -72,7 +72,7 @@ func TestIsString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count++
|
count++
|
||||||
b, ok = toBool("abc")
|
b, ok = ToBool("abc")
|
||||||
if !(ok && b) {
|
if !(ok && b) {
|
||||||
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
|
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||||
failed++
|
failed++
|
||||||
@@ -81,7 +81,7 @@ func TestIsString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count++
|
count++
|
||||||
b, ok = toBool([]int{})
|
b, ok = ToBool([]int{})
|
||||||
if ok {
|
if ok {
|
||||||
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
|
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
|
||||||
failed++
|
failed++
|
||||||
@@ -97,7 +97,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 := ToInt(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 +110,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 := ToInt(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",
|
||||||
|
|||||||
@@ -156,15 +156,15 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
|
|||||||
if index64, ok := computedValue.(int64); ok {
|
if index64, ok := computedValue.(int64); ok {
|
||||||
i = int(index64)
|
i = int(index64)
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("%s, got %s (%v)", valueDescription, typeName(computedValue), computedValue)
|
err = self.Errorf("%s, got %s (%v)", valueDescription, TypeName(computedValue), computedValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||||
leftType := typeName(leftValue)
|
leftType := TypeName(leftValue)
|
||||||
leftText := getFormatted(leftValue, Truncate)
|
leftText := getFormatted(leftValue, Truncate)
|
||||||
rightType := typeName(rightValue)
|
rightType := TypeName(rightValue)
|
||||||
rightText := getFormatted(rightValue, Truncate)
|
rightText := getFormatted(rightValue, Truncate)
|
||||||
return self.tk.Errorf(
|
return self.tk.Errorf(
|
||||||
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
|
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
|
||||||
@@ -176,7 +176,7 @@ func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
|||||||
func (self *term) errIncompatibleType(value any) error {
|
func (self *term) errIncompatibleType(value any) error {
|
||||||
return self.tk.Errorf(
|
return self.tk.Errorf(
|
||||||
"prefix/postfix operator %q do not support operand '%v' [%s]",
|
"prefix/postfix operator %q do not support operand '%v' [%s]",
|
||||||
self.source(), value, typeName(value))
|
self.source(), value, TypeName(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *term) Errorf(template string, args ...any) (err error) {
|
func (self *term) Errorf(template string, args ...any) (err error) {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
builtin ["os.file", "base"];
|
||||||
|
|
||||||
|
readInt=func(fh){
|
||||||
|
line=fileReadText(fh);
|
||||||
|
line ? [nil] {nil} :: {int(line)}
|
||||||
|
};
|
||||||
|
|
||||||
|
ds={
|
||||||
|
"init":func(filename){
|
||||||
|
fh=fileOpen(filename);
|
||||||
|
fh ? [nil] {nil} :: { @current=readInt(fh) };
|
||||||
|
fh
|
||||||
|
},
|
||||||
|
"current":func(){
|
||||||
|
current
|
||||||
|
},
|
||||||
|
"next":func(fh){
|
||||||
|
@current=readInt(fh);
|
||||||
|
current
|
||||||
|
},
|
||||||
|
"clean":func(fh){
|
||||||
|
fileClose(fh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//;f=$(ds, "int.list")
|
||||||
|
|
||||||
|
//;f++
|
||||||
|
//;f++
|
||||||
|
//;f++
|
||||||
|
//*/
|
||||||
|
//;add(f)
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func numAsFloat(v any) (f float64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func toBool(v any) (b bool, ok bool) {
|
func ToBool(v any) (b bool, ok bool) {
|
||||||
ok = true
|
ok = true
|
||||||
switch x := v.(type) {
|
switch x := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -201,13 +201,13 @@ 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 ToInt(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 {
|
||||||
i = valueInt
|
i = valueInt
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("%s expected integer, got %s (%v)", description, typeName(value), value)
|
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user