Compare commits

...

12 Commits

38 changed files with 1886 additions and 441 deletions
+21 -15
View File
@@ -255,18 +255,25 @@ func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any
return
}
// func unsetFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
// var varName string
// var ok bool
func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var ord int
// if varName, ok = args[kern.ParamName].(string); !ok {
// return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
// } else {
// ctx.GetParent().DeleteVar(varName)
// result = nil
// }
// return
// }
if n, ok := args[kern.ParamValue].(byte); ok {
ord = int(n)
} else if n, ok := args[kern.ParamValue].(int64); ok {
ord = int(n)
} else if n, ok := args[kern.ParamValue].(int); ok {
ord = n
} else {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
}
if ord < 0 || ord > 255 {
err = kern.ErrFuncInvalidArg(name, fmt.Sprintf("character code must be in range 0-255, got %d", ord))
} else {
result = string(rune(ord))
}
return
}
//// import
@@ -308,10 +315,9 @@ func ImportBuiltinsFuncs(ctx kern.ExprContext) {
kern.NewFuncParam(kern.ParamValue),
})
// ctx.RegisterFunc("unset", kern.NewGolangFunctor(unsetFunc), kern.TypeAny, []kern.ExprFuncParam{
// kern.NewFuncParam(kern.ParamName),
// kern.NewFuncParam(kern.ParamValue),
// })
ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
})
}
func init() {
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const fileByteIteratorType = "fileByteIterator"
type fileFileByteIterator struct {
fileIterBase
b byte
}
func newFileByteIterator(r *file.Reader, autoClose bool) *fileFileByteIterator {
return &fileFileByteIterator{
fileIterBase: fileIterBase{reader: r, index: -1, count: 0, autoClose: autoClose},
b: 0}
}
func (it *fileFileByteIterator) TypeName() string {
return fileByteIteratorType
}
func (it *fileFileByteIterator) String() string {
return it.repr(fileByteIteratorType)
}
func (it *fileFileByteIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.b, err = it.reader.ReadByte(); err == nil {
it.increment()
item = it.b
} else if it.autoClose {
it.Clean()
}
return
}
func (it *fileFileByteIterator) Current() (item any, err error) {
item = it.b
return
}
func (it *fileFileByteIterator) Reset() (err error) {
if err = it.reader.Reset(); err == nil {
it.reset()
it.b = 0
}
return
}
func (it *fileFileByteIterator) Clean() (err error) {
if it.reader.Valid() {
if err = it.reader.GetFile().Close(); err == nil {
it.reader = nil
}
}
it.reset()
it.b = 0
return
}
func (it *fileFileByteIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func fileByteIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle *file.Reader
var invalidFileHandle any
var autoClose bool
if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil {
if handle != nil {
result = newFileByteIterator(handle, autoClose)
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const fileLineIteratorType = "fileLineIterator"
type fileFileLineIterator struct {
fileIterBase
line string
}
func newFileLineIterator(r *file.Reader, autoClose bool) *fileFileLineIterator {
return &fileFileLineIterator{
fileIterBase: fileIterBase{reader: r, index: -1, count: 0, autoClose: autoClose},
line: "",
}
}
func (it *fileFileLineIterator) TypeName() string {
return fileLineIteratorType
}
func (it *fileFileLineIterator) String() string {
return it.repr(fileLineIteratorType)
}
func (it *fileFileLineIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.line, err = it.reader.ReadString('\n'); err == nil {
it.increment()
item = it.line[0 : len(it.line)-1]
} else if it.autoClose {
it.Clean()
}
return
}
func (it *fileFileLineIterator) Current() (item any, err error) {
if len(it.line) > 0 {
item = it.line[0 : len(it.line)-1]
}
return
}
func (it *fileFileLineIterator) Reset() (err error) {
if err = it.reader.Reset(); err == nil {
it.reset()
it.line = ""
}
return
}
func (it *fileFileLineIterator) Clean() (err error) {
if it.reader != nil {
if err = it.reader.Close(); err == nil {
it.reader = nil
}
}
it.reset()
it.line = ""
return
}
func (it *fileFileLineIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func fileLineIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle *file.Reader
var invalidFileHandle any
var autoClose bool
if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil {
if handle != nil {
result = newFileLineIterator(handle, autoClose)
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
+23 -103
View File
@@ -6,146 +6,66 @@ package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
const paramHandleOrPath = "handle-or-path"
const fileReadTextIteratorType = "fileReadTextIterator"
type fileReadTextIterator struct {
osReader *osReader
type fileIterBase struct {
reader *file.Reader
index int64
count int64
line string
autoClose bool
}
func newReadTextIterator(r *osReader, autoClose bool) *fileReadTextIterator {
return &fileReadTextIterator{osReader: r, index: -1, autoClose: autoClose}
}
func (it *fileReadTextIterator) TypeName() string {
return fileReadTextIteratorType
}
func (it *fileReadTextIterator) String() string {
if it.osReader != nil && it.osReader.fh != nil {
return fmt.Sprintf("$(%s@%q)", fileReadTextIteratorType, it.osReader.fh.Name())
}
return fmt.Sprintf("$(%s@<nil>)", fileReadTextIteratorType)
}
func (it *fileReadTextIterator) Count() int64 {
func (it *fileIterBase) Count() int64 {
return it.count
}
func (it *fileReadTextIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.osReader.fh != nil {
if it.line, err = it.osReader.reader.ReadString('\n'); err == nil {
it.index++
it.count++
item = it.line[0 : len(it.line)-1]
} else if it.autoClose {
it.Clean()
}
}
return
}
func (it *fileReadTextIterator) Current() (item any, err error) {
if len(it.line) > 0 {
item = it.line[0 : len(it.line)-1]
}
return
}
func (it *fileReadTextIterator) Index() int64 {
func (it *fileIterBase) Index() int64 {
return it.index
}
func (it *fileReadTextIterator) Reset() (err error) {
if _, err = it.osReader.fh.Seek(0, io.SeekStart); err == nil {
it.index = -1
it.count = 0
it.line = ""
}
return
}
func (it *fileReadTextIterator) HasOperation(name string) bool {
func (it *fileIterBase) HasOperation(name string) bool {
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
}
func (it *fileReadTextIterator) Clean() (err error) {
if it.osReader.fh != nil {
if err = it.osReader.fh.Close(); err == nil {
it.osReader = nil
}
}
return nil
func (it *fileIterBase) reset() {
it.index = -1
it.count = 0
}
func (it *fileReadTextIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
func (it *fileIterBase) increment() {
it.index++
it.count++
}
func fileReadIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle *osReader
var invalidFileHandle any
var ok, autoClose bool
func (it *fileIterBase) repr(typeName string) string {
if it.reader.Valid() {
return fmt.Sprintf("$(%s@%q)", typeName, it.reader.GetName())
}
return fmt.Sprintf("$(%s@<nil>)", typeName)
}
result = nil
if handle, ok = args[paramHandleOrPath].(*osReader); !ok {
func initFileHandle(ctx kern.ExprContext, name string, args map[string]any) (handle *file.Reader, invalidFileHandle any, autoClose bool, err error) {
var ok bool
if handle, ok = args[paramHandleOrPath].(*file.Reader); !ok {
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
var handleAny any
if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil {
return
}
if handleAny != nil {
handle = handleAny.(*osReader)
handle = handleAny.(*file.Reader)
autoClose = true
}
} else {
invalidFileHandle = args[paramHandleOrPath]
}
}
if handle != nil {
result = newReadTextIterator(handle, autoClose)
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
// func ImportOsIterFuncs(ctx ExprContext) {
// ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
// NewFuncParam(paramHandleOrPath),
// })
// }
// func init() {
// RegisterBuiltinModule("os.file", ImportOsIterFuncs, "Operating system file iterator functions")
// }
+42 -72
View File
@@ -5,11 +5,10 @@
package expr
import (
"bufio"
"fmt"
"io"
"os"
"git.portale-stac.it/go-pkg/expr/file"
"git.portale-stac.it/go-pkg/expr/kern"
)
@@ -17,44 +16,6 @@ const (
osLimitCh = "limitCh"
)
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) TypeName() string {
return "osWriter"
}
func (h *osWriter) String() string {
return "writer"
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) TypeName() string {
return "osReader"
}
func (h *osReader) String() string {
return "reader"
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func errMissingFilePath(funcName string) error {
return fmt.Errorf("%s(): missing or invalid file path", funcName)
}
@@ -67,24 +28,26 @@ func errInvalidFileHandle(funcName string, v any) error {
}
}
func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
// var fh *os.File
// if fh, err = os.Open(filePath); err == nil {
// result = file.NewReader(fh)
// }
result, err = file.OpenReader(filePath)
} else {
err = errMissingFilePath(name)
}
return
}
func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
}
// var fh *os.File
// if fh, err = os.Create(filePath); err == nil {
// result = file.NewWriter(fh)
// }
result, err = file.CreateWriter(filePath)
} else {
err = errMissingFilePath(name)
}
@@ -93,10 +56,11 @@ func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (resul
func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
// var fh *os.File
// if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
// result = file.NewWriter(fh)
// }
result, err = file.AppendWriter(filePath)
} else {
err = errMissingFilePath(name)
}
@@ -104,18 +68,18 @@ func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (res
}
func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var handle file.Handle
var invalidFileHandle any
var ok bool
if handle, ok = args[kern.ParamHandle].(osHandle); !ok {
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush()
if fh := handle.GetFile(); fh != nil {
if w, ok := handle.(*file.Writer); ok {
err = w.Flush()
}
if err == nil {
@@ -131,19 +95,20 @@ func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (resu
}
func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var handle file.Handle
var invalidFileHandle any
var ok bool
if handle, ok = args[kern.ParamHandle].(osHandle); !ok {
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if w, ok := handle.(*osWriter); ok {
if w, ok := handle.(*file.Writer); ok {
if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any)
result, err = fmt.Fprint(w.writer, argv...)
// result, err = fmt.Fprint(w.writer, argv...)
result, err = w.Write(argv...)
}
} else {
invalidFileHandle = handle
@@ -157,24 +122,24 @@ func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (
}
func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var handle file.Handle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil {
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
if r, ok := handle.(*file.Reader); ok {
var limit byte = '\n'
var v string
if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
limit = s[0]
}
v, err = r.reader.ReadString(limit)
v, err = r.ReadString(limit)
if err == io.EOF {
err = nil
}
@@ -197,19 +162,19 @@ func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (r
}
func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var handle file.Handle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil {
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
if r, ok := handle.(*file.Reader); ok {
var b []byte
b, err = io.ReadAll(r.reader)
b, err = r.ReadAll()
result = string(b)
} else {
invalidFileHandle = handle
@@ -253,9 +218,14 @@ func ImportOsFuncs(ctx kern.ExprContext) {
kern.NewFuncParam(kern.ParamHandle),
})
ctx.RegisterFunc("fileReadIterator", kern.NewGolangFunctor(fileReadIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
ctx.RegisterFunc("fileLineIterator", kern.NewGolangFunctor(fileLineIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
kern.NewFuncParam(paramHandleOrPath),
})
ctx.RegisterFunc("fileByteIterator", kern.NewGolangFunctor(fileByteIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
kern.NewFuncParam(paramHandleOrPath),
})
}
func init() {
+87 -38
View File
@@ -34,7 +34,7 @@ Expressions calculator
toc::[]
#TODO: Work in progress (last update on 2026/04/21, 6:49 p.m.)#
#TODO: Work in progress (last update on 2026/05/08)#
== Expr
_Expr_ is a GO package that can analyze, interpret and calculate expressions.
@@ -698,9 +698,9 @@ The value on the left side of [blue]`=` must be a variable identifier or an expr
=== Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Selector literal Syntax
.Selector literal syntax
====
_selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
*_selector-operator_* = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
_selector-case_ = [_match-list_] _case-value_ +
_match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
_item_ = _expression_ +
@@ -737,19 +737,38 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
`>>>` [blue]`10 ? {"a"} : {"b"}` +
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
==== Triple special case of the selector operator
If the _select-expression_ is a boolean expression, the selector operator can be used as a sort of _if-then-else_ statement. In this case, the first case is evaluated if the _select-expression_ is true, and the second case is evaluated if the _select-expression_ is false. In this special case, the _match-list_ of both cases must be empty.
.Example
`>>>` [blue]`(true) ? {"T"}: {"F"}` +
[green]`T` +
`>>>` [blue]`(2 > 1) ? {"a"} : {"b"}` +
[green]`a` +
`>>>` [blue]`(2 < 1) ? {"a"} : {"b"}` +
[green]`b`
[WARNING]
====
The triple special case of the selector operator is very useful, but it only works with boolean expressions.
.Example of confusion
`>>>` [blue]`int(true) ? {"T"}: {"F"}` +
[green]`F`
====
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operatand can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
The [blue]`??` operator do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the variable on the left side.
The third one, [blue]`?!`, is the alternate operator. If the variable on the left size is not defined, it returns [blue]_nil_. Otherwise it returns the result of the expressione on the right side.
IMPORTANT: If the left variable is NOT defined, the right expression is not evaluated at all.
IMPORTANT: If the variable [blue]`?!` is NOT defined, the expression is not evaluated at all.
.Examples
`>>>` [blue]`var ?? (1+2)` +
@@ -836,8 +855,14 @@ The table below shows all supported operators by decreasing priorities.
.2+|*INSERT*| [blue]`+>` | _Infix_ | _Prepend_ | _any_ `+>` _list_ -> _list_
| [blue]`<+` | _Infix_ | _Append_ | _list_ `<+` _any_ -> _list_
.2+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
4+| _See also the table of special allocation operators below_
4+| _See also the table of special assignment operators below_
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
.6+|*ITER-OP*| [blue]`digest` | _Infix_ | _Item-digesting_ | _iterable_ `digest` _expr_ -> _any_
| [blue]`filter` | _Infix_ | _Item-filtering_ | _iterable_ `filter` _expr_ -> _list_
| [blue]`groupby` | _Infix_ | _Dict-grouping_ | _iterable_ `groupby` _key-expr_ -> _dict_
| [blue]`join` | _Infix_ | _Item-joining_ | _iterable_ `join` _iterable_ -> _list_
| [blue]`map` | _Infix_ | _Item-mapping_ | _iterable_ `map` _-expr_ -> _list_
4+| _See iterators section for examples_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|===
@@ -921,12 +946,12 @@ _param-name_ = _identifier_
[green]`fib(n):any{}`
`>>>` [gray]_// Required and optional parameters_ +
`>>>` [blue]`measure = func(value, unit="meter"){ value + " " + unit + (value > 1) ? [true] {"s"} :: {""}}` +
`>>>` [blue]`measure = func(value, unit="meter"){ value + " " + unit + (value > 1) ? {"s"} :: {""}}` +
[green]`measure(value, unit="meter"):any{}`
=== _Golang_ function definition
Description of how to define Golang functions and how to bind them to _Expr_ are topics covered in another document that I'll write, one day, maybe.
Description of how to define Golang functions and how to bind them to _Expr_ are topics covered in another documents that I'll write, one day, maybe.
=== Function calls
To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.
@@ -1010,7 +1035,7 @@ Clone variables are normal local variables. The only diffence will appear when t
.Example
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
[green]`f():any{}` +
`>>>` [blue]`f()` [gray]_// The multi-expression (two) in f() is calculated and the last result is returned_ +
`>>>` [blue]`f()` [gray]_// The multi-expression (two expressions) in f() is calculated and the last result is returned_ +
[green]`5` +
`>>>` [blue]`x` [gray]_// The `x` variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the `@x` variable, local to f() after its termnation._ +
[green]`3`
@@ -1048,7 +1073,7 @@ Builtins activation is done by using the [blue]`BUILTIN` operator. All modules e
.Builtin activation syntax
====
*_builtin-activation_* = [blue]`BUILTIN` (_builtin-name_ | _list-of-builtin-names_) +
*_builtin-activation_* = [blue]`BUILTIN` (_builtin-name_ | _list-of-builtin-names_ | **"*"**) +
_builtin-name_ = _string_ +
_list-of-builtin-names_ = **[** _string_ { "**,**" _string_ } **]**
====
@@ -1086,9 +1111,9 @@ The "base" builtin module provides functions for type checking and type conversi
* <<_fract,fract()>>
.Other functions
* <<_char,char()>>
* <<_eval,eval()>>
* <<_set,set()>>
* <<_unset,unset()>>
* <<_var,var()>>
@@ -1100,7 +1125,9 @@ Returns _true_ if the value type of _<expr>_ is boolean, false otherwise.
`>>>` [blue]`isBool(true)` +
[green]`true` +
`>>>` [blue]`isBool(3==2)` +
[green]`true`
[green]`true` +
`>>>` [blue]`isBool(3 + 2)` +
[green]`false`
===== isDict()
Syntax: `isDict(<expr>) -> bool` +
@@ -1178,7 +1205,7 @@ Returns _true_ if the value type of _<expr>_ is fraction or int, false otherwise
===== isString()
Syntax: `isString(<expr>) -> bool` +
Returns a boolean value , false otherwise.
Returns _true_ if the value type of _<expr>_ is string, false otherwise.
.Examples
`>>>` [blue]`isString("ciao")` +
@@ -1190,7 +1217,7 @@ Returns a boolean value , false otherwise.
===== bool()
Syntax: `bool(<expr>) -> bool` +
Returns a _boolean_ value consisent to the value of the expression.
Returns a _boolean_ value consisent with the value of the expression.
.Examples
`>>>` [blue]`bool(1)` +
@@ -1295,6 +1322,16 @@ Returns a _fraction_ value consistent with the value of the expression.
`>>>` [blue]`fract(true)` +
[green]`1:1`
===== char()
Syntax: `char(<intexpr>) -> string` +
Returns the character whose ASCII (soon Unicode too) code point is specified by the integer expression.
.Examples
`>>>` [blue]`char(65)` +
[green]`"A"` +
`>>>` [blue]`char(97)` +
[green]`"a"`
===== eval()
Syntax: `eval(<string-expr>) -> any` +
Computes and returns the value of the [.underline]#string# expression.
@@ -1308,12 +1345,12 @@ Syntax: +
`{4sp}var(<string-expr>, <expr>) -> any` +
`{4sp}var(<string-expr>) -> any`
This function allows you to define variables whose names must include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.
This function allows you to define variables whose names can include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.
.Examples
`>>>` [blue]`var("$x", 3+9)` +
[green]`12` +
`>>>` [blue]`var("$x")` +
`>>>` [blue]`var("$"+"x")` +
[green]`12` +
`>>>` [blue]`var("gain%", var("$x"))` +
[green]`12` +
@@ -1334,26 +1371,17 @@ It is equivalent to the first form of the var() function, but it is more explici
`>>>` [blue]`var("$x")` +
[green]`100` +
===== unset()
Syntax: +
`{4sp}unset(<string-expr>) -> any`
This function allows you to unset a variable whose name can include special characters. The parameter is the name of the variable to unset.
.Examples
`>>>` [blue]`unset("$x")` +
[green]`nil` +
`>>>` [blue]`var("$x")` +
[red]`Eval Error: var(): unknown variable "$x"`
==== Module "fmt"
#to-do#
===== print()
===== println()
==== Module "import"
Module actiovation: +
Module activation: +
`{4sp}BUILTIN "import"`
===== _import()_
Syntax: +
@@ -1364,6 +1392,8 @@ Loads the multi-expression contained in the specified source and returns its val
===== _importAll()_
==== Module "iterator"
Module activation: +
`{4sp}BUILTIN "iterator"`
===== run()
Syntax: +
@@ -1372,6 +1402,9 @@ Syntax: +
Iterates over the specified iterator and applies the specified operator to the current value of the iterator.
==== Module "math.arith"
Module activation: +
`{4sp}BUILTIN "math.arith"`
Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.
* <<_add,add()>>
@@ -1421,13 +1454,12 @@ Same as <<_add,add()>> but returns the product of the values of the parameters.
[green]`24`
==== Module "os.file"
Module activation: +
`{4sp}BUILTIN "os.file"`
The "os.file" module provides functions for working with files.
Activation: +
`{4sp}builtin "os.file"`
Currently available functions:
.File related functions
* <<_fileOpen,fileOpen()>>
* <<_fileAppend,fileAppend()>>
* <<_fileCreate,fileCreate()>>
@@ -1436,6 +1468,10 @@ Currently available functions:
* <<_fileReadText,fileReadText()>>
* <<_fileReadTextAll,fileReadTextAll()>>
.Iterator functions for files
* <<_fileByteIterator,fileByteIterator()>>
* <<_fileLineIterator,fileLineIterator()>>
More functions will be added in the future.
---
@@ -1447,6 +1483,10 @@ Syntax: +
Returns a file handle for the specified file path. The file is opened in read-write mode. If the file does not exist, it is created.
===== fileAppend()
Syntax: +
`{4sp}fileAppend(<file-path>) -> any`
Like <<_fileCreate,fileCreate()>> but write operations happen at the end of the file.
===== fileCreate()
Syntax: +
@@ -1455,21 +1495,30 @@ Syntax: +
Creates or truncates the named _<file-path>_. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0o666 (before umask). The associated file descriptor has mode [O_RDWR]. The directory containing the file must already exist.
===== fileClose()
#to-do#
===== fileWriteText()
#to-do#
===== fileReadText()
#to-do#
===== fileReadTextAll()
#to-do#
===== fileByteIterator()
#to-do#
===== fileLineIterator()
#to-do#
==== Module "string"
Module activation: +
`{4sp}BUILTIN "string"`
This module provides functions for working with strings.
Activation: +
`{4sp}builtin "string"`
Currently available functions:
* <<_strJoin,strJoin()>>
+322
View File
@@ -0,0 +1,322 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>file: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">git.portale-stac.it/go-pkg/expr/file/file.go (88.9%)</option>
<option value="file1">git.portale-stac.it/go-pkg/expr/file/reader.go (77.8%)</option>
<option value="file2">git.portale-stac.it/go-pkg/expr/file/writer.go (100.0%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// file.go
package file
import (
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
type Handle interface {
kern.Typer
GetFile() *os.File
GetName() string
Valid() bool
Close() error
}
type handleBase struct {
fh *os.File
}
func (h *handleBase) GetFile() *os.File <span class="cov0" title="0">{
return h.fh
}</span>
func (h *handleBase) GetName() (name string) <span class="cov8" title="1">{
if h.fh != nil </span><span class="cov8" title="1">{
name = h.fh.Name()
}</span>
<span class="cov8" title="1">return</span>
}
func (h *handleBase) Valid() bool <span class="cov8" title="1">{
return h.fh != nil
}</span>
func (h *handleBase) Close() (err error) <span class="cov8" title="1">{
if h.fh != nil </span><span class="cov8" title="1">{
err = h.fh.Close()
h.fh = nil
}</span>
<span class="cov8" title="1">return</span>
}
</pre>
<pre class="file" id="file1" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// reader.go
package file
import (
"bufio"
"io"
"os"
)
type Reader struct {
// fh *os.File
handleBase
reader *bufio.Reader
}
func NewReader(fh *os.File) *Reader <span class="cov8" title="1">{
return &amp;Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)}
}</span>
func OpenReader(filePath string) (r *Reader, err error) <span class="cov8" title="1">{
var fh *os.File
if fh, err = os.Open(filePath); err == nil </span><span class="cov8" title="1">{
r = NewReader(fh)
}</span>
<span class="cov8" title="1">return</span>
}
func (h *Reader) TypeName() string <span class="cov8" title="1">{
return "fileReader"
}</span>
func (h *Reader) String() string <span class="cov8" title="1">{
return "reader"
}</span>
func (h *Reader) Valid() bool <span class="cov8" title="1">{
return h.handleBase.Valid() &amp;&amp; h.reader != nil
}</span>
func (w *Reader) Close() (err error) <span class="cov8" title="1">{
w.reader = nil
err = w.handleBase.Close()
return
}</span>
func (h *Reader) ReadByte() (b byte, err error) <span class="cov8" title="1">{
if h.reader != nil </span><span class="cov8" title="1">{
b, err = h.reader.ReadByte()
}</span> else<span class="cov0" title="0"> {
err = io.ErrClosedPipe
}</span>
<span class="cov8" title="1">return</span>
}
func (h *Reader) ReadAll() (p []byte, err error) <span class="cov8" title="1">{
if h.reader != nil </span><span class="cov8" title="1">{
p, err = io.ReadAll(h.reader)
}</span> else<span class="cov0" title="0"> {
err = io.ErrClosedPipe
}</span>
<span class="cov8" title="1">return</span>
}
func (h *Reader) ReadString(delim byte) (s string, err error) <span class="cov0" title="0">{
if h.reader != nil </span><span class="cov0" title="0">{
s, err = h.reader.ReadString(delim)
}</span> else<span class="cov0" title="0"> {
err = io.ErrClosedPipe
}</span>
<span class="cov0" title="0">return</span>
}
func (h *Reader) Reset() (err error) <span class="cov8" title="1">{
if h.fh != nil </span><span class="cov8" title="1">{
if _, err = h.fh.Seek(0, 0); err == nil </span><span class="cov8" title="1">{
h.reader.Reset(h.fh)
}</span>
}
<span class="cov8" title="1">return</span>
}
</pre>
<pre class="file" id="file2" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// writer.go
package file
import (
"bufio"
"fmt"
"os"
)
type Writer struct {
// fh *os.File
handleBase
writer *bufio.Writer
}
func NewWriter(fh *os.File) *Writer <span class="cov8" title="1">{
return &amp;Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)}
}</span>
func CreateWriter(filePath string) (w *Writer, err error) <span class="cov8" title="1">{
var fh *os.File
if fh, err = os.Create(filePath); err == nil </span><span class="cov8" title="1">{
w = NewWriter(fh)
}</span>
<span class="cov8" title="1">return</span>
}
func AppendWriter(filePath string) (w *Writer, err error) <span class="cov8" title="1">{
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil </span><span class="cov8" title="1">{
w = NewWriter(fh)
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) TypeName() string <span class="cov8" title="1">{
return "fileWriter"
}</span>
func (w *Writer) String() string <span class="cov8" title="1">{
return "writer"
}</span>
func (w *Writer) Valid() bool <span class="cov8" title="1">{
return w.handleBase.Valid() &amp;&amp; w.writer != nil
}</span>
func (w *Writer) Close() (err error) <span class="cov8" title="1">{
var err1 error
if w.writer != nil </span><span class="cov8" title="1">{
err1 = w.Flush()
w.writer = nil
}</span>
<span class="cov8" title="1">if err = w.handleBase.Close(); err == nil </span><span class="cov8" title="1">{
err = err1
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) Flush() (err error) <span class="cov8" title="1">{
if w.writer != nil </span><span class="cov8" title="1">{
err = w.writer.Flush()
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) Write(args ...any) (n int, err error) <span class="cov8" title="1">{
if w.writer != nil </span><span class="cov8" title="1">{
n, err = fmt.Fprint(w.writer, args...)
}</span>
<span class="cov8" title="1">return</span>
}
func (w *Writer) Writef(format string, args ...any) (n int, err error) <span class="cov8" title="1">{
if w.writer != nil </span><span class="cov8" title="1">{
n, err = fmt.Fprintf(w.writer, format, args...)
}</span>
<span class="cov8" title="1">return</span>
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
+46
View File
@@ -0,0 +1,46 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// file.go
package file
import (
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
type Handle interface {
kern.Typer
GetFile() *os.File
GetName() string
Valid() bool
Close() error
}
type handleBase struct {
fh *os.File
}
func (h *handleBase) GetFile() *os.File {
return h.fh
}
func (h *handleBase) GetName() (name string) {
if h.fh != nil {
name = h.fh.Name()
}
return
}
func (h *handleBase) Valid() bool {
return h.fh != nil
}
func (h *handleBase) Close() (err error) {
if h.fh != nil {
err = h.fh.Close()
h.fh = nil
}
return
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// reader.go
package file
import (
"bufio"
"io"
"os"
)
type Reader struct {
// fh *os.File
handleBase
reader *bufio.Reader
}
func NewReader(fh *os.File) *Reader {
return &Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)}
}
func OpenReader(filePath string) (r *Reader, err error) {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
r = NewReader(fh)
}
return
}
func (h *Reader) TypeName() string {
return "fileReader"
}
func (h *Reader) String() string {
return "reader"
}
func (h *Reader) Valid() bool {
return h.handleBase.Valid() && h.reader != nil
}
func (w *Reader) Close() (err error) {
w.reader = nil
err = w.handleBase.Close()
return
}
func (h *Reader) ReadByte() (b byte, err error) {
if h.reader != nil {
b, err = h.reader.ReadByte()
} else {
err = io.ErrClosedPipe
}
return
}
func (h *Reader) ReadAll() (p []byte, err error) {
if h.reader != nil {
p, err = io.ReadAll(h.reader)
} else {
err = io.ErrClosedPipe
}
return
}
func (h *Reader) ReadString(delim byte) (s string, err error) {
if h.reader != nil {
s, err = h.reader.ReadString(delim)
} else {
err = io.ErrClosedPipe
}
return
}
func (h *Reader) Reset() (err error) {
if h.fh != nil {
if _, err = h.fh.Seek(0, 0); err == nil {
h.reader.Reset(h.fh)
}
}
return
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// reader.go
package file
import "testing"
func TestOpenReader(t *testing.T) {
r, err := OpenReader("t_reader_test.go")
if err != nil {
t.Fatalf("OpenReader failed: %v", err)
}
defer r.Close()
if !r.Valid() {
t.Fatal("Reader should be valid after opening")
}
if r.TypeName() != "fileReader" {
t.Fatalf("Expected TypeName 'fileReader', got '%s'", r.TypeName())
}
if r.String() != "reader" {
t.Fatalf("Expected String 'reader', got '%s'", r.String())
}
// GetName may return either "t_reader_test.go" or "./t_reader_test.go" depending on the environment
name := r.GetName()
if (name != "t_reader_test.go") && (name != "./t_reader_test.go") {
t.Fatalf("Expected GetName 't_reader_test.go' or './t_reader_test.go', got '%s'", name)
}
// Test reading a byte
b, err := r.ReadByte()
if err != nil {
t.Fatalf("ReadByte failed: %v", err)
}
if b == 0 {
t.Fatal("ReadByte should not return zero byte")
}
err = r.Reset()
if err != nil {
t.Fatalf("Reset failed: %v", err)
}
if s, err := r.ReadString('\n'); err != nil {
t.Fatalf("ReadString failed: %v", err)
} else {
t.Logf("ReadString: %s", s)
}
// Test reading all content
content, err := r.ReadAll()
if err != nil {
t.Fatalf("ReadAll failed: %v", err)
}
if len(content) == 0 {
t.Fatal("ReadAll should return non-empty content")
}
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// writer.go
package file
import "testing"
func TestCreateWriter(t *testing.T) {
w, err := CreateWriter("/tmp/test_writer.txt")
if err != nil {
t.Fatalf("CreateWriter failed: %v", err)
}
defer w.Close()
if !w.Valid() {
t.Fatal("Writer should be valid after creation")
}
if w.TypeName() != "fileWriter" {
t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName())
}
if w.String() != "writer" {
t.Fatalf("Expected String 'writer', got '%s'", w.String())
}
name := w.GetName()
if name != "/tmp/test_writer.txt" {
t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name)
}
if n, err := w.Write("Hello, World!\n"); err != nil {
t.Fatalf("Write failed: %v", err)
} else if n != len("Hello, World!\n") {
t.Fatalf("Expected to write %d bytes, wrote %d", len("Hello, World!\n"), n)
}
if n, err := w.Writef("This is a %s.\n", "test"); err != nil {
t.Fatalf("Writef failed: %v", err)
} else if n != len("This is a test.\n") {
t.Fatalf("Expected to write %d bytes, wrote %d", len("This is a test.\n"), n)
}
}
func TestAppendWriter(t *testing.T) {
w, err := AppendWriter("/tmp/test_writer.txt")
if err != nil {
t.Fatalf("AppendWriter failed: %v", err)
}
defer w.Close()
if !w.Valid() {
t.Fatal("Writer should be valid after opening for append")
}
if w.TypeName() != "fileWriter" {
t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName())
}
if w.String() != "writer" {
t.Fatalf("Expected String 'writer', got '%s'", w.String())
}
name := w.GetName()
if name != "/tmp/test_writer.txt" {
t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name)
}
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// writer.go
package file
import (
"bufio"
"fmt"
"os"
)
type Writer struct {
// fh *os.File
handleBase
writer *bufio.Writer
}
func NewWriter(fh *os.File) *Writer {
return &Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)}
}
func CreateWriter(filePath string) (w *Writer, err error) {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
w = NewWriter(fh)
}
return
}
func AppendWriter(filePath string) (w *Writer, err error) {
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil {
w = NewWriter(fh)
}
return
}
func (w *Writer) TypeName() string {
return "fileWriter"
}
func (w *Writer) String() string {
return "writer"
}
func (w *Writer) Valid() bool {
return w.handleBase.Valid() && w.writer != nil
}
func (w *Writer) Close() (err error) {
var err1 error
if w.writer != nil {
err1 = w.Flush()
w.writer = nil
}
if err = w.handleBase.Close(); err == nil {
err = err1
}
return
}
func (w *Writer) Flush() (err error) {
if w.writer != nil {
err = w.writer.Flush()
}
return
}
func (w *Writer) Write(args ...any) (n int, err error) {
if w.writer != nil {
n, err = fmt.Fprint(w.writer, args...)
}
return
}
func (w *Writer) Writef(format string, args ...any) (n int, err error) {
if w.writer != nil {
n, err = fmt.Fprintf(w.writer, format, args...)
}
return
}
+9 -1
View File
@@ -12,6 +12,8 @@ import (
"git.portale-stac.it/go-pkg/expr/scan"
)
const iterIterType = "IterIter"
type IterIter struct {
it kern.Iterator
count int64
@@ -22,7 +24,13 @@ type IterIter struct {
}
func NewIterIter(it kern.Iterator, ctx kern.ExprContext, exprs []*scan.Term) (iter kern.Iterator, err error) {
if ctx == nil {
err = fmt.Errorf("context is required for %s", iterIterType)
} else if it == nil {
err = fmt.Errorf("source iterator is required for %s", iterIterType)
} else {
iter = &IterIter{it: it, count: 0, index: -1, ctx: ctx, exprList: exprs, current: nil}
}
return
}
@@ -31,7 +39,7 @@ func (it *IterIter) String() string {
}
func (it *IterIter) TypeName() string {
return "IterIter"
return iterIterType
}
func (it *IterIter) HasOperation(name string) bool {
+4
View File
@@ -84,6 +84,10 @@ func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
}
func ErrFuncInvalidArg(funcName, details string) error {
return fmt.Errorf("%s(): invalid argument -- %s", funcName, details)
}
// --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm Term) error {
+3 -2
View File
@@ -44,10 +44,11 @@ func MakeGeneratingFraction(s string) (f *FractionType, err error) {
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
switch s[0] {
case '-':
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
case '+':
s = s[1:]
}
// if strings.HasSuffix(s, "()") {
+43
View File
@@ -7,6 +7,7 @@ package kern
import (
// "errors"
"fmt"
"slices"
)
// Operator names
@@ -50,3 +51,45 @@ func IsIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
type IteratorBase struct {
ItemIndex int64
ItemCount int64
current any
}
func (it *IteratorBase) Current() (item any, err error) {
return it.current, nil
}
func (it *IteratorBase) Index() int64 {
return it.ItemIndex
}
func (it *IteratorBase) Count() int64 {
return it.ItemCount
}
func (it *IteratorBase) HasOperation(name string) bool {
return slices.Contains([]string{NextName, IndexName, CountName, CurrentName}, name)
}
func (it *IteratorBase) Clean() (err error) {
return
}
func (it *IteratorBase) Reset() (err error) {
it.ItemIndex = -1
it.ItemCount = 0
it.current = nil
return
}
func (it *IteratorBase) Increment() {
it.ItemIndex++
it.ItemCount++
}
func (it *IteratorBase) SetCurrent(v any) {
it.current = v
}
+273
View File
@@ -0,0 +1,273 @@
// simple-list project slist.go
package kern
import (
"fmt"
)
type ListNode struct {
data any
next *ListNode
}
func (node *ListNode) Next() *ListNode {
return node.next
}
func (self *ListNode) Data() any {
return self.data
}
type LinkedList struct {
count uint32
first *ListNode
last *ListNode
}
func NewLinkedList() (list *LinkedList) {
list = &LinkedList{0, nil, nil}
return
}
func (ls *LinkedList) Len() uint32 {
return ls.count
}
func (ls *LinkedList) Empty() bool {
return ls.count == 0
}
func (ls *LinkedList) PushFront(data any) *ListNode {
ls.first = &ListNode{data, ls.first}
if ls.last == nil {
ls.last = ls.first
}
ls.count++
return ls.first
}
func (ls *LinkedList) SeqPushFront(data any) (result *LinkedList) {
switch seq := data.(type) {
case Iterator:
result = ls.IterPushFront(seq)
default:
ls.PushFront(data)
result = ls
}
return
}
func (ls *LinkedList) IterPushFront(it Iterator) *LinkedList {
for item, err1 := it.Next(); err1 == nil; item, err1 = it.Next() {
ls.PushFront(item)
}
return ls
}
func (ls *LinkedList) PushBack(data any) (node *ListNode) {
node = &ListNode{data, nil}
if ls.last != nil {
ls.last.next = node
}
ls.last = node
if ls.first == nil {
ls.first = node
}
ls.count++
return
}
func (ls *LinkedList) SeqPushBack(data any) (result *LinkedList) {
switch seq := data.(type) {
case Iterator:
result = ls.IterPushBack(seq)
default:
ls.PushBack(data)
result = ls
}
return
}
func (ls *LinkedList) IterPushBack(it Iterator) *LinkedList {
for item, err1 := it.Next(); err1 == nil; item, err1 = it.Next() {
ls.PushBack(item)
}
return ls
}
func (ls *LinkedList) FirstNode() *ListNode {
return ls.first
}
func (ls *LinkedList) First() (data any, err error) {
if ls.first != nil {
data = ls.first.data
} else {
err = errorListEmpty()
}
return
}
func (ls *LinkedList) LastNode() *ListNode {
return ls.last
}
func (ls *LinkedList) Last() (data interface{}, err error) {
if ls.last != nil {
data = ls.last.data
} else {
err = errorListEmpty()
}
return
}
func (ls *LinkedList) NodeAt(index uint32) (node *ListNode, err error) {
if ls.count == 0 {
err = errorListEmpty()
} else if index > ls.count {
err = errorOutOfRange()
} else if index == ls.count-1 {
node = ls.last
} else {
current := ls.first
for pos := uint32(0); pos < index; pos++ {
current = current.next
}
node = current
}
return
}
func (ls *LinkedList) At(index uint32) (data interface{}, err error) {
node, err := ls.NodeAt(index)
if err == nil {
data = node.data
}
return
}
func (ls *LinkedList) Insert(index uint32, data interface{}) *ListNode {
var prev *ListNode
prev = nil
current := ls.first
for pos := uint32(0); current != nil && pos < index; pos++ {
prev = current
current = current.next
}
node := &ListNode{data, current}
if prev != nil {
prev.next = node
}
if ls.first == current {
ls.first = node
}
if node.next == nil {
ls.last = node
}
ls.count++
return node
}
func (ls *LinkedList) PushBackStringArray(items []string) {
for _, v := range items {
ls.PushBack(v)
}
}
// type TraverseOperator func(index uint32, elem interface{}, userData interface{}) (err error)
// func (self *LinkedList) Traverse(op TraverseOperator, user_data interface{}) (err error) {
// index := uint32(0)
// for current := self.first; current != nil; current = current.next {
// err = op(index, current.data, user_data)
// if err != nil {
// break
// }
// index++
// }
// return
// }
// func (self *LinkedList) Traverse2(observer Observer, abortOnError bool) (err error) {
// index := uint32(0)
// for current := self.first; current != nil; current = current.next {
// err = observer.Observe(current, index)
// if err != nil && abortOnError {
// break
// }
// index++
// }
// return
// }
type EqualFunc func(current, target any) bool
func (ls *LinkedList) FindFirst(eqFunc EqualFunc, targetData any) (targetNode *ListNode) {
for current := ls.first; current != nil && targetNode == nil; current = current.next {
if eqFunc(current.data, targetData) {
targetNode = current
}
}
return
}
func (ls *LinkedList) FindNext(eqFunc EqualFunc, startNode *ListNode) (targetNode *ListNode) {
if startNode != nil {
for current := startNode.next; current != nil && targetNode == nil; current = current.next {
if eqFunc(current.data, startNode.Data()) {
targetNode = current
}
}
}
return
}
// type DataFeeder func(user_data interface{}) interface{}
// type NodeObserver func(node *ListNode, index uint32, userData interface{})
// func (self *LinkedList) FeedTail(feeder DataFeeder, feederUserData interface{}, observer NodeObserver, observerUserData interface{}) (count uint32) {
// count = 0
// for item := feeder(feederUserData); item != nil; item = feeder(feederUserData) {
// // fmt.Println("Item", count, item)
// node := self.PushBack(item)
// if observer != nil {
// observer(node, count, observerUserData)
// }
// count++
// }
// return
// }
// func (self *LinkedList) FeedTail2(feeder Feeder, observer Observer, abortOnError bool) (count uint32, err error) {
// count = 0
// // item := feeder.Next()
// for item, err1 := feeder.Next(); item != nil; item, err1 = feeder.Next() {
// if err1 != nil {
// if err == nil {
// err = err1
// }
// if abortOnError {
// break
// }
// }
// // fmt.Println("Item", count, item)
// node := self.PushBack(item)
// if observer != nil {
// observer.Observe(node, count)
// }
// count++
// }
// return
// }
func errorListEmpty() error {
return fmt.Errorf("List is empty")
}
func errorOutOfRange() error {
return fmt.Errorf("Out of range")
}
+6 -6
View File
@@ -50,13 +50,13 @@ func ListFromStrings(stringList []string) (list *ListType) {
return
}
func (dict *ListType) ToString(opt FmtOpt) (s string) {
func (ls *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteByte('[')
if len(*dict) > 0 {
if len(*ls) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
@@ -64,7 +64,7 @@ func (dict *ListType) ToString(opt FmtOpt) (s string) {
sb.WriteByte('\n')
sb.WriteString(nest)
}
for i, item := range []any(*dict) {
for i, item := range []any(*ls) {
if i > 0 {
if flags&MultiLine != 0 {
sb.WriteString(",\n")
@@ -96,11 +96,11 @@ func (dict *ListType) ToString(opt FmtOpt) (s string) {
return
}
func (dict *ListType) String() string {
return dict.ToString(0)
func (ls *ListType) String() string {
return ls.ToString(0)
}
func (dict *ListType) TypeName() string {
func (ls *ListType) TypeName() string {
return "list"
}
+151
View File
@@ -0,0 +1,151 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-cat.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- cat term
func newCatTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalCat,
}
}
func evalCat(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var itLeft, itRight kern.Iterator
// var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
return
}
if itLeft, ok = leftValue.(kern.Iterator); !ok {
if itLeft, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
if itRight, ok = rightValue.(kern.Iterator); !ok {
if itRight, err = NewIterator(ctx, rightValue, nil); err != nil {
return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", kern.TypeName(rightValue))
}
}
// values := kern.NewListA()
// for _, it := range []kern.Iterator{itLeft, itRight} {
// for item, err = it.Next(); err == nil; item, err = it.Next() {
// values.AppendItem(item)
// }
// }
// if err == io.EOF {
// err = nil
// }
// v = values
v = newCatIterator(itLeft, itRight)
return
}
const catIteratorType = "cat"
type catIterator struct {
kern.IteratorBase
itLeft kern.Iterator
itRight kern.Iterator
itCurrent kern.Iterator
}
func newCatIterator(itLeft, itRight kern.Iterator) (it *catIterator) {
it = &catIterator{
IteratorBase: kern.IteratorBase{},
itLeft: itLeft,
itRight: itRight,
itCurrent: itLeft,
}
it.Reset()
return
}
func (it *catIterator) TypeName() string {
return catIteratorType
}
func (it *catIterator) String() string {
return fmt.Sprintf("$(%s %s %s)", it.itLeft, catIteratorType, it.itRight)
}
func (it *catIterator) Next() (item any, err error) {
if it.itCurrent == nil {
err = io.EOF
} else {
if item, err = it.itCurrent.Next(); err == nil {
it.Increment()
} else if err == io.EOF {
if it.itCurrent == it.itLeft {
it.itCurrent = it.itRight
} else {
return
}
if item, err = it.itCurrent.Next(); err == nil {
it.Increment()
} else {
it.itCurrent = nil
}
}
}
it.SetCurrent(item)
return
}
// func (it *catIterator) Reset() (err error) {
// err = it.itLeft.Reset()
// it.IteratorBase.Reset()
// return
// }
func (it *catIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
// case kern.ResetName:
// err = it.Reset()
// case kern.CleanName:
// err = it.Clean()
case kern.IndexName:
v = it.Index()
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.Count()
default:
err = kern.ErrNoOperation(name)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwCat, newCatTerm)
}
+19 -1
View File
@@ -5,6 +5,8 @@
package expr
import (
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
@@ -23,8 +25,8 @@ func newContextTerm(tk *scan.Token) (inst *scan.Term) {
func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
var sourceCtx kern.ExprContext
if len(opTerm.Children) == 0 {
sourceCtx = ctx
} else if opTerm.Children[0].Symbol() == scan.SymVariable && opTerm.Children[0].Source() == "global" {
@@ -33,6 +35,8 @@ func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
} else {
return
}
if sourceCtx != nil {
@@ -53,6 +57,20 @@ func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error
}
v = d
}
} else if childValue != nil {
if it, ok := childValue.(kern.Iterator); ok {
var item any
values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
values.AppendItem(item)
}
if err == io.EOF {
err = nil
v = values
}
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
+107 -18
View File
@@ -6,7 +6,6 @@ package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
@@ -25,9 +24,10 @@ func newFilterTerm(tk *scan.Token) (inst *scan.Term) {
}
func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
// var leftValue, rightValue any
var leftValue any
var it kern.Iterator
var item any
// var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
@@ -44,31 +44,120 @@ func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
}
}
values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
// values := kern.NewListA()
// for item, err = it.Next(); err == nil; item, err = it.Next() {
// ctx.SetVar("_", item)
// ctx.SetVar("__", it.Index())
// ctx.SetVar("_#", it.Count())
// if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
// if success, valid := kern.ToBool(rightValue); valid {
// if success {
// values.AppendItem(item)
// }
// } else {
// err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
// }
// }
// ctx.DeleteVar("_#")
// ctx.DeleteVar("__")
// ctx.DeleteVar("_")
// if err != nil {
// break
// }
// }
// if err == io.EOF {
// err = nil
// }
// v = values
v = newFilerIterator(ctx, it, opTerm.Children[1])
return
}
const filterIteratorType = "filter"
type filterIterator struct {
kern.IteratorBase
ctx kern.ExprContext
itSrc kern.Iterator
expr *scan.Term
}
func newFilerIterator(ctx kern.ExprContext, itSrc kern.Iterator, filterExpr *scan.Term) (it *filterIterator) {
it = &filterIterator{
IteratorBase: kern.IteratorBase{},
ctx: ctx,
itSrc: itSrc,
expr: filterExpr,
}
it.IteratorBase.Reset()
return
}
func (it *filterIterator) TypeName() string {
return filterIteratorType
}
func (it *filterIterator) String() string {
return fmt.Sprintf("$(%s %s bool-expr)", it.itSrc, filterIteratorType)
}
func (it *filterIterator) Next() (item any, err error) {
var attempt, result any
ctx := it.ctx
for attempt, err = it.itSrc.Next(); err == nil; attempt, err = it.itSrc.Next() {
ctx.SetVar("_", attempt)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if success, valid := kern.ToBool(rightValue); valid {
if success {
values.AppendItem(item)
}
} else {
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
}
}
result, err = it.expr.Compute(ctx)
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
if err == nil {
if success, valid := kern.ToBool(result); valid {
if success {
item = attempt
break
}
} else {
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", result, result)
}
}
if err != nil {
break
}
}
if err == io.EOF {
err = nil
it.SetCurrent(item)
return
}
// func (it *filterIterator) Reset() (err error) {
// err = it.itLeft.Reset()
// it.IteratorBase.Reset()
// return
// }
func (it *filterIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
// case kern.ResetName:
// err = it.Reset()
// case kern.CleanName:
// err = it.Clean()
case kern.IndexName:
v = it.Index()
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.Count()
default:
err = kern.ErrNoOperation(name)
}
v = values
return
}
+69 -16
View File
@@ -31,6 +31,41 @@ func newAppendTerm(tk *scan.Token) (inst *scan.Term) {
}
}
func prependToList(ctx kern.ExprContext, opTerm *scan.Term, leftValue, rightValue any) (result any, err error) {
if list, ok := rightValue.(*kern.ListType); ok {
var it kern.Iterator
if it, ok = leftValue.(kern.Iterator); !ok {
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return
}
}
ls := kern.NewLinkedList()
ls.SeqPushBack(it)
newList := kern.ListType(nil)
if newSize := len(*list) + int(ls.Len()); newSize > cap(*list) {
newList = make([]any, 0, newSize)
for node := ls.FirstNode(); node != nil; node = node.Next() {
newList = append(newList, node.Data())
}
}
for _, item := range *list {
newList = append(newList, item)
}
result = &newList
// ***** EVENTUALMENTE ABILITARE LA MODIFICA DELLA VARIABILE
// ***** CON UN OPERATORE SPECIFICO
// if opTerm.Children[1].Symbol() == scan.SymVariable {
// ctx.UnsafeSetVar(opTerm.Children[1].Source(), result)
// }
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalPrepend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
@@ -38,13 +73,39 @@ func evalPrepend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return
}
if kern.IsList(rightValue) {
list, _ := rightValue.(*kern.ListType)
newList := append(kern.ListType{leftValue}, *list...)
v = &newList
if opTerm.Children[1].Symbol() == scan.SymVariable {
ctx.UnsafeSetVar(opTerm.Children[1].Source(), v)
v, err = prependToList(ctx, opTerm, leftValue, rightValue)
return
}
func appendToList(ctx kern.ExprContext, opTerm *scan.Term, leftValue, rightValue any) (result any, err error) {
if list, ok := leftValue.(*kern.ListType); ok {
var it kern.Iterator
if it, ok = rightValue.(kern.Iterator); !ok {
if it, err = NewIterator(ctx, rightValue, nil); err != nil {
return
}
}
ls := kern.NewLinkedList()
ls.SeqPushBack(it)
newList := *list
if newSize := len(*list) + int(ls.Len()); newSize > cap(*list) {
newList = make([]any, 0, newSize)
for _, item := range *list {
newList = append(newList, item)
}
}
for node := ls.FirstNode(); node != nil; node = node.Next() {
newList = append(newList, node.Data())
}
result = &newList
// ***** EVENTUALMENTE ABILITARE LA MODIFICA DELLA VARIABILE
// ***** CON UN OPERATORE SPECIFICO
// if opTerm.Children[0].Symbol() == scan.SymVariable {
// ctx.UnsafeSetVar(opTerm.Children[0].Source(), result)
// }
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
@@ -58,16 +119,8 @@ func evalAppend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return
}
if kern.IsList(leftValue) {
list, _ := leftValue.(*kern.ListType)
newList := append(*list, rightValue)
v = &newList
if opTerm.Children[0].Symbol() == scan.SymVariable {
ctx.UnsafeSetVar(opTerm.Children[0].Source(), v)
}
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
v, err = appendToList(ctx, opTerm, leftValue, rightValue)
return
}
-73
View File
@@ -1,73 +0,0 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-join.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- join term
func newJoinTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalJoin,
}
}
func evalJoin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var itLeft, itRight kern.Iterator
var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
return
}
if itLeft, ok = leftValue.(kern.Iterator); !ok {
if itLeft, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
if itRight, ok = rightValue.(kern.Iterator); !ok {
if itRight, err = NewIterator(ctx, rightValue, nil); err != nil {
return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", kern.TypeName(rightValue))
}
}
values := kern.NewListA()
for _, it := range []kern.Iterator{itLeft, itRight} {
for item, err = it.Next(); err == nil; item, err = it.Next() {
values.AppendItem(item)
}
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwJoin, newJoinTerm)
}
+25 -24
View File
@@ -6,7 +6,6 @@ package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
@@ -25,9 +24,10 @@ func newMapTerm(tk *scan.Token) (inst *scan.Term) {
}
func evalMap(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
// var leftValue, rightValue any
var leftValue any
var it kern.Iterator
var item any
// var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
@@ -44,27 +44,28 @@ func evalMap(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
}
}
values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
values.AppendItem(rightValue)
}
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
if err == nil {
v = values
}
// values := kern.NewListA()
// for item, err = it.Next(); err == nil; item, err = it.Next() {
// ctx.SetVar("_", item)
// ctx.SetVar("__", it.Index())
// ctx.SetVar("_#", it.Count())
// if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
// values.AppendItem(rightValue)
// }
// ctx.DeleteVar("_#")
// ctx.DeleteVar("__")
// ctx.DeleteVar("_")
// if err != nil {
// break
// }
// }
// if err == io.EOF {
// err = nil
// }
// if err == nil {
// v = values
// }
v, err = NewIterIter(it, ctx, opTerm.Children[1:])
return
}
+6 -2
View File
@@ -39,7 +39,9 @@ func evalRightShift(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error)
return
}
v, err = bitRightShift(opTerm, leftValue, rightValue)
if v, err = bitRightShift(opTerm, leftValue, rightValue); err != nil {
v, err = prependToList(ctx, opTerm, leftValue, rightValue)
}
return
}
@@ -71,7 +73,9 @@ func evalLeftShift(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return
}
v, err = bitLeftShift(opTerm, leftValue, rightValue)
if v, err = bitLeftShift(opTerm, leftValue, rightValue); err != nil {
v, err = appendToList(ctx, opTerm, leftValue, rightValue)
}
return
}
+1
View File
@@ -138,6 +138,7 @@ func init() {
SymKwMap: {"map", SymClassOperator, PosInfix},
SymKwFilter: {"filter", SymClassOperator, PosInfix},
SymKwDigest: {"digest", SymClassOperator, PosInfix},
SymKwCat: {"cat", SymClassOperator, PosInfix},
SymKwJoin: {"join", SymClassOperator, PosInfix},
SymKwGroupBy: {"groupby", SymClassOperator, PosInfix},
SymKwFunc: {"func(", SymClassDeclaration, PosPrefix},
+2
View File
@@ -122,6 +122,7 @@ const (
SymKwMap
SymKwFilter
SymKwDigest
SymKwCat
SymKwGroupBy
SymKwJoin
SymKwNil
@@ -141,6 +142,7 @@ func init() {
"IN": SymKwIn,
"INCLUDE": SymKwInclude,
"MAP": SymKwMap,
"CAT": SymKwCat,
"FILTER": SymKwFilter,
"NOT": SymKwNot,
"OR": SymKwOr,
+1 -1
View File
@@ -15,9 +15,9 @@ type TermPriority uint32
const (
PriNone TermPriority = iota
PriRange
PriIterOp // map, filter, digest, etc
PriBut
PriAssign
PriIterOp // map, filter, digest, etc
PriInsert
PriOr
PriAnd
+1 -1
View File
@@ -21,7 +21,7 @@ func TestFuncRun(t *testing.T) {
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
/* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil},
/* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`},
/* 10 */ {`builtin ["os.file", "iterator"]; it = fileReadIterator("test-file.txt"); run(it)`, nil, nil},
/* 10 */ {`builtin ["os.file", "iterator"]; it = fileLineIterator("test-file.txt"); run(it)`, nil, nil},
}
//t.Setenv("EXPR_PATH", ".")
+19 -5
View File
@@ -27,16 +27,30 @@ func TestFuncOs(t *testing.T) {
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`},
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`},
/* 16 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++`, "uno", nil},
/* 17 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++;it++;it++`, nil, io.EOF.Error()},
/* 18 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it.clean`, nil, nil},
/* 19 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); string(it)`, `$(fileReadTextIterator@"test-file.txt")`, nil},
/* 16 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); it++`, "uno", nil},
/* 17 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); it++;it++;it++`, nil, io.EOF.Error()},
/* 18 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); it.clean`, nil, nil},
/* 19 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); string(it)`, `$(fileLineIterator@"test-file.txt")`, nil},
/* 20 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle); string(handle)`, `reader`, nil},
/* 21 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle); string(handle)`, `writer`, nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 19)
// runTestSuiteSpec(t, section, inputs, 24)
runTestSuite(t, section, inputs)
}
func TestFuncOsByteIter(t *testing.T) {
section := "Builtin-OS-File-Byte-Iter"
inputs := []inputType{
/* 1 */ {`builtin "os.file"; it=fileByteIterator("test-file.txt"); string(it)`, `$(fileByteIterator@"test-file.txt")`, nil},
/* 2 */ {`builtin ["os.file", "string"]; it=fileByteIterator("test-file.txt"); char(it++)`, `u`, nil},
/* 3 */ {`builtin ["os.file", "string"]; it=fileByteIterator("test-file.txt"); it++; it.reset; char(it++)`, `u`, nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 24)
runTestSuite(t, section, inputs)
}
+7 -1
View File
@@ -23,13 +23,19 @@ func runCtxTestSuiteSpec(t *testing.T, ctx kern.ExprContext, section string, inp
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, ctx, section, &inputs[count-1], count)
index := count - 1
if index >= 0 && index < len(inputs) {
good := doTest(t, ctx, section, &inputs[index], count)
if good {
succeeded++
} else {
failed++
}
} else {
t.Errorf("%s -- test count: %d, invalid index", section, count)
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
+1 -2
View File
@@ -54,8 +54,7 @@ func TestFuncs2(t *testing.T) {
inputs := []inputType{
/* 1 */ {`sum=func(a,b){a+b}; sum(1,2)`, int64(3), nil},
/* 2 */ {`sum=func(a,b){a+b}; sum(1)`, nil, `sum(): too few params -- expected 2, got 1`},
/* 3 */ {`["1", "2", "3"] map int()`, nil, `int(): too few params -- expected 1, got 0`},
/* 4 */ {`builtin "iterator"; times=func(a,b){a*b}; run($(["1", "2", "3"]), times)`, nil, `operator(): missing params -- a, b`},
/* 3 */ {`builtin "iterator"; times=func(a,b){a*b}; run($(["1", "2", "3"]), times)`, nil, `operator(): missing params -- a, b`},
}
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
+5 -5
View File
@@ -11,16 +11,16 @@ import (
)
func TestIterIterator(t *testing.T) {
section := "Iterator"
section := "Iter-Iter"
inputs := []inputType{
/* 1 */ {`it=$(4); $(it) filter ${_}==100`, kern.NewListA(), nil},
/* 2 */ {`it=$(4); $(it, $_) filter ${_}==100`, kern.NewListA(), nil},
/* 1 */ {`it=$(4); $$($(it) filter ${_}==100)`, kern.NewListA(), nil},
/* 2 */ {`it=$(4); $$($(it, $_) filter ${_}==100)`, kern.NewListA(), nil},
/* 3 */ {`it=$(4); $(it, 10+$_, last-1) digest ${_}`, int64(12), nil},
/* 4 */ {`f=func(n){last-n}; it=$(4); $(it, 10+$_, f(-1)) digest ${_}`, int64(14), nil},
}
runTestSuiteSpec(t, section, inputs, 4)
// runTestSuite(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
}
// func TestNewIterIterator(t *testing.T) {
+3 -2
View File
@@ -92,9 +92,10 @@ func TestNewIterList5(t *testing.T) {
}
func TestNewIterList6(t *testing.T) {
ctx := NewSimpleStore()
list := kern.NewListA("a", "b", "c", "d")
it1, _ := NewIterator(nil, list, nil)
it, _ := NewIterator(nil, it1, nil)
it1, _ := NewIterator(ctx, list, nil)
it, _ := NewIterator(ctx, it1, nil)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
+55 -10
View File
@@ -35,17 +35,62 @@ func TestIteratorParser(t *testing.T) {
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
/* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil},
/* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, kern.NewList([]any{int64(1), "one"}), nil},
/* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${__}`, kern.NewList([]any{int64(0), int64(1)}), nil},
/* 24 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil},
/* 25 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil},
/* 26 */ {`#($(10) map ${_})`, int64(10), nil},
/* 27 */ {`#($(10,0) map ${_})`, int64(10), nil},
/* 28 */ {`$(10) digest ${_}`, int64(9), nil},
/* 29 */ {`$(10,0) digest ${_}`, int64(1), nil},
/* 30 */ {`$(10,0,-2) digest ${_}`, int64(2), nil},
/* 31 */ {`[3,4,5] map ${_#}`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
}
// runTestSuiteSpec(t, section, inputs, 10)
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
func TestFilterIterator(t *testing.T) {
section := "Iterator-Filter"
inputs := []inputType{
/* 1 */ {`$$([1,2,3] filter $_%2==0)`, kern.NewList([]any{int64(2)}), nil},
/* 2 */ {`it = [1,2,3] filter $_%2!=1; $$(it)`, kern.NewList([]any{int64(2)}), nil},
/* 3 */ {`builtin "os.file"; #$$(fileLineIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil},
/* 4 */ {`builtin "os.file"; #$$(fileLineIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil},
}
// runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
}
func TestDigestIterator(t *testing.T) {
section := "Iterator-Digest"
inputs := []inputType{
/* 1 */ {`$(10) digest ${_}`, int64(9), nil},
/* 2 */ {`$(10,0) digest ${_}`, int64(1), nil},
/* 3 */ {`$(10,0,-2) digest ${_}`, int64(2), nil},
}
// runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
}
func TestCatIterator(t *testing.T) {
section := "Iterator-Cat"
inputs := []inputType{
/* 1 */ {`$$([1] cat [])`, kern.NewList([]any{int64(1)}), nil},
/* 2 */ {`$$([1] cat [2])`, kern.NewList([]any{int64(1), int64(2)}), nil},
/* 3 */ {`$$([1] cat nil)`, kern.NewList([]any{int64(1)}), nil},
/* 4 */ {`$$(nil cat [2])`, kern.NewList([]any{int64(2)}), nil},
/* 5 */ {`$$(nil cat nil)`, kern.NewList([]any{}), nil},
/* 6 */ {`$$(["a","b"] cat ["x"-true])`, nil, `[1:23] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`},
}
// runTestSuiteSpec(t, section, inputs, 6)
runTestSuite(t, section, inputs)
}
func TestMapIterator(t *testing.T) {
section := "Iterator-Map"
inputs := []inputType{
/* 1 */ {`$$([3,4,5] map ${_#})`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 2 */ {`#$$($(10) map ${_})`, int64(10), nil},
/* 3 */ {`#$$($(10,0) map ${_})`, int64(10), nil},
/* 4 */ {`builtin "os.file"; $$(fileLineIterator("test-file.txt") map ${__})`, kern.NewList([]any{int64(0), int64(1)}), nil},
/* 5 */ {`$$(["1", "2", "3"] map int())`, nil, `int(): too few params -- expected 1, got 0`},
}
// runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
}
+4 -1
View File
@@ -54,10 +54,13 @@ func TestListParser(t *testing.T) {
/* 38 */ {`[0,1,2,3,4][3:-1]`, kern.NewListA(int64(3)), nil},
/* 30 */ {`[0,1,2,3,4][-3:-1]`, kern.NewListA(int64(2), int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][0:]`, kern.NewListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 41 */ {`[0] << $([1,2,3,4])`, kern.NewListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 42 */ {`L=[]; [1] >> L; L`, kern.NewListA(), nil},
/* 43 */ {`L=[]; L << [1]; L`, kern.NewListA(), nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
// runTestSuiteSpec(t, section, inputs, 42)
runTestSuite(t, section, inputs)
}
+16 -30
View File
@@ -43,49 +43,35 @@ func TestOperator(t *testing.T) {
runTestSuite(t, section, inputs)
}
func TestOperatorMap(t *testing.T) {
section := "Operator-Map"
func TestOperatorInsert(t *testing.T) {
section := "Operator-Insert"
inputs := []inputType{
/* 1 */ {`a=1; --a`, int64(0), nil},
/* 2 */ {`[1,2,3] map var("_")`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 3 */ {`[1,2,3] map $_`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 1 */ {`["a", "b"] << nil`, kern.NewListA("a", "b"), nil},
/* 2 */ {`["a", "b"] << []`, kern.NewListA("a", "b"), nil},
/* 3 */ {`["a", "b"] << 3`, kern.NewListA("a", "b", int64(3)), nil},
/* 4 */ {`3 << ["a", "b"]`, nil, `[1:5] left operand '3' [integer] and right operand '["a", "b"]' [list] are not compatible with operator "<<"`},
/* 5 */ {`nil >> ["a", "b"]`, kern.NewListA("a", "b"), nil},
/* 6 */ {`[] >> ["a", "b"]`, kern.NewListA("a", "b"), nil},
/* 7 */ {`["a", "b"] << $([1,2,3])`, kern.NewListA("a", "b", int64(1), int64(2), int64(3)), nil},
/* 8 */ {`L=["a", "b"]; L << $([1,2,3])`, kern.NewListA("a", "b", int64(1), int64(2), int64(3)), nil},
/* 9 */ {`L=["a", "b"]; L << $([1,2,3]); L`, kern.NewListA("a", "b", int64(1), int64(2), int64(3)), nil},
/* 10 */ {`L << $([1,2,3])`, nil, `undefined variable or function "L"`},
}
// runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
func TestOperatorFilter(t *testing.T) {
section := "Operator-Filter"
inputs := []inputType{
/* 1 */ {`[1,2,3,4] filter ($_ % 2 == 0)`, kern.NewList([]any{int64(2), int64(4)}), nil},
}
// runTestSuiteSpec(t, section, inputs, 1)
// runTestSuiteSpec(t, section, inputs, 9)
runTestSuite(t, section, inputs)
}
func TestOperatorDigest(t *testing.T) {
section := "Operator-Digest"
inputs := []inputType{
/* 1 */ {`max=0; [2,3,1] digest max=(($_ > max) ? {$_} :: {max})`, int64(3), nil},
/* 1 */ {`max=0; [2,3,1] digest (max=(($_ > max) ? {$_} :: {max}))`, int64(3), nil},
}
// runTestSuiteSpec(t, section, inputs, 29)
runTestSuite(t, section, inputs)
}
func TestOperatorJoin(t *testing.T) {
section := "Operator-Join"
inputs := []inputType{
/* 1 */ {`["a","b"] join ["x"]`, kern.NewList([]any{"a", "b", "x"}), nil},
/* 2 */ {`["a","b"] join ["x"-true]`, nil, `[1:21] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`},
}
// runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
}
func TestOperatorGroupBy(t *testing.T) {
section := "Operator-GroupBy"
inputs := []inputType{
@@ -113,6 +99,6 @@ func TestOperatorGroupBy(t *testing.T) {
nil},
}
runTestSuiteSpec(t, section, inputs, 3)
// runTestSuite(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+9 -9
View File
@@ -32,15 +32,15 @@ func testStringEndingWithOperator(source string) bool {
return scan.StringEndsWithOperator(source)
}
func testStringArrayEndingWithOperatorSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
for i := range spec {
if spec[i] < 1 || spec[i] > len(inputs) {
t.Errorf("Invalid test spec index: %d (must be between 1 and %d)", spec[i], len(inputs))
continue
}
doEndingTest(t, section, inputs[spec[i]-1], spec[i]-1)
}
}
// func testStringArrayEndingWithOperatorSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
// for i := range spec {
// if spec[i] < 1 || spec[i] > len(inputs) {
// t.Errorf("Invalid test spec index: %d (must be between 1 and %d)", spec[i], len(inputs))
// continue
// }
// doEndingTest(t, section, inputs[spec[i]-1], spec[i]-1)
// }
// }
func testStringArrayEndingWithOperator(t *testing.T, section string, inputs []inputType) {
for i, input := range inputs {