Compare commits

...

14 Commits

Author SHA1 Message Date
bf8f1a175f modules are now represented by a struct that holds import-function, module description and importa status.
It is also available the new function IterateModules() to explore all modules.
2024-04-28 05:41:13 +02:00
6dd8283308 function-register.go module-register.go 2024-04-28 04:52:02 +02:00
06ab303b9e accessing to the Reset() and Clean()functions of an iterator is now done by identifiers, i.e. reset, not by string, i.e. "reset". 2024-04-28 04:44:19 +02:00
2ccbdb2254 Fixed Clean() and added error message when calling optional missing functions 2024-04-28 04:41:43 +02:00
c5fca70cfc operator-length.go: Fix: the returned value was int, instead of int64 2024-04-27 22:37:56 +02:00
895778f236 operator-dot.go: '*dataCursor' case added; to be enhanced 2024-04-27 22:34:23 +02:00
81c85afbea operand-iterator.go: accepts two new optional functions 'reset' and 'clean' from data-source 2024-04-27 22:32:57 +02:00
354cb79580 data-cursor.go: added Reset() e Clean() functions 2024-04-27 22:31:14 +02:00
327bffa01f symbol.go: typo 2024-04-27 22:30:18 +02:00
c99be491df operator-dot.go: refactoring 2024-04-27 14:44:52 +02:00
60effe8f1b new prefix operator 'include' 2024-04-27 09:48:45 +02:00
824b9382be termo.go: changed the name of a variable 2024-04-27 09:48:05 +02:00
9ce6b7255b corrected a comment 2024-04-27 09:47:24 +02:00
9dbf472630 helpers.go: two new functions: EvalStream() and EvalFile() 2024-04-27 09:46:03 +02:00
15 changed files with 259 additions and 92 deletions

View File

@ -5,11 +5,14 @@
package expr package expr
import ( import (
"errors"
"io" "io"
) )
const ( const (
initName = "init" initName = "init"
cleanName = "clean"
resetName = "reset"
nextName = "next" nextName = "next"
currentName = "current" currentName = "current"
) )
@ -20,6 +23,8 @@ type dataCursor struct {
index int index int
resource any resource any
nextFunc Functor nextFunc Functor
cleanFunc Functor
resetFunc Functor
currentFunc Functor currentFunc Functor
} }
@ -35,6 +40,30 @@ func (dc *dataCursor) String() string {
return "$(...)" return "$(...)"
} }
func (dc *dataCursor) Reset() (err error) {
if dc.resetFunc != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{}); err == nil {
dc.index = -1
}
exportObjects(dc.ctx, ctx)
} else {
err = errors.New("no 'reset' function defined in the data-source")
}
return
}
func (dc *dataCursor) Clean() (err error) {
if dc.cleanFunc != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{})
exportObjects(dc.ctx, ctx)
} 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 func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx) ctx := cloneContext(dc.ctx)
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil { if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {

View File

@ -55,5 +55,5 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
} }
func init() { func init() {
registerImport("builtins", ImportBuiltinsFuncs) registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
} }

View File

@ -142,5 +142,5 @@ func ImportImportFuncs(ctx ExprContext) {
} }
func init() { func init() {
registerImport("import", ImportImportFuncs) registerImport("import", ImportImportFuncs, "Functions import() and include()")
} }

View File

@ -115,5 +115,5 @@ func ImportMathFuncs(ctx ExprContext) {
} }
func init() { func init() {
registerImport("math.arith", ImportMathFuncs) registerImport("math.arith", ImportMathFuncs, "Function add() and mul()")
} }

View File

@ -164,5 +164,5 @@ func ImportOsFuncs(ctx ExprContext) {
} }
func init() { func init() {
registerImport("os", ImportOsFuncs) registerImport("os.file", ImportOsFuncs, "Operating system file functions")
} }

View File

@ -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))
}
}

View File

@ -6,6 +6,8 @@ package expr
import ( import (
"fmt" "fmt"
"io"
"os"
"strings" "strings"
) )
@ -59,3 +61,24 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
} }
return 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
}

74
module-register.go Normal file
View File

@ -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)
}
}

View File

@ -54,14 +54,23 @@ func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err
if dictAny, ok := value.(map[any]any); ok { if dictAny, ok := value.(map[any]any); ok {
ds = make(map[string]Functor) ds = make(map[string]Functor)
for _, k := range []string{initName, currentName, nextName} { // required functions
for _, k := range []string{currentName, nextName} {
if item, exists := dictAny[k]; exists && item != nil { if item, exists := dictAny[k]; exists && item != nil {
if functor, ok := item.(*funcDefFunctor); ok { if functor, ok := item.(*funcDefFunctor); ok {
ds[k] = functor ds[k] = functor
} }
} else if k != initName { } else {
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k) err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
break 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 { } else {
@ -98,6 +107,9 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
dc.nextFunc, _ = ds[nextName] dc.nextFunc, _ = ds[nextName]
dc.currentFunc, _ = ds[currentName] dc.currentFunc, _ = ds[currentName]
dc.cleanFunc, _ = ds[cleanName]
dc.resetFunc, _ = ds[resetName]
v = dc v = dc
return return

View File

@ -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.
// operator-length.go // operator-builtin.go
package expr package expr
//-------- builtin term //-------- builtin term

View File

@ -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) { func evalDot(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any 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 return
} }
indexTerm := self.children[1] indexTerm := self.children[1]
if isList(leftValue) { switch unboxedValue := leftValue.(type) {
case []any:
var index int var index int
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil { if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
return v = unboxedValue[index]
} }
case string:
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) {
var index int var index int
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil { if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
return v = unboxedValue[index]
} }
case map[any]any:
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) {
var ok bool var ok bool
d, _ := leftValue.(map[any]any) var indexValue any
if v, ok = d[rightValue]; !ok { 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) err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
} }
}
case *dataCursor:
if indexTerm.symbol() == SymIdentifier {
opName := indexTerm.source()
if opName == resetName {
err = unboxedValue.Reset()
} else if opName == cleanName {
err = unboxedValue.Clean()
} else { } else {
err = indexTerm.Errorf("iterators do not support command %q", opName)
}
v = err == nil
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
return return

57
operator-include.go Normal file
View File

@ -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)
}

View File

@ -25,12 +25,12 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
if isList(rightValue) { if isList(rightValue) {
list, _ := rightValue.([]any) list, _ := rightValue.([]any)
v = len(list) v = int64(len(list))
} else if isString(rightValue) { } else if isString(rightValue) {
s, _ := rightValue.(string) s, _ := rightValue.(string)
v = len(s) v = int64(len(s))
} else if it, ok := rightValue.(Iterator); ok { } else if it, ok := rightValue.(Iterator); ok {
v = it.Index() v = int64(it.Index())
} else { } else {
err = self.errIncompatibleType(rightValue) err = self.errIncompatibleType(rightValue)
} }

View File

@ -33,7 +33,7 @@ const (
SymBackTick // 22: '`' SymBackTick // 22: '`'
SymExclamation // 23: '!' SymExclamation // 23: '!'
SymQuestion // 24: '?' SymQuestion // 24: '?'
SymAmpersand // 25: '&&' SymAmpersand // 25: '&'
SymDoubleAmpersand // 26: '&&' SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%' SymPercent // 27: '%'
SymAt // 28: '@' SymAt // 28: '@'
@ -64,7 +64,7 @@ const (
SymCaret // 53: '^' SymCaret // 53: '^'
SymDollarRound // 54: '$(' SymDollarRound // 54: '$('
SymOpenClosedRound // 55: '()' SymOpenClosedRound // 55: '()'
SymDoubleDollar // 56: '$$ SymDoubleDollar // 56: '$$'
SymChangeSign SymChangeSign
SymUnchangeSign SymUnchangeSign
SymIdentifier SymIdentifier
@ -96,6 +96,7 @@ const (
SymKwBut SymKwBut
SymKwFunc SymKwFunc
SymKwBuiltin SymKwBuiltin
SymKwInclude
SymKwNil SymKwNil
) )
@ -108,6 +109,7 @@ func init() {
"BUILTIN": SymKwBuiltin, "BUILTIN": SymKwBuiltin,
"BUT": SymKwBut, "BUT": SymKwBut,
"FUNC": SymKwFunc, "FUNC": SymKwFunc,
"INCLUDE": SymKwInclude,
"NOT": SymKwNot, "NOT": SymKwNot,
"OR": SymKwOr, "OR": SymKwOr,
"NIL": SymKwNil, "NIL": SymKwNil,

View File

@ -217,9 +217,9 @@ func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err err
return 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 { if err = self.checkOperands(); err == nil {
rightValue, err = self.children[0].compute(ctx) childValue, err = self.children[0].compute(ctx)
} }
return return
} }