Compare commits
47 Commits
v0.5.0
...
0bca3333aa
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bca3333aa | |||
| 02b7a6df6c | |||
| 8d9963207e | |||
| f9486fa1bd | |||
| 360ebce015 | |||
| dc9eca83e8 | |||
| 2c55167dd0 | |||
| c124e880c4 | |||
| 4db015e4b1 | |||
| 5809de419f | |||
| 7c748f0e31 | |||
| cd6b7982ee | |||
| e00886b1ed | |||
| 49904f9097 | |||
| 2d0d03b975 | |||
| 8cb048edb0 | |||
| cb3d8827fa | |||
| 4c83764332 | |||
| c0c2ab8b4e | |||
| 288e93d708 | |||
| 92e862da19 | |||
| dc6975e56c | |||
| 6a2d3c53fd | |||
| 5643a57bcc | |||
| 52fb398cd8 | |||
| cdbe3dfc22 | |||
| 7aabd068ed | |||
| 924f5da725 | |||
| d657cbb51e | |||
| be874503ec | |||
| 056d42d328 | |||
| aa66d07caa | |||
| fc0e1ffaee | |||
| bf8f1a175f | |||
| 6dd8283308 | |||
| 06ab303b9e | |||
| 2ccbdb2254 | |||
| c5fca70cfc | |||
| 895778f236 | |||
| 81c85afbea | |||
| 354cb79580 | |||
| 327bffa01f | |||
| c99be491df | |||
| 60effe8f1b | |||
| 824b9382be | |||
| 9ce6b7255b | |||
| 9dbf472630 |
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// common-errors.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// --- General errors
|
||||
|
||||
func errCantConvert(funcName string, value any, kind string) error {
|
||||
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
||||
}
|
||||
|
||||
func errExpectedGot(funcName string, kind string, value any) error {
|
||||
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
|
||||
}
|
||||
|
||||
// --- Parameter errors
|
||||
|
||||
func errOneParam(funcName string) error {
|
||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
}
|
||||
|
||||
func errMissingRequiredParameter(funcName, paramName string) error {
|
||||
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
|
||||
}
|
||||
|
||||
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
|
||||
}
|
||||
|
||||
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// common-params.go
|
||||
package expr
|
||||
|
||||
const (
|
||||
paramParts = "parts"
|
||||
paramSeparator = "separator"
|
||||
paramSource = "source"
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// common-type-names.go
|
||||
package expr
|
||||
|
||||
const (
|
||||
typeBoolean = "boolean"
|
||||
typeFloat = "decimal"
|
||||
typeFraction = "fraction"
|
||||
typeInt = "integer"
|
||||
typeNumber = "number"
|
||||
typeString = "string"
|
||||
)
|
||||
@@ -27,8 +27,10 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||
|
||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||
exportAll := isEnabled(sourceCtx, control_export_all)
|
||||
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
||||
// Export variables
|
||||
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
// fmt.Printf("\tExporting %q\n", refName)
|
||||
refValue, _ := sourceCtx.GetVar(refName)
|
||||
exportVar(destCtx, refName, refValue)
|
||||
}
|
||||
|
||||
+105
-10
@@ -5,34 +5,122 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
initName = "init"
|
||||
cleanName = "clean"
|
||||
resetName = "reset"
|
||||
nextName = "next"
|
||||
currentName = "current"
|
||||
)
|
||||
|
||||
type dataCursor struct {
|
||||
ds map[any]*term
|
||||
ds map[string]Functor
|
||||
ctx ExprContext
|
||||
index int
|
||||
resource any
|
||||
nextFunc Functor
|
||||
cleanFunc Functor
|
||||
resetFunc Functor
|
||||
currentFunc Functor
|
||||
}
|
||||
|
||||
func newDataCursor(ctx ExprContext) (dc *dataCursor) {
|
||||
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
|
||||
dc = &dataCursor{
|
||||
ds: ds,
|
||||
index: -1,
|
||||
ctx: ctx.Clone(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mapToString(m map[string]Functor) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte('{')
|
||||
for key, _ := range m {
|
||||
if sb.Len() > 1 {
|
||||
sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%q: func(){}", key))
|
||||
}
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (dc *dataCursor) String() string {
|
||||
return "$(...)"
|
||||
return "$()"
|
||||
/* var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf(`$(
|
||||
index: %d,
|
||||
ds: %s,
|
||||
ctx: `, dc.index, mapToString(dc.ds)))
|
||||
CtxToBuilder(&sb, dc.ctx, 1)
|
||||
sb.WriteByte(')')
|
||||
return sb.String()
|
||||
*/
|
||||
}
|
||||
|
||||
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
||||
f, ok := dc.ds[name]
|
||||
exists = ok && isFunctor(f)
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
|
||||
if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
|
||||
if functor == dc.cleanFunc {
|
||||
return nil, dc.Clean()
|
||||
} else if functor == dc.resetFunc {
|
||||
return nil, dc.Reset()
|
||||
} else {
|
||||
ctx := cloneContext(dc.ctx)
|
||||
value, err = functor.Invoke(ctx, name, []any{})
|
||||
exportObjects(dc.ctx, ctx)
|
||||
}
|
||||
|
||||
} else {
|
||||
err = errNoOperation(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Reset() (err error) {
|
||||
if dc.resetFunc != nil {
|
||||
if dc.resource != nil {
|
||||
ctx := cloneContext(dc.ctx)
|
||||
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
|
||||
dc.index = -1
|
||||
}
|
||||
exportObjects(dc.ctx, ctx)
|
||||
} else {
|
||||
err = errInvalidDataSource()
|
||||
}
|
||||
} else {
|
||||
err = errNoOperation(resetName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Clean() (err error) {
|
||||
if dc.cleanFunc != nil {
|
||||
if dc.resource != nil {
|
||||
ctx := cloneContext(dc.ctx)
|
||||
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
|
||||
dc.resource = nil
|
||||
exportObjects(dc.ctx, ctx)
|
||||
} else {
|
||||
err = errInvalidDataSource()
|
||||
}
|
||||
} else {
|
||||
err = errors.New("no 'clean' function defined in the data-source")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||
@@ -45,15 +133,22 @@ func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||
ctx := cloneContext(dc.ctx)
|
||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
|
||||
if item == nil {
|
||||
err = io.EOF
|
||||
} else {
|
||||
dc.index++
|
||||
if dc.resource != nil {
|
||||
ctx := cloneContext(dc.ctx)
|
||||
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
||||
if item == nil {
|
||||
err = io.EOF
|
||||
} else {
|
||||
dc.index++
|
||||
}
|
||||
}
|
||||
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||
exportObjects(dc.ctx, ctx)
|
||||
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
|
||||
} else {
|
||||
err = errInvalidDataSource()
|
||||
}
|
||||
exportObjects(dc.ctx, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// formatter.go
|
||||
package expr
|
||||
|
||||
type FmtOpt uint16
|
||||
|
||||
const (
|
||||
TTY FmtOpt = 1 << iota
|
||||
MultiLine
|
||||
)
|
||||
|
||||
type Formatter interface {
|
||||
ToString(options FmtOpt) string
|
||||
}
|
||||
@@ -55,5 +55,5 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("builtins", ImportBuiltinsFuncs)
|
||||
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-common.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func errOneParam(funcName string) error {
|
||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
}
|
||||
|
||||
func errCantConvert(funcName string, value any, kind string) error {
|
||||
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
||||
}
|
||||
+2
-2
@@ -30,7 +30,7 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
|
||||
|
||||
dirList = addEnvImportDirs(dirList)
|
||||
dirList = addPresetImportDirs(ctx, dirList)
|
||||
result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args))
|
||||
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,5 +142,5 @@ func ImportImportFuncs(ctx ExprContext) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("import", ImportImportFuncs)
|
||||
registerImport("import", ImportImportFuncs, "Functions import() and include()")
|
||||
}
|
||||
|
||||
+78
-20
@@ -10,16 +10,17 @@ import (
|
||||
)
|
||||
|
||||
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
||||
if !(isNumber(paramValue) || isList(paramValue)) {
|
||||
if !(isNumber(paramValue) || isList(paramValue) || isFraction(paramValue)) {
|
||||
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
var sumAsFloat = false
|
||||
var sumAsFloat, sumAsFract bool
|
||||
var floatSum float64 = 0.0
|
||||
var intSum int64 = 0
|
||||
var fractSum *fraction
|
||||
var v any
|
||||
|
||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||
@@ -27,23 +28,46 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
if v, err = doAdd(ctx, name, subIter); err != nil {
|
||||
break
|
||||
}
|
||||
if subIter.HasOperation(cleanName) {
|
||||
if _, err = subIter.CallOperation(cleanName, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
||||
break
|
||||
}
|
||||
if array, ok := v.([]any); ok {
|
||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
||||
if list, ok := v.(*ListType); ok {
|
||||
if v, err = doAdd(ctx, name, NewListIterator(list)); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !sumAsFloat && isFloat(v) {
|
||||
sumAsFloat = true
|
||||
floatSum = float64(intSum)
|
||||
if !sumAsFloat {
|
||||
if isFloat(v) {
|
||||
sumAsFloat = true
|
||||
if sumAsFract {
|
||||
floatSum = fractSum.toFloat()
|
||||
} else {
|
||||
floatSum = float64(intSum)
|
||||
}
|
||||
} else if !sumAsFract && isFraction(v) {
|
||||
fractSum = newFraction(intSum, 1)
|
||||
sumAsFract = true
|
||||
}
|
||||
}
|
||||
|
||||
if sumAsFloat {
|
||||
floatSum += numAsFloat(v)
|
||||
} else if sumAsFract {
|
||||
var item *fraction
|
||||
var ok bool
|
||||
if item, ok = v.(*fraction); !ok {
|
||||
iv, _ := v.(int64)
|
||||
item = newFraction(iv, 1)
|
||||
}
|
||||
fractSum = sumFract(fractSum, item)
|
||||
} else {
|
||||
iv, _ := v.(int64)
|
||||
intSum += iv
|
||||
@@ -53,6 +77,8 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
err = nil
|
||||
if sumAsFloat {
|
||||
result = floatSum
|
||||
} else if sumAsFract {
|
||||
result = fractSum
|
||||
} else {
|
||||
result = intSum
|
||||
}
|
||||
@@ -61,33 +87,63 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
}
|
||||
|
||||
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
|
||||
result, err = doAdd(ctx, name, NewArrayIterator(args))
|
||||
return
|
||||
}
|
||||
|
||||
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
var mulAsFloat = false
|
||||
var mulAsFloat, mulAsFract bool
|
||||
var floatProd float64 = 1.0
|
||||
var intProd int64 = 1
|
||||
var fractProd *fraction
|
||||
var v any
|
||||
|
||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if array, ok := v.([]any); ok {
|
||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
||||
if subIter, ok := v.(Iterator); ok {
|
||||
if v, err = doMul(ctx, name, subIter); err != nil {
|
||||
break
|
||||
}
|
||||
if subIter.HasOperation(cleanName) {
|
||||
if _, err = subIter.CallOperation(cleanName, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if list, ok := v.(*ListType); ok {
|
||||
if v, err = doMul(ctx, name, NewListIterator(list)); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mulAsFloat && isFloat(v) {
|
||||
mulAsFloat = true
|
||||
floatProd = float64(intProd)
|
||||
if !mulAsFloat {
|
||||
if isFloat(v) {
|
||||
mulAsFloat = true
|
||||
if mulAsFract {
|
||||
floatProd = fractProd.toFloat()
|
||||
} else {
|
||||
floatProd = float64(intProd)
|
||||
}
|
||||
} else if !mulAsFract && isFraction(v) {
|
||||
fractProd = newFraction(intProd, 1)
|
||||
mulAsFract = true
|
||||
}
|
||||
}
|
||||
|
||||
if mulAsFloat {
|
||||
floatProd *= numAsFloat(v)
|
||||
} else if mulAsFract {
|
||||
var item *fraction
|
||||
var ok bool
|
||||
if item, ok = v.(*fraction); !ok {
|
||||
iv, _ := v.(int64)
|
||||
item = newFraction(iv, 1)
|
||||
}
|
||||
fractProd = mulFract(fractProd, item)
|
||||
} else {
|
||||
iv, _ := v.(int64)
|
||||
intProd *= iv
|
||||
@@ -97,6 +153,8 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
err = nil
|
||||
if mulAsFloat {
|
||||
result = floatProd
|
||||
} else if mulAsFract {
|
||||
result = fractProd
|
||||
} else {
|
||||
result = intProd
|
||||
}
|
||||
@@ -105,7 +163,7 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
}
|
||||
|
||||
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result, err = doMul(ctx, name, NewFlatArrayIterator(args))
|
||||
result, err = doMul(ctx, name, NewArrayIterator(args))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,5 +173,5 @@ func ImportMathFuncs(ctx ExprContext) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("math.arith", ImportMathFuncs)
|
||||
registerImport("math.arith", ImportMathFuncs, "Function add() and mul()")
|
||||
}
|
||||
|
||||
+6
-3
@@ -126,7 +126,7 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
|
||||
|
||||
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
|
||||
result = nil
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
@@ -141,13 +141,16 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
|
||||
limit = s[0]
|
||||
}
|
||||
}
|
||||
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
|
||||
if v, err = r.reader.ReadString(limit); err == nil {
|
||||
if len(v) > 0 && v[len(v)-1] == limit {
|
||||
result = v[0 : len(v)-1]
|
||||
} else {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,5 +167,5 @@ func ImportOsFuncs(ctx ExprContext) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("os", ImportOsFuncs)
|
||||
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-string.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// --- Start of function definitions
|
||||
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
|
||||
var sb strings.Builder
|
||||
var v any
|
||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||
if it.Index() > 0 {
|
||||
sb.WriteString(sep)
|
||||
}
|
||||
if s, ok := v.(string); ok {
|
||||
sb.WriteString(s)
|
||||
} else {
|
||||
err = errExpectedGot(funcName, typeString, v)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err == nil || err == io.EOF {
|
||||
err = nil
|
||||
result = sb.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) < 1 {
|
||||
return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
}
|
||||
if sep, ok := args[0].(string); ok {
|
||||
if len(args) == 1 {
|
||||
result = ""
|
||||
} else if len(args) == 2 {
|
||||
if ls, ok := args[1].(*ListType); ok {
|
||||
result, err = doJoinStr(name, sep, NewListIterator(ls))
|
||||
} else if it, ok := args[1].(Iterator); ok {
|
||||
result, err = doJoinStr(name, sep, it)
|
||||
} else {
|
||||
err = errInvalidParameterValue(name, paramParts, args[1])
|
||||
}
|
||||
} else {
|
||||
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
|
||||
}
|
||||
} else {
|
||||
err = errWrongParamType(name, paramSeparator, typeString, args[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var start = 0
|
||||
var count = -1
|
||||
var source string
|
||||
var ok bool
|
||||
|
||||
if len(args) < 1 {
|
||||
return nil, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
if source, ok = args[0].(string); !ok {
|
||||
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if start, err = toInt(args[1], name+"()"); err != nil {
|
||||
return
|
||||
}
|
||||
if len(args) > 2 {
|
||||
if count, err = toInt(args[2], name+"()"); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if start < 0 {
|
||||
start = len(source) + start
|
||||
}
|
||||
}
|
||||
if count < 0 {
|
||||
count = len(source) - start
|
||||
}
|
||||
end := min(start+count, len(source))
|
||||
result = source[start:end]
|
||||
return
|
||||
}
|
||||
|
||||
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var source string
|
||||
var ok bool
|
||||
|
||||
if len(args) < 1 {
|
||||
return nil, errMissingRequiredParameter(name, paramSource)
|
||||
}
|
||||
if source, ok = args[0].(string); !ok {
|
||||
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||
}
|
||||
result = strings.TrimSpace(source)
|
||||
return
|
||||
}
|
||||
|
||||
// --- End of function definitions
|
||||
|
||||
// Import above functions in the context
|
||||
func ImportStringFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
||||
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
||||
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
||||
}
|
||||
|
||||
// Register the import function in the import-register.
|
||||
// That will allow to import all function of this module by the "builtin" operator."
|
||||
func init() {
|
||||
registerImport("string", ImportStringFuncs, "string utilities")
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// function-register.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var functionRegister map[string]func(ExprContext)
|
||||
|
||||
func registerImport(name string, importFunc func(ExprContext)) {
|
||||
if functionRegister == nil {
|
||||
functionRegister = make(map[string]func(ExprContext))
|
||||
}
|
||||
functionRegister[name] = importFunc
|
||||
}
|
||||
|
||||
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||
var importFunc func(ExprContext)
|
||||
if importFunc, exists = functionRegister[name]; exists {
|
||||
importFunc(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||
var matched bool
|
||||
for name, importFunc := range functionRegister {
|
||||
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||
if matched {
|
||||
count++
|
||||
importFunc(ctx)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
if functionRegister == nil {
|
||||
functionRegister = make(map[string]func(ExprContext))
|
||||
}
|
||||
}
|
||||
+23
@@ -6,6 +6,8 @@ package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -59,3 +61,24 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
|
||||
var tree *ast
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
parser := NewParser(ctx)
|
||||
|
||||
if tree, err = parser.Parse(scanner); err == nil {
|
||||
result, err = tree.Eval(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func EvalFile(ctx ExprContext, filePath string) (result any, err error) {
|
||||
var fh *os.File
|
||||
if fh, err = os.Open(filePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
result, err = EvalStream(ctx, fh)
|
||||
return
|
||||
}
|
||||
|
||||
+37
-9
@@ -6,25 +6,53 @@ package expr
|
||||
|
||||
import "io"
|
||||
|
||||
type FlatArrayIterator struct {
|
||||
a []any
|
||||
type ListIterator struct {
|
||||
a *ListType
|
||||
index int
|
||||
}
|
||||
|
||||
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
|
||||
return &FlatArrayIterator{a: array, index: 0}
|
||||
func NewListIterator(list *ListType) *ListIterator {
|
||||
return &ListIterator{a: list, index: 0}
|
||||
}
|
||||
|
||||
func (it *FlatArrayIterator) Current() (item any, err error) {
|
||||
if it.index >= 0 && it.index < len(it.a) {
|
||||
item = it.a[it.index]
|
||||
func NewArrayIterator(array []any) *ListIterator {
|
||||
return &ListIterator{a: (*ListType)(&array), index: 0}
|
||||
}
|
||||
|
||||
func NewAnyIterator(value any) (it *ListIterator) {
|
||||
if value == nil {
|
||||
it = NewArrayIterator([]any{})
|
||||
} else if list, ok := value.(*ListType); ok {
|
||||
it = NewListIterator(list)
|
||||
} else if array, ok := value.([]any); ok {
|
||||
it = NewArrayIterator(array)
|
||||
} else if it1, ok := value.(*ListIterator); ok {
|
||||
it = it1
|
||||
} else {
|
||||
it = NewArrayIterator([]any{value})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (it *ListIterator) HasOperation(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (it *ListIterator) CallOperation(name string, args []any) (any, error) {
|
||||
return nil, errNoOperation(name)
|
||||
}
|
||||
|
||||
func (it *ListIterator) Current() (item any, err error) {
|
||||
a := *(it.a)
|
||||
if it.index >= 0 && it.index < len(a) {
|
||||
item = a[it.index]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (it *FlatArrayIterator) Next() (item any, err error) {
|
||||
func (it *ListIterator) Next() (item any, err error) {
|
||||
if item, err = it.Current(); err != io.EOF {
|
||||
it.index++
|
||||
}
|
||||
@@ -37,6 +65,6 @@ func (it *FlatArrayIterator) Next() (item any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (it *FlatArrayIterator) Index() int {
|
||||
func (it *ListIterator) Index() int {
|
||||
return it.index - 1
|
||||
}
|
||||
|
||||
+15
@@ -4,8 +4,23 @@
|
||||
// iterator.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Iterator interface {
|
||||
Next() (item any, err error) // must return io.EOF after the last item
|
||||
Current() (item any, err error)
|
||||
Index() int
|
||||
HasOperation(name string) bool
|
||||
CallOperation(name string, args []any) (value any, err error)
|
||||
}
|
||||
|
||||
func errNoOperation(name string) error {
|
||||
return fmt.Errorf("no %q function defined in the data-source", name)
|
||||
}
|
||||
|
||||
func errInvalidDataSource() error {
|
||||
return errors.New("invalid data-source")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// module-register.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
importFunc func(ExprContext)
|
||||
description string
|
||||
imported bool
|
||||
}
|
||||
|
||||
func newModule(importFunc func(ExprContext), description string) *module {
|
||||
return &module{importFunc, description, false}
|
||||
}
|
||||
|
||||
var moduleRegister map[string]*module
|
||||
|
||||
func registerImport(name string, importFunc func(ExprContext), description string) {
|
||||
if moduleRegister == nil {
|
||||
moduleRegister = make(map[string]*module)
|
||||
}
|
||||
if _, exists := moduleRegister[name]; exists {
|
||||
panic(fmt.Errorf("module %q already registered", name))
|
||||
}
|
||||
moduleRegister[name] = newModule(importFunc, description)
|
||||
}
|
||||
|
||||
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||
var mod *module
|
||||
if mod, exists = moduleRegister[name]; exists {
|
||||
mod.importFunc(ctx)
|
||||
mod.imported = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||
var matched bool
|
||||
for name, mod := range moduleRegister {
|
||||
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||
if matched {
|
||||
count++
|
||||
mod.importFunc(ctx)
|
||||
mod.imported = true
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func IterateModules(op func(name, description string, imported bool) bool) {
|
||||
if op != nil {
|
||||
for name, mod := range moduleRegister {
|
||||
if !op(name, mod.description, mod.imported) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
||||
func init() {
|
||||
if moduleRegister == nil {
|
||||
moduleRegister = make(map[string]*module)
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
ctx := cloneContext(parentCtx)
|
||||
name, _ := self.tk.Value.(string)
|
||||
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
||||
params := make([]any, len(self.children))
|
||||
for i, tree := range self.children {
|
||||
var param any
|
||||
|
||||
+65
-8
@@ -6,6 +6,8 @@ package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// -------- iterator term
|
||||
@@ -41,6 +43,44 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
||||
// var value any
|
||||
// if len(self.children) < 1 || self.children[0] == nil {
|
||||
// err = self.Errorf("missing the data-source parameter")
|
||||
// return
|
||||
// }
|
||||
|
||||
// if value, err = self.children[0].compute(ctx); err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if dictAny, ok := value.(map[any]any); ok {
|
||||
// ds = make(map[string]Functor)
|
||||
// // required functions
|
||||
// for _, k := range []string{currentName, nextName} {
|
||||
// if item, exists := dictAny[k]; exists && item != nil {
|
||||
// if functor, ok := item.(*funcDefFunctor); ok {
|
||||
// ds[k] = functor
|
||||
// }
|
||||
// } else {
|
||||
// err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// // Optional functions
|
||||
// for _, k := range []string{initName, resetName, cleanName} {
|
||||
// if item, exists := dictAny[k]; exists && item != nil {
|
||||
// if functor, ok := item.(*funcDefFunctor); ok {
|
||||
// ds[k] = functor
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
||||
var value any
|
||||
if len(self.children) < 1 || self.children[0] == nil {
|
||||
@@ -52,18 +92,31 @@ func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err
|
||||
return
|
||||
}
|
||||
|
||||
requiredFields := []string{currentName, nextName}
|
||||
fieldsMask := 0b11
|
||||
foundFields := 0
|
||||
if dictAny, ok := value.(map[any]any); ok {
|
||||
ds = make(map[string]Functor)
|
||||
for _, k := range []string{initName, currentName, nextName} {
|
||||
if item, exists := dictAny[k]; exists && item != nil {
|
||||
for keyAny, item := range dictAny {
|
||||
if key, ok := keyAny.(string); ok {
|
||||
if functor, ok := item.(*funcDefFunctor); ok {
|
||||
ds[k] = functor
|
||||
ds[key] = functor
|
||||
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||
foundFields |= 1 << index
|
||||
}
|
||||
}
|
||||
} else if k != initName {
|
||||
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
|
||||
break
|
||||
}
|
||||
}
|
||||
// check required functions
|
||||
if foundFields != fieldsMask {
|
||||
missingFields := make([]string, 0, len(requiredFields))
|
||||
for index, field := range requiredFields {
|
||||
if (foundFields & (1 << index)) == 0 {
|
||||
missingFields = append(missingFields, field)
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
|
||||
}
|
||||
} else {
|
||||
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
||||
}
|
||||
@@ -77,8 +130,9 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
dc := newDataCursor(ctx)
|
||||
|
||||
dc := newDataCursor(ctx, ds)
|
||||
// var it Iterator = dc
|
||||
// fmt.Println(it)
|
||||
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
||||
var args []any
|
||||
if len(self.children) > 1 {
|
||||
@@ -98,6 +152,9 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
dc.nextFunc, _ = ds[nextName]
|
||||
dc.currentFunc, _ = ds[currentName]
|
||||
dc.cleanFunc, _ = ds[cleanName]
|
||||
dc.resetFunc, _ = ds[resetName]
|
||||
|
||||
v = dc
|
||||
|
||||
return
|
||||
|
||||
+44
-30
@@ -4,6 +4,48 @@
|
||||
// operand-list.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ListType []any
|
||||
|
||||
func (ls *ListType) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte('[')
|
||||
if len(*ls) > 0 {
|
||||
if opt&MultiLine != 0 {
|
||||
sb.WriteString("\n ")
|
||||
}
|
||||
for i, item := range []any(*ls) {
|
||||
if i > 0 {
|
||||
if opt&MultiLine != 0 {
|
||||
sb.WriteString(",\n ")
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
}
|
||||
if s, ok := item.(string); ok {
|
||||
sb.WriteByte('"')
|
||||
sb.WriteString(s)
|
||||
sb.WriteByte('"')
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", item))
|
||||
}
|
||||
}
|
||||
if opt&MultiLine != 0 {
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ls *ListType) String() string {
|
||||
return ls.ToString(0)
|
||||
}
|
||||
|
||||
// -------- list term
|
||||
func newListTermA(args ...*term) *term {
|
||||
return newListTerm(args)
|
||||
@@ -23,7 +65,7 @@ func newListTerm(args []*term) *term {
|
||||
// -------- list func
|
||||
func evalList(ctx ExprContext, self *term) (v any, err error) {
|
||||
list, _ := self.value().([]*term)
|
||||
items := make([]any, len(list))
|
||||
items := make(ListType, len(list))
|
||||
for i, tree := range list {
|
||||
var param any
|
||||
if param, err = tree.compute(ctx); err != nil {
|
||||
@@ -32,35 +74,7 @@ func evalList(ctx ExprContext, self *term) (v any, err error) {
|
||||
items[i] = param
|
||||
}
|
||||
if err == nil {
|
||||
v = items
|
||||
v = &items
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// // -------- list term
|
||||
// func newListTerm(args []*term) *term {
|
||||
// return &term{
|
||||
// tk: *NewToken(0, 0, SymList, "[]"),
|
||||
// parent: nil,
|
||||
// children: args,
|
||||
// position: posLeaf,
|
||||
// priority: priValue,
|
||||
// evalFunc: evalList,
|
||||
// }
|
||||
// }
|
||||
|
||||
// // -------- list func
|
||||
// func evalList(ctx ExprContext, self *term) (v any, err error) {
|
||||
// items := make([]any, len(self.children))
|
||||
// for i, tree := range self.children {
|
||||
// var param any
|
||||
// if param, err = tree.compute(ctx); err != nil {
|
||||
// break
|
||||
// }
|
||||
// items[i] = param
|
||||
// }
|
||||
// if err == nil {
|
||||
// v = items
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
+16
-12
@@ -1,9 +1,11 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-length.go
|
||||
// operator-builtin.go
|
||||
package expr
|
||||
|
||||
import "io"
|
||||
|
||||
//-------- builtin term
|
||||
|
||||
func newBuiltinTerm(tk *Token) (inst *term) {
|
||||
@@ -17,16 +19,20 @@ func newBuiltinTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
|
||||
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
||||
var rightValue any
|
||||
var childValue any
|
||||
|
||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
count := 0
|
||||
if isList(rightValue) {
|
||||
list, _ := rightValue.([]any)
|
||||
for i, moduleSpec := range list {
|
||||
if isString(childValue) {
|
||||
module, _ := childValue.(string)
|
||||
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||
} else {
|
||||
var moduleSpec any
|
||||
it := NewAnyIterator(childValue)
|
||||
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
||||
if module, ok := moduleSpec.(string); ok {
|
||||
if ImportInContext(ctx, module) {
|
||||
count++
|
||||
@@ -35,15 +41,13 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
|
||||
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if isString(rightValue) {
|
||||
module, _ := rightValue.(string)
|
||||
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
v = count
|
||||
|
||||
+13
-5
@@ -31,12 +31,20 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
if sourceCtx != nil {
|
||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||
d := make(map[string]any)
|
||||
for _, key := range keys {
|
||||
d[key], _ = sourceCtx.GetVar(key)
|
||||
if formatter, ok := ctx.(Formatter); ok {
|
||||
v = formatter.ToString(0)
|
||||
} else {
|
||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||
d := make(map[string]any)
|
||||
for _, key := range keys {
|
||||
d[key], _ = sourceCtx.GetVar(key)
|
||||
}
|
||||
keys = sourceCtx.EnumFuncs(func(name string) bool { return true })
|
||||
for _, key := range keys {
|
||||
d[key], _ = sourceCtx.GetFuncInfo(key)
|
||||
}
|
||||
v = d
|
||||
}
|
||||
v = d
|
||||
} else {
|
||||
err = self.errIncompatibleType(childValue)
|
||||
}
|
||||
|
||||
+47
-30
@@ -17,50 +17,67 @@ func newDotTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
|
||||
var v int
|
||||
var indexValue any
|
||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
|
||||
if v >= 0 && v < maxValue {
|
||||
index = v
|
||||
} else if index >= -maxValue {
|
||||
index = maxValue + v
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
if err = self.checkOperands(); err != nil {
|
||||
return
|
||||
}
|
||||
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexTerm := self.children[1]
|
||||
|
||||
if isList(leftValue) {
|
||||
switch unboxedValue := leftValue.(type) {
|
||||
case []any:
|
||||
var index int
|
||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
||||
return
|
||||
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||
v = unboxedValue[index]
|
||||
}
|
||||
|
||||
list, _ := leftValue.([]any)
|
||||
if index >= 0 && index < len(list) {
|
||||
v = list[index]
|
||||
} else if index >= -len(list) {
|
||||
v = list[len(list)+index]
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
||||
}
|
||||
} else if isString(leftValue) {
|
||||
case string:
|
||||
var index int
|
||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
||||
return
|
||||
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||
v = unboxedValue[index]
|
||||
}
|
||||
|
||||
s, _ := leftValue.(string)
|
||||
if index >= 0 && index < len(s) {
|
||||
v = string(s[index])
|
||||
} else if index >= -len(s) {
|
||||
v = string(s[len(s)+index])
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
||||
}
|
||||
} else if isDict(leftValue) {
|
||||
case map[any]any:
|
||||
var ok bool
|
||||
d, _ := leftValue.(map[any]any)
|
||||
if v, ok = d[rightValue]; !ok {
|
||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||
var indexValue any
|
||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||
if v, ok = unboxedValue[indexValue]; !ok {
|
||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
case *dataCursor:
|
||||
if indexTerm.symbol() == SymIdentifier {
|
||||
opName := indexTerm.source()
|
||||
if opName == resetName {
|
||||
err = unboxedValue.Reset()
|
||||
} else if opName == cleanName {
|
||||
err = unboxedValue.Clean()
|
||||
} else {
|
||||
err = indexTerm.Errorf("iterators do not support command %q", opName)
|
||||
}
|
||||
v = err == nil
|
||||
}
|
||||
default:
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operand-fraction.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fraction struct {
|
||||
num, den int64
|
||||
}
|
||||
|
||||
func newFraction(num, den int64) *fraction {
|
||||
/* if den < 0 {
|
||||
den = -den
|
||||
num = -num
|
||||
}*/
|
||||
num, den = simplifyIntegers(num, den)
|
||||
return &fraction{num, den}
|
||||
}
|
||||
|
||||
func (f *fraction) toFloat() float64 {
|
||||
return float64(f.num) / float64(f.den)
|
||||
}
|
||||
|
||||
func (f *fraction) String() string {
|
||||
return f.ToString(0)
|
||||
}
|
||||
|
||||
func (f *fraction) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
if opt&MultiLine == 0 {
|
||||
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
|
||||
} else {
|
||||
var s, num string
|
||||
if f.num < 0 && opt&TTY == 0 {
|
||||
num = strconv.FormatInt(-f.num, 10)
|
||||
s = "-"
|
||||
} else {
|
||||
num = strconv.FormatInt(f.num, 10)
|
||||
}
|
||||
den := strconv.FormatInt(f.den, 10)
|
||||
size := max(len(num), len(den))
|
||||
if opt&TTY != 0 {
|
||||
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
|
||||
} else {
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
||||
sb.WriteByte('\n')
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(s)
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
sb.WriteString(strings.Repeat("-", size))
|
||||
sb.WriteByte('\n')
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// -------- fraction term
|
||||
func newFractionTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priFraction,
|
||||
evalFunc: evalFraction,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- eval func
|
||||
func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
||||
var numValue, denValue any
|
||||
var num, den int64
|
||||
var ok bool
|
||||
|
||||
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if num, ok = numValue.(int64); !ok {
|
||||
err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue)
|
||||
return
|
||||
}
|
||||
if den, ok = denValue.(int64); !ok {
|
||||
err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue)
|
||||
return
|
||||
}
|
||||
if den == 0 {
|
||||
err = errors.New("division by zero")
|
||||
return
|
||||
}
|
||||
|
||||
if den < 0 {
|
||||
den = -den
|
||||
num = -num
|
||||
}
|
||||
g := gcd(num, den)
|
||||
num = num / g
|
||||
den = den / g
|
||||
if den == 1 {
|
||||
v = num
|
||||
} else {
|
||||
v = &fraction{num, den}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func gcd(a, b int64) (g int64) {
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
if a < b {
|
||||
a, b = b, a
|
||||
}
|
||||
r := a % b
|
||||
for r > 0 {
|
||||
a, b = b, r
|
||||
r = a % b
|
||||
}
|
||||
g = b
|
||||
return
|
||||
}
|
||||
|
||||
func lcm(a, b int64) (l int64) {
|
||||
g := gcd(a, b)
|
||||
l = a * b / g
|
||||
return
|
||||
}
|
||||
|
||||
func sumFract(f1, f2 *fraction) (sum *fraction) {
|
||||
m := lcm(f1.den, f2.den)
|
||||
sum = newFraction(f1.num*(m/f1.den) + f2.num*(m/f2.den), m)
|
||||
return
|
||||
}
|
||||
|
||||
func mulFract(f1, f2 *fraction) (prod *fraction) {
|
||||
prod = newFraction(f1.num * f2.num, f1.den * f2.den)
|
||||
return
|
||||
}
|
||||
|
||||
func anyToFract(v any) (f *fraction, err error) {
|
||||
var ok bool
|
||||
if f, ok = v.(*fraction); !ok {
|
||||
if n, ok := v.(int64); ok {
|
||||
f = intToFraction(n)
|
||||
}
|
||||
}
|
||||
if f == nil {
|
||||
err = errExpectedGot("fract", typeFraction, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
|
||||
if f1, err = anyToFract(v1); err != nil {
|
||||
return
|
||||
}
|
||||
if f2, err = anyToFract(v2); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sumAnyFract(af1, af2 any) (sum any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
f := sumFract(f1, f2)
|
||||
if f.num == 0 {
|
||||
sum = 0
|
||||
} else {
|
||||
sum = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func subAnyFract(af1, af2 any) (sum any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
f2.num = -f2.num
|
||||
f := sumFract(f1, f2)
|
||||
if f.num == 0 {
|
||||
sum = 0
|
||||
} else {
|
||||
sum = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mulAnyFract(af1, af2 any) (prod any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
if f1.num == 0 || f2.num == 0 {
|
||||
prod = 0
|
||||
} else {
|
||||
f := &fraction{f1.num * f2.num, f1.den * f2.den}
|
||||
prod = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func divAnyFract(af1, af2 any) (quot any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
if f2.num == 0 {
|
||||
err = errors.New("division by zero")
|
||||
return
|
||||
return
|
||||
}
|
||||
if f1.num == 0 || f2.den == 0 {
|
||||
quot = 0
|
||||
} else {
|
||||
f := &fraction{f1.num * f2.den, f1.den * f2.num}
|
||||
quot = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func simplifyFraction(f *fraction) (v any) {
|
||||
f.num, f.den = simplifyIntegers(f.num, f.den)
|
||||
if f.den == 1 {
|
||||
v = f.num
|
||||
} else {
|
||||
v = &fraction{f.num, f.den}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func simplifyIntegers(num, den int64) (a, b int64) {
|
||||
if num == 0 {
|
||||
return 0, 1
|
||||
}
|
||||
if den == 0 {
|
||||
panic("fraction with denominator == 0")
|
||||
}
|
||||
if den < 0 {
|
||||
den = -den
|
||||
num = -num
|
||||
}
|
||||
g := gcd(num, den)
|
||||
a = num / g
|
||||
b = den / g
|
||||
return
|
||||
}
|
||||
|
||||
func intToFraction(n int64) *fraction {
|
||||
return &fraction{n, 1}
|
||||
}
|
||||
|
||||
func isFraction(v any) (ok bool) {
|
||||
_, ok = v.(*fraction)
|
||||
return ok
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymVertBar, newFractionTerm)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-include.go
|
||||
package expr
|
||||
|
||||
//-------- include term
|
||||
|
||||
func newIncludeTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPrefix,
|
||||
priority: priSign,
|
||||
evalFunc: evalInclude,
|
||||
}
|
||||
}
|
||||
|
||||
func evalInclude(ctx ExprContext, self *term) (v any, err error) {
|
||||
var childValue any
|
||||
|
||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
count := 0
|
||||
if isList(childValue) {
|
||||
list, _ := childValue.([]any)
|
||||
for i, filePathSpec := range list {
|
||||
if filePath, ok := filePathSpec.(string); ok {
|
||||
if v, err = EvalFile(ctx, filePath); err == nil {
|
||||
count++
|
||||
} else {
|
||||
err = self.Errorf("can't load file %q", filePath)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
err = self.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if isString(childValue) {
|
||||
filePath, _ := childValue.(string)
|
||||
v, err = EvalFile(ctx, filePath)
|
||||
} else {
|
||||
err = self.errIncompatibleType(childValue)
|
||||
}
|
||||
if err == nil {
|
||||
v = count
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymKwInclude, newIncludeTerm)
|
||||
}
|
||||
+3
-3
@@ -25,12 +25,12 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
if isList(rightValue) {
|
||||
list, _ := rightValue.([]any)
|
||||
v = len(list)
|
||||
v = int64(len(list))
|
||||
} else if isString(rightValue) {
|
||||
s, _ := rightValue.(string)
|
||||
v = len(s)
|
||||
v = int64(len(s))
|
||||
} else if it, ok := rightValue.(Iterator); ok {
|
||||
v = it.Index()
|
||||
v = int64(it.Index())
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
||||
rightInt, _ := rightValue.(int64)
|
||||
v = leftInt * rightInt
|
||||
}
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = mulAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -85,6 +87,8 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = leftInt / rightInt
|
||||
}
|
||||
}
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = divAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
|
||||
+19
-15
@@ -39,22 +39,24 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = leftInt + rightInt
|
||||
}
|
||||
} else if isList(leftValue) || isList(rightValue) {
|
||||
var leftList, rightList []any
|
||||
var leftList, rightList *ListType
|
||||
var ok bool
|
||||
if leftList, ok = leftValue.([]any); !ok {
|
||||
leftList = []any{leftValue}
|
||||
if leftList, ok = leftValue.(*ListType); !ok {
|
||||
leftList = &ListType{leftValue}
|
||||
}
|
||||
if rightList, ok = rightValue.([]any); !ok {
|
||||
rightList = []any{rightValue}
|
||||
if rightList, ok = rightValue.(*ListType); !ok {
|
||||
rightList = &ListType{rightValue}
|
||||
}
|
||||
sumList := make([]any, 0, len(leftList)+len(rightList))
|
||||
for _, item := range leftList {
|
||||
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||
for _, item := range *leftList {
|
||||
sumList = append(sumList, item)
|
||||
}
|
||||
for _, item := range rightList {
|
||||
for _, item := range *rightList {
|
||||
sumList = append(sumList, item)
|
||||
}
|
||||
v = sumList
|
||||
v = &sumList
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = sumAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -89,15 +91,17 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = leftInt - rightInt
|
||||
}
|
||||
} else if isList(leftValue) && isList(rightValue) {
|
||||
leftList, _ := leftValue.([]any)
|
||||
rightList, _ := rightValue.([]any)
|
||||
diffList := make([]any, 0, len(leftList)-len(rightList))
|
||||
for _, item := range leftList {
|
||||
if slices.Index(rightList, item) < 0 {
|
||||
leftList, _ := leftValue.(*ListType)
|
||||
rightList, _ := rightValue.(*ListType)
|
||||
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
|
||||
for _, item := range *leftList {
|
||||
if slices.Index(*rightList, item) < 0 {
|
||||
diffList = append(diffList, item)
|
||||
}
|
||||
}
|
||||
v = diffList
|
||||
v = &diffList
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = subAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
|
||||
+19
-3
@@ -132,7 +132,7 @@ func TestParser(t *testing.T) {
|
||||
/* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
||||
/* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
// /* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 114 */ {`x ?? "default"`, "default", nil},
|
||||
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||
/* 116 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
||||
@@ -145,7 +145,7 @@ func TestParser(t *testing.T) {
|
||||
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||
/* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
|
||||
// /* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
|
||||
/* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
|
||||
/* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
||||
/* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
||||
@@ -162,7 +162,23 @@ func TestParser(t *testing.T) {
|
||||
/* 140 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||
/* 141 */ {`{"key":}`, nil, errors.New(`[1:9] expected dictionary value, got "}"`)},
|
||||
/* 142 */ {`{}`, map[any]any{}, nil},
|
||||
/* 144 */ //{`3^2`, int64(9), nil},
|
||||
/* 143 */ {`1|2`, newFraction(1, 2), nil},
|
||||
/* 144 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||
/* 145 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||
/* 146 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||
/* 147 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||
/* 148 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||
/* 149 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||
/* 150 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||
/* 151 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||
/* 152 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||
/* 153 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
|
||||
/* 154 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
|
||||
/* 155 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(1), nil},
|
||||
/* 156 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
|
||||
/* 157 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
|
||||
/* 158 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
|
||||
/* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
||||
}
|
||||
check_env_expr_path := 113
|
||||
|
||||
|
||||
+61
-3
@@ -4,7 +4,10 @@
|
||||
// simple-func-store.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleFuncStore struct {
|
||||
SimpleVarStore
|
||||
@@ -18,6 +21,29 @@ type funcInfo struct {
|
||||
functor Functor
|
||||
}
|
||||
|
||||
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
var i int
|
||||
sb.WriteString("func(")
|
||||
for i = 0; i < info.minArgs; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||
}
|
||||
for ; i < info.maxArgs; i++ {
|
||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||
}
|
||||
if info.maxArgs < 0 {
|
||||
if info.minArgs > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString("...")
|
||||
}
|
||||
sb.WriteString(") {...}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (info *funcInfo) Name() string {
|
||||
return info.name
|
||||
}
|
||||
@@ -44,12 +70,44 @@ func NewSimpleFuncStore() *SimpleFuncStore {
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
svs := ctx.SimpleVarStore
|
||||
return &SimpleFuncStore{
|
||||
SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||
funcStore: CloneMap(ctx.funcStore),
|
||||
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||
}
|
||||
}
|
||||
|
||||
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("funcs: {\n")
|
||||
first := true
|
||||
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
value, _ := ctx.GetFuncInfo(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
sb.WriteString("=")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
return
|
||||
|
||||
+53
-2
@@ -4,7 +4,10 @@
|
||||
// simple-var-store.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleVarStore struct {
|
||||
varStore map[string]any
|
||||
@@ -16,9 +19,14 @@ func NewSimpleVarStore() *SimpleVarStore {
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
|
||||
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
||||
// fmt.Println("*** Cloning context ***")
|
||||
clone = &SimpleVarStore{
|
||||
varStore: CloneMap(ctx.varStore),
|
||||
varStore: ctx.cloneVars(),
|
||||
}
|
||||
return clone
|
||||
}
|
||||
@@ -29,10 +37,12 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||
ctx.varStore[varName] = value
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||
if allowedValue, ok := fromGenericAny(value); ok {
|
||||
ctx.varStore[varName] = allowedValue
|
||||
} else {
|
||||
@@ -68,3 +78,44 @@ func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, m
|
||||
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("vars: {\n")
|
||||
first := true
|
||||
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
value, _ := ctx.GetVar(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
sb.WriteString(": ")
|
||||
if f, ok := value.(Formatter); ok {
|
||||
sb.WriteString(f.ToString(0))
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
} else if _, ok = value.(map[any]any); ok {
|
||||
sb.WriteString("dict{}")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func varsCtxToString(ctx ExprContext, indent int) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, indent)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ const (
|
||||
SymBackTick // 22: '`'
|
||||
SymExclamation // 23: '!'
|
||||
SymQuestion // 24: '?'
|
||||
SymAmpersand // 25: '&&'
|
||||
SymAmpersand // 25: '&'
|
||||
SymDoubleAmpersand // 26: '&&'
|
||||
SymPercent // 27: '%'
|
||||
SymAt // 28: '@'
|
||||
@@ -64,7 +64,7 @@ const (
|
||||
SymCaret // 53: '^'
|
||||
SymDollarRound // 54: '$('
|
||||
SymOpenClosedRound // 55: '()'
|
||||
SymDoubleDollar // 56: '$$
|
||||
SymDoubleDollar // 56: '$$'
|
||||
SymChangeSign
|
||||
SymUnchangeSign
|
||||
SymIdentifier
|
||||
@@ -96,6 +96,7 @@ const (
|
||||
SymKwBut
|
||||
SymKwFunc
|
||||
SymKwBuiltin
|
||||
SymKwInclude
|
||||
SymKwNil
|
||||
)
|
||||
|
||||
@@ -108,6 +109,7 @@ func init() {
|
||||
"BUILTIN": SymKwBuiltin,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"INCLUDE": SymKwInclude,
|
||||
"NOT": SymKwNot,
|
||||
"OR": SymKwOr,
|
||||
"NIL": SymKwNil,
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
priRelational
|
||||
priSum
|
||||
priProduct
|
||||
priFraction
|
||||
priSelector
|
||||
priSign
|
||||
priFact
|
||||
@@ -154,7 +155,7 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
|
||||
if index64, ok := computedValue.(int64); ok {
|
||||
i = int(index64)
|
||||
} else {
|
||||
err = self.Errorf("%s, got %T", valueDescription, computedValue)
|
||||
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -217,9 +218,9 @@ func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err err
|
||||
return
|
||||
}
|
||||
|
||||
func (self *term) evalPrefix(ctx ExprContext) (rightValue any, err error) {
|
||||
func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
|
||||
if err = self.checkOperands(); err == nil {
|
||||
rightValue, err = self.children[0].compute(ctx)
|
||||
childValue, err = self.children[0].compute(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo >&2 "Usage: $(basename "${0}") <module-name> <func-name>..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MODULE_NAME=${1,,}
|
||||
GO_FILE="func-${MODULE_NAME//[-.]/_}.go"
|
||||
|
||||
shift
|
||||
FUNC_LIST=
|
||||
i=0
|
||||
for name; do
|
||||
if [ ${i} -gt 0 ]; then
|
||||
if [ $((i+1)) -eq $# ]; then
|
||||
FUNC_LIST+=" and "
|
||||
else
|
||||
FUNC_LIST+=", "
|
||||
fi
|
||||
fi
|
||||
FUNC_LIST+="${name}()"
|
||||
((i++))
|
||||
done
|
||||
|
||||
cat > "${GO_FILE}" <<EOF
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-${MODULE_NAME}.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
)
|
||||
|
||||
|
||||
// --- Start of function definitions
|
||||
|
||||
$(
|
||||
for name; do
|
||||
cat <<IEOF
|
||||
func ${name}Func(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
IEOF
|
||||
|
||||
done
|
||||
)
|
||||
|
||||
// --- End of function definitions
|
||||
|
||||
// Import above functions in the context
|
||||
func Import${MODULE_NAME^}Funcs(ctx ExprContext) {
|
||||
$(
|
||||
for name; do
|
||||
cat <<IEOF
|
||||
ctx.RegisterFunc("${name}", &simpleFunctor{f: ${name}Func}, 1, -1)
|
||||
IEOF
|
||||
|
||||
done
|
||||
)
|
||||
}
|
||||
|
||||
// Register the import function in the import-register.
|
||||
// That will allow to import all function of this module by the "builtin" operator."
|
||||
func init() {
|
||||
registerImport("${MODULE_NAME}", Import${name}Funcs, "The \"${MODULE_NAME}\" module implements the ${FUNC_LIST} function(s)")
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
// utils.go
|
||||
package expr
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func isString(v any) (ok bool) {
|
||||
_, ok = v.(string)
|
||||
@@ -22,7 +25,7 @@ func isFloat(v any) (ok bool) {
|
||||
}
|
||||
|
||||
func isList(v any) (ok bool) {
|
||||
_, ok = v.([]any)
|
||||
_, ok = v.(*ListType)
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -39,11 +42,25 @@ func isNumberString(v any) (ok bool) {
|
||||
return isString(v) || isNumber(v)
|
||||
}
|
||||
|
||||
func isFunctor(v any) (ok bool) {
|
||||
_, ok = v.(Functor)
|
||||
return
|
||||
}
|
||||
|
||||
func isIterator(v any) (ok bool) {
|
||||
_, ok = v.(Iterator)
|
||||
return
|
||||
}
|
||||
|
||||
func numAsFloat(v any) (f float64) {
|
||||
var ok bool
|
||||
if f, ok = v.(float64); !ok {
|
||||
i, _ := v.(int64)
|
||||
f = float64(i)
|
||||
if fract, ok := v.(*fraction); ok {
|
||||
f = fract.toFloat()
|
||||
} else {
|
||||
i, _ := v.(int64)
|
||||
f = float64(i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -136,3 +153,32 @@ func CloneMap[K comparable, V any](source map[K]V) map[K]V {
|
||||
dest := make(map[K]V, len(source))
|
||||
return CopyMap(dest, source)
|
||||
}
|
||||
|
||||
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||
// fmt.Printf("--- Clone with filter %p\n", filter)
|
||||
if filter == nil {
|
||||
return CopyMap(dest, source)
|
||||
} else {
|
||||
for k, v := range source {
|
||||
if filter(k) {
|
||||
// fmt.Printf("\tClone var %q\n", k)
|
||||
dest[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||
dest := make(map[K]V, len(source))
|
||||
return CopyFilteredMap(dest, source, filter)
|
||||
}
|
||||
|
||||
func toInt(value any, description string) (i int, err error) {
|
||||
if valueInt64, ok := value.(int64); ok {
|
||||
i = int(valueInt64)
|
||||
} else {
|
||||
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user