Compare commits
	
		
			No commits in common. "bf8f1a175f0c21b56268fef38e3969d60f978b62" and "723976b37e65e1f6807f5e6496d6d492bacf9133" have entirely different histories.
		
	
	
		
			bf8f1a175f
			...
			723976b37e
		
	
		
| @ -5,14 +5,11 @@ | ||||
| package expr | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	initName    = "init" | ||||
| 	cleanName   = "clean" | ||||
| 	resetName   = "reset" | ||||
| 	nextName    = "next" | ||||
| 	currentName = "current" | ||||
| ) | ||||
| @ -23,8 +20,6 @@ type dataCursor struct { | ||||
| 	index       int | ||||
| 	resource    any | ||||
| 	nextFunc    Functor | ||||
| 	cleanFunc   Functor | ||||
| 	resetFunc   Functor | ||||
| 	currentFunc Functor | ||||
| } | ||||
| 
 | ||||
| @ -40,30 +35,6 @@ func (dc *dataCursor) String() string { | ||||
| 	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
 | ||||
| 	ctx := cloneContext(dc.ctx) | ||||
| 	if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil { | ||||
|  | ||||
| @ -55,5 +55,5 @@ func ImportBuiltinsFuncs(ctx ExprContext) { | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.") | ||||
| 	registerImport("builtins", ImportBuiltinsFuncs) | ||||
| } | ||||
| @ -142,5 +142,5 @@ func ImportImportFuncs(ctx ExprContext) { | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	registerImport("import", ImportImportFuncs, "Functions import() and include()") | ||||
| 	registerImport("import", ImportImportFuncs) | ||||
| } | ||||
|  | ||||
| @ -115,5 +115,5 @@ func ImportMathFuncs(ctx ExprContext) { | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	registerImport("math.arith", ImportMathFuncs, "Function add() and mul()") | ||||
| 	registerImport("math.arith", ImportMathFuncs) | ||||
| } | ||||
|  | ||||
| @ -164,5 +164,5 @@ func ImportOsFuncs(ctx ExprContext) { | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	registerImport("os.file", ImportOsFuncs, "Operating system file functions") | ||||
| 	registerImport("os", ImportOsFuncs) | ||||
| } | ||||
|  | ||||
							
								
								
									
										47
									
								
								function-register.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								function-register.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // 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
									
								
								helpers.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								helpers.go
									
									
									
									
									
								
							| @ -6,8 +6,6 @@ package expr | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| @ -61,24 +59,3 @@ 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 | ||||
| } | ||||
|  | ||||
| @ -1,74 +0,0 @@ | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
| @ -54,23 +54,14 @@ func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err | ||||
| 
 | ||||
| 	if dictAny, ok := value.(map[any]any); ok { | ||||
| 		ds = make(map[string]Functor) | ||||
| 		// required functions
 | ||||
| 		for _, k := range []string{currentName, nextName} { | ||||
| 		for _, k := range []string{initName, currentName, nextName} { | ||||
| 			if item, exists := dictAny[k]; exists && item != nil { | ||||
| 				if functor, ok := item.(*funcDefFunctor); ok { | ||||
| 					ds[k] = functor | ||||
| 				} | ||||
| 			} else { | ||||
| 			} else if k != initName { | ||||
| 				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 | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| @ -107,9 +98,6 @@ 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 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | ||||
| // All rights reserved.
 | ||||
| 
 | ||||
| // operator-builtin.go
 | ||||
| // operator-length.go
 | ||||
| package expr | ||||
| 
 | ||||
| //-------- builtin term
 | ||||
|  | ||||
| @ -17,67 +17,50 @@ 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 err = self.checkOperands(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if leftValue, err = self.children[0].compute(ctx); err != nil { | ||||
| 	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	indexTerm := self.children[1] | ||||
| 
 | ||||
| 	switch unboxedValue := leftValue.(type) { | ||||
| 	case []any: | ||||
| 	if isList(leftValue) { | ||||
| 		var index int | ||||
| 		if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil { | ||||
| 			v = unboxedValue[index] | ||||
| 		if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	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 | ||||
| 		if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil { | ||||
| 			v = unboxedValue[index] | ||||
| 		if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	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 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) | ||||
| 			} | ||||
| 		d, _ := leftValue.(map[any]any) | ||||
| 		if v, ok = d[rightValue]; !ok { | ||||
| 			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 { | ||||
| 				err = indexTerm.Errorf("iterators do not support command %q", opName) | ||||
| 			} | ||||
| 			v = err == nil | ||||
| 		} | ||||
| 	default: | ||||
| 	} else { | ||||
| 		err = self.errIncompatibleTypes(leftValue, rightValue) | ||||
| 	} | ||||
| 	return | ||||
|  | ||||
| @ -1,57 +0,0 @@ | ||||
| // 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) | ||||
| } | ||||
| @ -25,12 +25,12 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) { | ||||
| 
 | ||||
| 	if isList(rightValue) { | ||||
| 		list, _ := rightValue.([]any) | ||||
| 		v = int64(len(list)) | ||||
| 		v = len(list) | ||||
| 	} else if isString(rightValue) { | ||||
| 		s, _ := rightValue.(string) | ||||
| 		v = int64(len(s)) | ||||
| 		v = len(s) | ||||
| 	} else if it, ok := rightValue.(Iterator); ok { | ||||
| 		v = int64(it.Index()) | ||||
| 		v = it.Index() | ||||
| 	} else { | ||||
| 		err = self.errIncompatibleType(rightValue) | ||||
| 	} | ||||
|  | ||||
| @ -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,7 +96,6 @@ const ( | ||||
| 	SymKwBut | ||||
| 	SymKwFunc | ||||
| 	SymKwBuiltin | ||||
| 	SymKwInclude | ||||
| 	SymKwNil | ||||
| ) | ||||
| 
 | ||||
| @ -109,7 +108,6 @@ func init() { | ||||
| 		"BUILTIN": SymKwBuiltin, | ||||
| 		"BUT":     SymKwBut, | ||||
| 		"FUNC":    SymKwFunc, | ||||
| 		"INCLUDE": SymKwInclude, | ||||
| 		"NOT":     SymKwNot, | ||||
| 		"OR":      SymKwOr, | ||||
| 		"NIL":     SymKwNil, | ||||
|  | ||||
							
								
								
									
										4
									
								
								term.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								term.go
									
									
									
									
									
								
							| @ -217,9 +217,9 @@ func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err err | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) { | ||||
| func (self *term) evalPrefix(ctx ExprContext) (rightValue any, err error) { | ||||
| 	if err = self.checkOperands(); err == nil { | ||||
| 		childValue, err = self.children[0].compute(ctx) | ||||
| 		rightValue, err = self.children[0].compute(ctx) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user