Compare commits

...

8 Commits

15 changed files with 165 additions and 86 deletions

View File

@ -43,16 +43,16 @@ func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (re
return return
} }
func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) { // func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) {
ctx := cloneContext(parentCtx) // ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx) // ctx.SetParent(parentCtx)
if err == nil { // if err == nil {
if err = checkFunctionCall(ctx, funcName, &params); err == nil { // if err = checkFunctionCall(ctx, funcName, &params); err == nil {
if v, err = ctx.Call(funcName, params); err == nil { // if v, err = ctx.Call(funcName, params); err == nil {
exportObjects(parentCtx, ctx) // exportObjects(parentCtx, ctx)
} // }
} // }
} // }
return // return
} // }

View File

@ -68,7 +68,7 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("createFile") err = errMissingFilePath(name)
} }
return return
} }
@ -80,7 +80,7 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err
result = &osReader{fh: fh, reader: bufio.NewReader(fh)} result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
} }
} else { } else {
err = errMissingFilePath("openFile") err = errMissingFilePath(name)
} }
return return
} }
@ -92,7 +92,7 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("appendFile") err = errMissingFilePath(name)
} }
return return
} }
@ -118,13 +118,13 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("closeFileFunc", handle) err = errInvalidFileHandle(name, handle)
} }
result = err == nil result = err == nil
return return
} }
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
@ -142,12 +142,12 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle) err = errInvalidFileHandle(name, invalidFileHandle)
} }
return return
} }
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
@ -165,23 +165,50 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
limit = s[0] limit = s[0]
} }
if v, err = r.reader.ReadString(limit); err == nil { v, err = r.reader.ReadString(limit)
if len(v) > 0 && v[len(v)-1] == limit { if err == io.EOF {
err = nil
}
if len(v) > 0 {
if v[len(v)-1] == limit {
result = v[0 : len(v)-1] result = v[0 : len(v)-1]
} else { } else {
result = v result = v
} }
} }
if err == io.EOF {
err = nil
}
} else { } else {
invalidFileHandle = handle invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("readFileFunc", invalidFileHandle) err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
invalidFileHandle = args[0]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
var b []byte
b, err = io.ReadAll(r.reader)
result = string(b)
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
} }
return return
} }
@ -190,21 +217,30 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileWrite", NewGolangFunctor(writeFileFunc), TypeInt, []ExprFuncParam{
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(TypeHandle),
})
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(TypeHandle),
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""), NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
}) })
ctx.RegisterFunc("fileRead", NewGolangFunctor(readFileFunc), TypeString, []ExprFuncParam{
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(TypeHandle),
NewFuncParamFlagDef("limitCh", PfDefault, "\n"), NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
}) })
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(TypeHandle),
}) })
} }

View File

@ -41,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
} }
} }
} }
func exportObjectsToParent(sourceCtx ExprContext) {
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
exportObjects(parentCtx, sourceCtx)
}
}

View File

@ -40,6 +40,7 @@ type ExprContext interface {
SetParent(ctx ExprContext) SetParent(ctx ExprContext)
GetParent() (ctx ExprContext) GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any) SetVar(varName string, value any)
UnsafeSetVar(varName string, value any) UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string) EnumVars(func(name string) (accept bool)) (varNames []string)

View File

@ -199,3 +199,42 @@ func (info *funcInfo) MaxArgs() int {
func (info *funcInfo) Functor() Functor { func (info *funcInfo) Functor() Functor {
return info.functor return info.functor
} }
// ----- Call a function ---
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsDefault() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunction(parentCtx ExprContext, name string, params []any) (result any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
if err = checkFunctionCall(ctx, name, &params); err == nil {
result, err = ctx.Call(name, params)
exportObjectsToParent(ctx)
}
return
}

View File

@ -6,7 +6,6 @@ package expr
import ( import (
"errors" "errors"
"fmt"
) )
// -------- function call term // -------- function call term
@ -22,35 +21,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) { func evalFuncCall(ctx ExprContext, self *term) (v any, err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsDefault() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
params := make([]any, len(self.children), len(self.children)+5) params := make([]any, len(self.children), len(self.children)+5)
for i, tree := range self.children { for i, tree := range self.children {
@ -62,11 +33,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
} }
if err == nil { if err == nil {
if err = checkFunctionCall(ctx, name, &params); err == nil { v, err = CallFunction(ctx, name, params)
if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
} }
return return
} }

View File

@ -87,13 +87,14 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = string(unboxedValue[index]) v = string(unboxedValue[index])
} }
case *DictType: case *DictType:
var ok bool /* var ok bool
var indexValue any var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil { if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok { if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue) err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
} }
} } */
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@ -113,6 +114,29 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
} else if IsDict(leftValue) {
d := leftValue.(*DictType)
/* var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}*/
v, err = getDictItem(d, indexTerm, indexList, rightValue)
}
return
}
func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue any) (v any, err error) {
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
} }
return return
} }

View File

@ -29,7 +29,7 @@ func pluginExists(name string) (exists bool) {
func makePluginName(name string) (decorated string) { func makePluginName(name string) (decorated string) {
var template string var template string
if execName, err := os.Executable(); err != nil || !strings.HasSuffix(execName, ".debug") { if execName, err := os.Executable(); err != nil || strings.Index(execName, "debug") < 0 {
template = "expr-%s-plugin.so" template = "expr-%s-plugin.so"
} else { } else {
template = "expr-%s-plugin.so.debug" template = "expr-%s-plugin.so.debug"

View File

@ -7,7 +7,7 @@ package expr
import ( import (
"fmt" "fmt"
"slices" "slices"
// "strings" // "strings"
) )
type SimpleStore struct { type SimpleStore struct {
@ -50,6 +50,7 @@ func (ctx *SimpleStore) Merge(src ExprContext) {
ctx.funcStore[name], _ = src.GetFuncInfo(name) ctx.funcStore[name], _ = src.GetFuncInfo(name)
} }
} }
/* /*
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) { func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n") sb.WriteString("vars: {\n")
@ -165,6 +166,11 @@ func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
return return
} }
func (ctx *SimpleStore) GetLast() (v any) {
v = ctx.varStore["last"]
return
}
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) { func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value) // fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value ctx.varStore[varName] = value

View File

@ -16,10 +16,10 @@ func TestFuncOs(t *testing.T) {
/* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil}, /* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)}, /* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil}, /* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWrite(handle, "bye-bye"); fileClose(handle)`, true, nil}, /* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileRead(handle, "-"); fileClose(handle);word`, "bye", nil}, /* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileRead(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)}, /* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, errors.New(`fileReadText(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; fileWrite(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)}, /* 7 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")

View File

@ -31,6 +31,7 @@ func TestDictParser(t *testing.T) {
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil}, /* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil}, /* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)}, /* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
} }
succeeded := 0 succeeded := 0

View File

@ -14,7 +14,7 @@ func TestExpr(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil}, /* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil}, /* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileRead(f); fileClose(f); line`, "uno", nil}, /* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil}, /* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil}, /* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {` /* 6 */ {`

View File

@ -14,9 +14,9 @@ func TestIteratorParser(t *testing.T) {
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil}, /* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil}, /* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil}, /* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil}, /* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil}, /* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil}, /* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil}, /* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
} }
// inputs1 := []inputType{ // inputs1 := []inputType{

View File

@ -1,23 +1,22 @@
builtin ["os.file", "base"]; builtin ["os.file", "base"];
readInt=func(fh){ readInt=func(fh){
line=fileRead(fh); line=fileReadText(fh);
line ? [nil] {nil} :: {int(line)} line ? [nil] {nil} :: {int(line)}
}; };
ds={ ds={
"init":func(filename){ "init":func(filename){
fh=fileOpen(filename); fh=fileOpen(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current }; fh ? [nil] {nil} :: { @current=readInt(fh) };
fh fh
}, },
"current":func(){ "current":func(){
prev current
}, },
"next":func(fh){ "next":func(fh){
current ? @current=readInt(fh);
[nil] {current} current
:: {@prev=current; @current=readInt(fh) but current}
}, },
"clean":func(fh){ "clean":func(fh){
fileClose(fh) fileClose(fh)
@ -25,9 +24,9 @@ ds={
} }
//;f=$(ds, "int.list") //;f=$(ds, "int.list")
/*
;f++ //;f++
;f++ //;f++
;f++ //;f++
*/ //*/
//;add(f) //;add(f)