Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba1d887a05 | |||
| faff5a7e2c | |||
| 36f6846a3f | |||
| 591b4ffc19 | |||
| fe9ab9ebd2 | |||
| 7198749063 | |||
| b76481bbf2 | |||
| 4f05e5c90a | |||
| 8ad25afdc4 | |||
| 54bc759f70 | |||
| b6887af77a | |||
| 353d495c50 | |||
| f45b2c0a88 | |||
| 624e3ac0f2 | |||
| 35fcbd2bce | |||
| 2150181303 |
+3
-1
@@ -22,6 +22,8 @@ Expressions calculator
|
|||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
|
#TODO: Work in progress#
|
||||||
|
|
||||||
== Expr
|
== Expr
|
||||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||||
|
|
||||||
@@ -182,7 +184,7 @@ a=1; b=2; c=3; a+b+c // returns 6
|
|||||||
=== [blue]`but` operator
|
=== [blue]`but` operator
|
||||||
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
|
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
|
||||||
|
|
||||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`;(` and [blue]`)`.
|
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||||
|
|
||||||
=== Assignment operator [blue]`=`
|
=== Assignment operator [blue]`=`
|
||||||
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
|
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
|
||||||
|
|||||||
+3
-1
@@ -25,7 +25,8 @@ func TestExpr(t *testing.T) {
|
|||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
inputs1 := []inputType{
|
inputs1 := []inputType{
|
||||||
{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
|
{`f=openFile("/tmp/test2.txt"); line=readFile(f); closeFile(f); line`, "ciao", nil},
|
||||||
|
//{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, input := range inputs1 {
|
for i, input := range inputs1 {
|
||||||
@@ -36,6 +37,7 @@ func TestExpr(t *testing.T) {
|
|||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleFuncStore()
|
||||||
// ImportMathFuncs(ctx)
|
// ImportMathFuncs(ctx)
|
||||||
// ImportImportFunc(ctx)
|
// ImportImportFunc(ctx)
|
||||||
|
ImportOsFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||||
|
|||||||
+1
-1
@@ -136,7 +136,7 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportImportFunc(ctx ExprContext) {
|
func ImportImportFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
||||||
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,3 +108,7 @@ func ImportMathFuncs(ctx ExprContext) {
|
|||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerImport("math.arith", ImportMathFuncs)
|
||||||
|
}
|
||||||
|
|||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-os.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type osHandle interface {
|
||||||
|
getFile() *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
type osWriter struct {
|
||||||
|
fh *os.File
|
||||||
|
writer *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *osWriter) getFile() *os.File {
|
||||||
|
return h.fh
|
||||||
|
}
|
||||||
|
|
||||||
|
type osReader struct {
|
||||||
|
fh *os.File
|
||||||
|
reader *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *osReader) getFile() *os.File {
|
||||||
|
return h.fh
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var filePath string
|
||||||
|
if len(args) > 0 {
|
||||||
|
filePath, _ = args[0].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filePath) > 0 {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Create(filePath); err == nil {
|
||||||
|
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): missing the file path", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var filePath string
|
||||||
|
if len(args) > 0 {
|
||||||
|
filePath, _ = args[0].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filePath) > 0 {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err == nil {
|
||||||
|
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): missing the file path", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var filePath string
|
||||||
|
if len(args) > 0 {
|
||||||
|
filePath, _ = args[0].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): missing the file path", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
handle, _ = args[0].(osHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.getFile(); fh != nil {
|
||||||
|
if w, ok := handle.(*osWriter); ok {
|
||||||
|
err = w.writer.Flush()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = fh.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): invalid file handle", name)
|
||||||
|
}
|
||||||
|
result = err == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
handle, _ = args[0].(osHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.getFile(); fh != nil {
|
||||||
|
if w, ok := handle.(*osWriter); ok {
|
||||||
|
result, err = fmt.Fprint(w.writer, args[1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
handle, _ = args[0].(osHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.getFile(); fh != nil {
|
||||||
|
if r, ok := handle.(*osReader); ok {
|
||||||
|
var limit byte = '\n'
|
||||||
|
var v string
|
||||||
|
if len(args) > 1 {
|
||||||
|
if s, ok := args[1].(string); ok && len(s) > 0 {
|
||||||
|
limit = s[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
|
||||||
|
if len(v) > 0 && v[len(v)-1] == limit {
|
||||||
|
result = v[0 : len(v)-1]
|
||||||
|
} else {
|
||||||
|
result = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportOsFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
|
||||||
|
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
||||||
|
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
||||||
|
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
||||||
|
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
|
||||||
|
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function-register.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var functionRegister map[string]func(ExprContext)
|
||||||
|
|
||||||
|
func registerImport(name string, importFunc func(ExprContext)) {
|
||||||
|
if functionRegister == nil {
|
||||||
|
functionRegister = make(map[string]func(ExprContext))
|
||||||
|
}
|
||||||
|
functionRegister[name] = importFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||||
|
var importFunc func(ExprContext)
|
||||||
|
if importFunc, exists = functionRegister[name]; exists {
|
||||||
|
importFunc(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||||
|
var matched bool
|
||||||
|
for name, importFunc := range functionRegister {
|
||||||
|
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||||
|
if matched {
|
||||||
|
count++
|
||||||
|
importFunc(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if functionRegister == nil {
|
||||||
|
functionRegister = make(map[string]func(ExprContext))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
First test
|
||||||
|
next commit
|
||||||
|
nr 3
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-length.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- builtin term
|
||||||
|
|
||||||
|
func newBuiltinTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priSign,
|
||||||
|
evalFunc: evalBuiltin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
if isList(rightValue) {
|
||||||
|
list, _ := rightValue.([]any)
|
||||||
|
for i, moduleSpec := range list {
|
||||||
|
if module, ok := moduleSpec.(string); ok {
|
||||||
|
if ImportInContext(ctx, module) {
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
err = self.Errorf("unknown module %q", module)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if isString(rightValue) {
|
||||||
|
module, _ := rightValue.(string)
|
||||||
|
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(rightValue)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = count
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwBuiltin, newBuiltinTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-dot.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- dot term
|
||||||
|
func newDotTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDot,
|
||||||
|
evalFunc: evalDot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTerm := self.children[1]
|
||||||
|
if !isInteger(rightValue) {
|
||||||
|
err = indexTerm.Errorf("index expression must be integer, got %T", rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index64, _ := rightValue.(int64)
|
||||||
|
index := int(index64)
|
||||||
|
|
||||||
|
if isList(leftValue) {
|
||||||
|
list, _ := leftValue.([]any)
|
||||||
|
if index >= 0 && index < len(list) {
|
||||||
|
v = list[index]
|
||||||
|
} else if index >= -len(list) {
|
||||||
|
v = list[len(list)+index]
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("index %v out of bounds", index)
|
||||||
|
}
|
||||||
|
} else if isString(leftValue) {
|
||||||
|
s, _ := leftValue.(string)
|
||||||
|
if index >= 0 && index < len(s) {
|
||||||
|
v = string(s[index])
|
||||||
|
} else if index >= -len(s) {
|
||||||
|
v = string(s[len(s)+index])
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("index %v out of bounds", index)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymDot, newDotTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-insert.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- insert term
|
||||||
|
|
||||||
|
func newInsertTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priAssign,
|
||||||
|
evalFunc: evalInsert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppendTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priAssign,
|
||||||
|
evalFunc: evalAppend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList(rightValue) {
|
||||||
|
list, _ := rightValue.([]any)
|
||||||
|
v = append([]any{leftValue}, list...)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList(leftValue) {
|
||||||
|
list, _ := leftValue.([]any)
|
||||||
|
v = append(list, rightValue)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymInsert, newInsertTerm)
|
||||||
|
registerTermConstructor(SymAppend, newAppendTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-length.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- length term
|
||||||
|
|
||||||
|
func newLengthTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priSign,
|
||||||
|
evalFunc: evalLength,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList(rightValue) {
|
||||||
|
list, _ := rightValue.([]any)
|
||||||
|
v = len(list)
|
||||||
|
} else if isString(rightValue) {
|
||||||
|
s, _ := rightValue.(string)
|
||||||
|
v = len(s)
|
||||||
|
// } else {
|
||||||
|
// v = 1
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleType(rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymHash, newLengthTerm)
|
||||||
|
}
|
||||||
@@ -66,8 +66,6 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newMinusTerm(tk *Token) (inst *term) {
|
func newMinusTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priSum,
|
priority: priSum,
|
||||||
|
|||||||
@@ -191,10 +191,11 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
|
|||||||
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
var selectorTerm *term = nil
|
var selectorTerm *term = nil
|
||||||
var currentTerm *term = nil
|
var currentTerm *term = nil
|
||||||
|
var tk *Token
|
||||||
tree = NewAst()
|
tree = NewAst()
|
||||||
firstToken := true
|
firstToken := true
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
for tk := scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
||||||
if tk.Sym == SymComment {
|
if tk.Sym == SymComment {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -281,6 +282,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
}
|
}
|
||||||
lastSym = tk.Sym
|
lastSym = tk.Sym
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = tk.Error()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ func TestParser(t *testing.T) {
|
|||||||
ctx.SetVar("var1", int64(123))
|
ctx.SetVar("var1", int64(123))
|
||||||
ctx.SetVar("var2", "abc")
|
ctx.SetVar("var2", "abc")
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
ImportImportFunc(ctx)
|
ImportImportFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||||
|
|||||||
+76
-17
@@ -152,9 +152,10 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
case ';':
|
case ';':
|
||||||
tk = self.makeToken(SymSemiColon, ch)
|
tk = self.makeToken(SymSemiColon, ch)
|
||||||
case '.':
|
case '.':
|
||||||
if next, _ := self.peek(); next >= '0' && next <= '9' {
|
//if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||||
tk = self.parseNumber(ch)
|
// tk = self.parseNumber(ch)
|
||||||
} else if next == '/' {
|
//} else if next == '/' {
|
||||||
|
if next, _ := self.peek(); next == '/' {
|
||||||
tk = self.moveOn(SymDotSlash, ch, next)
|
tk = self.moveOn(SymDotSlash, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymDot, ch)
|
tk = self.makeToken(SymDot, ch)
|
||||||
@@ -219,6 +220,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
case '<':
|
case '<':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := self.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymLessOrEqual, ch, next)
|
tk = self.moveOn(SymLessOrEqual, ch, next)
|
||||||
|
} else if next == '<' {
|
||||||
|
tk = self.moveOn(SymAppend, ch, next)
|
||||||
} else if next == '>' {
|
} else if next == '>' {
|
||||||
tk = self.moveOn(SymLessGreater, ch, next)
|
tk = self.moveOn(SymLessGreater, ch, next)
|
||||||
} else {
|
} else {
|
||||||
@@ -227,6 +230,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
case '>':
|
case '>':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := self.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymGreaterOrEqual, ch, next)
|
tk = self.moveOn(SymGreaterOrEqual, ch, next)
|
||||||
|
} else if next == '>' {
|
||||||
|
tk = self.moveOn(SymInsert, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymGreater, ch)
|
tk = self.makeToken(SymGreater, ch)
|
||||||
}
|
}
|
||||||
@@ -276,17 +281,78 @@ func (self *scanner) sync(err error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isBinaryDigit(ch byte) bool {
|
||||||
|
return ch == '0' || ch == '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOctalDigit(ch byte) bool {
|
||||||
|
return ch >= '0' && ch <= '7'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDecimalDigit(ch byte) bool {
|
||||||
|
return ch >= '0' && ch <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexDigit(ch byte) bool {
|
||||||
|
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
|
||||||
|
var ch byte
|
||||||
|
var digitType string
|
||||||
|
firstCh = currentFirstCh
|
||||||
|
digitFunc = isDecimalDigit
|
||||||
|
numBase = 10
|
||||||
|
|
||||||
|
if ch, err = self.peek(); err == nil {
|
||||||
|
if ch == 'b' || ch == 'B' {
|
||||||
|
numBase = 2
|
||||||
|
digitType = "binary"
|
||||||
|
self.readChar()
|
||||||
|
digitFunc = isBinaryDigit
|
||||||
|
firstCh, err = self.readChar()
|
||||||
|
} else if ch == 'o' || ch == 'O' {
|
||||||
|
numBase = 8
|
||||||
|
digitType = "octal"
|
||||||
|
self.readChar()
|
||||||
|
digitFunc = isOctalDigit
|
||||||
|
firstCh, err = self.readChar()
|
||||||
|
} else if ch == 'x' || ch == 'X' {
|
||||||
|
numBase = 16
|
||||||
|
digitType = "hex"
|
||||||
|
self.readChar()
|
||||||
|
digitFunc = isHexDigit
|
||||||
|
firstCh, err = self.readChar()
|
||||||
|
}
|
||||||
|
if err == nil && !digitFunc(firstCh) {
|
||||||
|
if len(digitType) == 0 {
|
||||||
|
digitType = "decimal"
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("expected %s digit, got '%c'", digitType, firstCh)
|
||||||
|
}
|
||||||
|
} else if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch byte
|
var ch byte
|
||||||
var sym Symbol = SymInteger
|
var sym Symbol = SymInteger
|
||||||
var value any
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
var isDigit func(byte) bool = isDecimalDigit
|
||||||
|
var numBase = 10
|
||||||
|
|
||||||
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
if firstCh == '0' {
|
||||||
|
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
|
||||||
|
}
|
||||||
|
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
if ch == '.' {
|
|
||||||
|
if numBase == 10 {
|
||||||
|
if err == nil && ch == '.' {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
ch, err = self.readChar()
|
ch, err = self.readChar()
|
||||||
@@ -296,7 +362,7 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ch == 'e' || ch == 'E' {
|
if err == nil && (ch == 'e' || ch == 'E') {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
if ch, err = self.readChar(); err == nil {
|
if ch, err = self.readChar(); err == nil {
|
||||||
@@ -308,30 +374,23 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
//err = self.sync(err)
|
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("expected integer exponent")
|
err = errors.New("expected integer exponent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// } else {
|
}
|
||||||
// err = self.sync(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
tk = self.makeErrorToken(err)
|
tk = self.makeErrorToken(err)
|
||||||
} else {
|
} else {
|
||||||
|
var value any
|
||||||
err = self.sync(err)
|
err = self.sync(err)
|
||||||
txt := sb.String()
|
txt := sb.String()
|
||||||
if sym == SymFloat {
|
if sym == SymFloat {
|
||||||
value, err = strconv.ParseFloat(txt, 64)
|
value, err = strconv.ParseFloat(txt, 64)
|
||||||
} else if strings.HasPrefix(txt, "0x") {
|
|
||||||
value, err = strconv.ParseInt(txt, 16, 64)
|
|
||||||
} else if strings.HasPrefix(txt, "0o") {
|
|
||||||
value, err = strconv.ParseInt(txt, 8, 64)
|
|
||||||
} else if strings.HasPrefix(txt, "0b") {
|
|
||||||
value, err = strconv.ParseInt(txt, 2, 64)
|
|
||||||
} else {
|
} else {
|
||||||
value, err = strconv.ParseInt(txt, 10, 64)
|
value, err = strconv.ParseInt(txt, numBase, 64)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tk = self.makeValueToken(sym, txt, value)
|
tk = self.makeValueToken(sym, txt, value)
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ const (
|
|||||||
SymQuestionEqual // 48: '?='
|
SymQuestionEqual // 48: '?='
|
||||||
SymDoubleAt // 49: '@@'
|
SymDoubleAt // 49: '@@'
|
||||||
SymDoubleColon // 50: '::'
|
SymDoubleColon // 50: '::'
|
||||||
|
SymInsert // 51: '>>'
|
||||||
|
SymAppend // 52: '<<'
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
@@ -87,6 +89,7 @@ const (
|
|||||||
SymKwOr
|
SymKwOr
|
||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
|
SymKwBuiltin
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords map[string]Symbol
|
var keywords map[string]Symbol
|
||||||
@@ -95,6 +98,7 @@ func init() {
|
|||||||
//keywords = make(map[string]Symbol)
|
//keywords = make(map[string]Symbol)
|
||||||
keywords = map[string]Symbol{
|
keywords = map[string]Symbol{
|
||||||
"AND": SymKwAnd,
|
"AND": SymKwAnd,
|
||||||
|
"BUILTIN": SymKwBuiltin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
priSign
|
priSign
|
||||||
priFact
|
priFact
|
||||||
priCoalesce
|
priCoalesce
|
||||||
|
priDot
|
||||||
priValue
|
priValue
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,7 +167,7 @@ func (self *term) checkOperands() (err error) {
|
|||||||
switch self.position {
|
switch self.position {
|
||||||
case posInfix:
|
case posInfix:
|
||||||
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
|
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
|
||||||
err = self.tk.Errorf("infix operator %q requires two not nil operands, got %d", self.source(), self.getChildrenCount())
|
err = self.tk.Errorf("infix operator %q requires two non-nil operands, got %d", self.source(), self.getChildrenCount())
|
||||||
}
|
}
|
||||||
case posPrefix:
|
case posPrefix:
|
||||||
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
|
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
|
||||||
|
|||||||
@@ -67,3 +67,17 @@ func (self *Token) Errorf(template string, args ...any) (err error) {
|
|||||||
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
|
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Token) Error() (err error) {
|
||||||
|
if self.Sym == SymError {
|
||||||
|
if msg, ok := self.Value.(error); ok {
|
||||||
|
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Token) Errors(msg string) (err error) {
|
||||||
|
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user