Added all source files

This commit is contained in:
Celestino Amoroso 2024-03-26 07:00:53 +01:00
parent 737696b71e
commit 522b880ca3
24 changed files with 2361 additions and 0 deletions

84
ast.go Normal file
View File

@ -0,0 +1,84 @@
// ast.go
package expr
import (
"errors"
"fmt"
"strings"
)
//-------- ast
type ast struct {
root *term
}
func NewAst() *ast {
return &ast{}
}
func (self *ast) String() string {
var sb strings.Builder
if self.root == nil {
sb.WriteString("(nil)")
} else {
self.root.toString(&sb)
}
return sb.String()
}
func (self *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens {
if err = self.addToken(tk); err != nil {
break
}
}
return
}
func (self *ast) addToken(tk *Token) (err error) {
if t := newTerm(tk, nil); t != nil {
err = self.addTerm(t)
} else {
err = fmt.Errorf("No term constructor for token %q", tk.String())
}
return
}
func (self *ast) addTerm(node *term) (err error) {
if self.root == nil {
self.root = node
} else {
self.root, err = self.insert(self.root, node)
}
return
}
func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.getPriority() < node.getPriority() {
root = tree
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
subRoot, err = self.insert(last, node)
subRoot.setParent(tree)
} else {
node.setParent(tree)
}
} else if !node.isLeaf() {
root = node
tree.setParent(node)
} else {
err = fmt.Errorf("two adjacent operators: %q and %q", tree, node)
}
return
}
func (self *ast) eval(ctx exprContext) (result any, err error) {
if self.root != nil {
result, err = self.root.compute(ctx)
} else {
err = errors.New("empty expression")
}
return
}

50
ast_test.go Normal file
View File

@ -0,0 +1,50 @@
// ast_test.go
package expr
import (
"errors"
"testing"
)
func TestAstString(t *testing.T) {
tree := NewAst()
if gotResult := tree.String(); gotResult != "(nil)" {
t.Errorf(`result: got %q, want "(nil)"`, gotResult)
}
}
func TestAddTokensGood(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr != nil {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestAddTokensBad(t *testing.T) {
tk0 := NewValueToken(SymInteger, "200", 200)
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
wantErr := errors.New(`two adjacent operators: "200" and "100"`)
tree := NewAst()
if gotErr := tree.addTokens(tk0, tk1, tk2, tk3); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestAddUnknownTokens(t *testing.T) {
tk0 := NewToken(SymPercent, "%")
wantErr := errors.New(`No term constructor for token "%"`)
tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
}
}

31
byte-ring.go Normal file
View File

@ -0,0 +1,31 @@
// byte-slider.go
package expr
import "bytes"
type ByteSlider struct {
buf []byte
length int
}
func NewByteSlider(size int) *ByteSlider {
return &ByteSlider{
buf: make([]byte, max(1, size)),
length: 0,
}
}
func (self *ByteSlider) PushEnd(b byte) {
if self.length == cap(self.buf) {
self.length--
for i := 0; i < self.length; i++ {
self.buf[i] = self.buf[i+1]
}
}
self.buf[self.length] = b
self.length++
}
func (self *ByteSlider) Equal(target []byte) bool {
return target != nil && bytes.Equal(self.buf, target)
}

15
context.go Normal file
View File

@ -0,0 +1,15 @@
// context.go
package expr
type exprFunc interface {
Name() string
MinArgs() int
MaxArgs() int
}
type exprContext interface {
GetValue(varName string) (value any, exists bool)
SetValue(varName string, value any)
GetFuncInfo(name string) exprFunc
Call(name string, args []any) (result any, err error)
}

72
operand-const.go Normal file
View File

@ -0,0 +1,72 @@
// operand-const.go
package expr
// -------- bool const term
func newBoolTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindBool,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- integer const term
func newIntegerTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindInteger,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- float const term
func newFloatTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindFloat,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- string const term
func newStringTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindString,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- eval func
func evalConst(ctx exprContext, self *term) (v any, err error) {
v = self.tk.Value
return
}
// init
func init() {
registerTermConstructor(SymString, newStringTerm)
registerTermConstructor(SymInteger, newIntegerTerm)
registerTermConstructor(SymFloat, newFloatTerm)
registerTermConstructor(SymBool, newBoolTerm)
}

33
operand-func.go Normal file
View File

@ -0,0 +1,33 @@
// operand-func.go
package expr
// -------- function term
func newFuncTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
parent: nil,
children: args,
position: posLeaf,
priority: priValue,
evalFunc: evalFunc,
}
}
// -------- eval func
func evalFunc(ctx exprContext, self *term) (v any, err error) {
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children))
for i, tree := range self.children {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
params[i] = param
}
if err == nil {
v, err = ctx.Call(name, params)
}
return
}

32
operand-var.go Normal file
View File

@ -0,0 +1,32 @@
// operand-var.go
package expr
import "fmt"
// -------- variable term
func newVarTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalVar,
}
}
// -------- eval func
func evalVar(ctx exprContext, self *term) (v any, err error) {
var exists bool
if v, exists = ctx.GetValue(self.tk.source); !exists {
err = fmt.Errorf("undefined variable %q", self.tk.source)
}
return
}
// init
func init() {
registerTermConstructor(SymIdentifier, newVarTerm)
}

106
operator-bool.go Normal file
View File

@ -0,0 +1,106 @@
// operator-bool.go
package expr
//-------- NOT term
func newNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priNot,
evalFunc: evalNot,
}
}
func evalNot(ctx exprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
return
}
if b, ok := toBool(rightValue); ok {
v = !b
} else {
err = self.errIncompatibleType(rightValue)
}
return
}
//-------- AND term
func newAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAnd,
evalFunc: evalAnd,
}
}
func evalAnd(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
leftBool, lok = toBool(leftValue)
rightBool, rok = toBool(rightValue)
if lok && rok {
v = leftBool && rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- OR term
func newOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priOr,
evalFunc: evalOr,
}
}
func evalOr(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
leftBool, lok = toBool(leftValue)
rightBool, rok = toBool(rightValue)
if lok && rok {
v = leftBool || rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymNot, newNotTerm)
registerTermConstructor(SymAnd, newAndTerm)
registerTermConstructor(SymOr, newOrTerm)
}

46
operator-fact.go Normal file
View File

@ -0,0 +1,46 @@
// operator-fact.go
package expr
import "fmt"
//-------- fact term
func newFactTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindInteger,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priFact,
evalFunc: evalFact,
}
}
func evalFact(ctx exprContext, self *term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
return
}
if isInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 {
f := int64(1)
for k := int64(1); k <= i; k++ {
f *= k
}
v = f
} else {
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
}
} else {
err = self.errIncompatibleType(leftValue)
}
return
}
// init
func init() {
registerTermConstructor(SymExclamation, newFactTerm)
}

95
operator-prod.go Normal file
View File

@ -0,0 +1,95 @@
// operator-prod.go
package expr
import (
"errors"
"strings"
)
//-------- multiply term
func newMultiplyTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalMultiply,
}
}
func evalMultiply(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isString(leftValue) && isInteger(rightValue) {
s, _ := leftValue.(string)
n, _ := rightValue.(int64)
v = strings.Repeat(s, int(n))
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) * numAsFloat(rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt * rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- divide term
func newDivideTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalDivide,
}
}
func evalDivide(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
} else {
v = numAsFloat(leftValue) / d
}
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = errors.New("division by zero")
} else {
v = leftInt / rightInt
}
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymStar, newMultiplyTerm)
registerTermConstructor(SymSlash, newDivideTerm)
}

220
operator-rel.go Normal file
View File

@ -0,0 +1,220 @@
// operator-rel.go
package expr
//-------- equal term
func newEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalEqual,
}
}
func evalEqual(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li == ri
} else {
v = numAsFloat(leftValue) == numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls == rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- not equal term
func newNotEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalNotEqual,
}
}
func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
}
return
}
// func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
// if isNumber(leftValue) && isNumber(rightValue) {
// if isInteger(leftValue) && isInteger(rightValue) {
// li, _ := leftValue.(int64)
// ri, _ := rightValue.(int64)
// v = li != ri
// } else {
// v = numAsFloat(leftValue) != numAsFloat(rightValue)
// }
// } else if isString(leftValue) && isString(rightValue) {
// ls, _ := leftValue.(string)
// rs, _ := rightValue.(string)
// v = ls != rs
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
//-------- less term
func newLessTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalLess,
}
}
func evalLess(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li < ri
} else {
v = numAsFloat(leftValue) < numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls < rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- less or equal term
func newLessEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalLessEqual,
}
}
func evalLessEqual(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li <= ri
} else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls <= rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- greater term
func newGreaterTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalGreater,
}
}
func evalGreater(ctx exprContext, self *term) (v any, err error) {
if v, err = evalLessEqual(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
}
return
}
//-------- greater or equal term
func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalGreaterEqual,
}
}
func evalGreaterEqual(ctx exprContext, self *term) (v any, err error) {
if v, err = evalLess(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
}
return
}
// init
func init() {
registerTermConstructor(SymDoubleEqual, newEqualTerm)
registerTermConstructor(SymNotEqual, newNotEqualTerm)
registerTermConstructor(SymLess, newLessTerm)
registerTermConstructor(SymLessOrEqual, newLessEqualTerm)
registerTermConstructor(SymGreater, newGreaterTerm)
registerTermConstructor(SymGreaterOrEqual, newGreaterEqualTerm)
}

61
operator-sign.go Normal file
View File

@ -0,0 +1,61 @@
// operator-sign.go
package expr
//-------- plus sign term
func newPlusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalSign,
}
}
func newMinusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalSign,
}
}
func evalSign(ctx exprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
return
}
if isFloat(rightValue) {
if self.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64)
v = -f
} else {
v = rightValue
}
} else if isInteger(rightValue) {
if self.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64)
v = -i
} else {
v = rightValue
}
} else {
err = self.errIncompatibleType(rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymUnchangeSign, newPlusSignTerm)
registerTermConstructor(SymChangeSign, newMinusSignTerm)
}

84
operator-sum.go Normal file
View File

@ -0,0 +1,84 @@
// operator-sum.go
package expr
import (
"fmt"
)
//-------- plus term
func newPlusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
evalFunc: evalPlus,
}
}
func evalPlus(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt + rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- minus term
func newMinusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
evalFunc: evalMinus,
}
}
func evalMinus(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt - rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymPlus, newPlusTerm)
registerTermConstructor(SymMinus, newMinusTerm)
}

76
parser.go Normal file
View File

@ -0,0 +1,76 @@
// parser.go
package expr
import (
"fmt"
)
//-------- parser
type parser struct {
ctx exprContext
}
func NewParser(ctx exprContext) (p *parser) {
p = &parser{
ctx: ctx,
}
return p
}
func (self *parser) parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
tree = NewAst()
firstToken := true
for tk := scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
//fmt.Println("Token:", tk)
if firstToken && (tk.Sym == SymMinus || tk.Sym == SymPlus) {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else {
tk.Sym = SymUnchangeSign
}
}
firstToken = false
if tk.Sym == SymOpenRound {
var subTree *ast
if subTree, err = self.parse(scanner, SymClosedRound); err == nil {
subTree.root.priority = priValue
tree.addTerm(subTree.root)
}
} else if tk.Sym == SymFunction {
name, _ := tk.Value.(string)
funcObj := self.ctx.GetFuncInfo(name)
if funcObj == nil {
err = fmt.Errorf("unknown function %q", name)
break
}
maxArgs := funcObj.MaxArgs()
if maxArgs < 0 {
maxArgs = funcObj.MinArgs() + 10
}
args := make([]*term, 0, maxArgs)
lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parse(scanner, SymComma, SymClosedRound); err == nil {
args = append(args, subTree.root)
} else {
break
}
lastSym = scanner.Previous().Sym
}
if err == nil {
// TODO Check arguments
t := newFuncTerm(tk, args)
tree.addTerm(t)
}
} else {
err = tree.addToken(tk)
}
}
return
}

215
parser_test.go Normal file
View File

@ -0,0 +1,215 @@
// parser_test.go
package expr
import (
"errors"
"fmt"
"strings"
"testing"
)
type testContext struct {
store map[string]any
}
func newTestContext() *testContext {
return &testContext{
store: make(map[string]any),
}
}
func (ctx *testContext) GetValue(varName string) (v any, exists bool) {
v, exists = ctx.store[varName]
return
}
func (ctx *testContext) SetValue(varName string, value any) {
ctx.store[varName] = value
}
func (ctx *testContext) GetFuncInfo(name string) (f exprFunc) {
if name == "ADD" {
f = &testAddFunc{}
}
return
}
func (ctx *testContext) Call(name string, args []any) (result any, err error) {
if name == "ADD" {
funcObj := &testAddFunc{}
result, err = funcObj.Invoke(ctx, args)
} else {
err = fmt.Errorf("unknown function %q", name)
}
return
}
type testAddFunc struct {
}
func (self *testAddFunc) Name() string {
return "ADD"
}
func (self *testAddFunc) MinArgs() int {
return 1
}
func (self *testAddFunc) MaxArgs() int {
return -1
}
func (self *testAddFunc) Invoke(ctx exprContext, args []any) (result any, err error) {
var sumAsFloat = false
var floatSum float64 = 0.0
var intSum int64 = 0
for i, v := range args {
if !isNumber(v) {
err = fmt.Errorf("add(): param nr %d has wrong type %T, number expected", i+1, v)
break
}
if !sumAsFloat && isFloat(v) {
sumAsFloat = true
floatSum = float64(intSum)
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil {
if sumAsFloat {
result = floatSum
} else {
result = intSum
}
}
return
}
func TestParser(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
ctx := newTestContext()
ctx.SetValue("var1", int64(123))
ctx.SetValue("var2", "abc")
inputs := []inputType{
{`123`, int64(123), nil},
{`1.`, float64(1.0), nil},
{`1.E-2`, float64(0.01), nil},
{`1E2`, float64(100), nil},
/* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil},
/* 3 */ {`3 != 4`, true, nil},
/* 4 */ {`4 == (3-1)*(10/5)`, true, nil},
/* 5 */ {`3 <= 4`, true, nil},
/* 6 */ {`3 < 4`, true, nil},
/* 7 */ {`4 < 3`, false, nil},
/* 8 */ {`1+5 < 4`, false, nil},
/* 9 */ {`3 > 4`, false, nil},
/* 10 */ {`4 >= 4`, true, nil},
/* 11 */ {`4 > 3`, true, nil},
/* 12 */ {`1+5 > 4`, true, nil},
/* 13 */ {`true`, true, nil},
/* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil},
/* 24 */ {"+1", int64(1), nil},
/* 25 */ {"-1", int64(-1), nil},
/* 26 */ {"435 + 105 * 2 - 1", int64(644), nil},
/* 27 */ {"200 / 2 - 1", int64(99), nil},
/* 28 */ {"(1+1)", int64(2), nil},
/* 29 */ {"-(1+1)", int64(-2), nil},
/* 30 */ {"-(-2+1)", int64(1), nil},
/* 31 */ {"(1+1)*5", int64(10), nil},
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
/* 33 */ {`add(1,2,3)`, int64(6), nil},
/* 34 */ {`mul(1,2,3)`, nil, errors.New(`unknown function "MUL"`)},
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 37 */ {`add(add(1+4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 39 */ {`(((1)))`, int64(1), nil},
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
/* 42 */ {`"s" + true`, nil, errors.New(`left operand 's' [string] is not compatible with right operand 'true' [bool] with respect to operator "+"`)},
/* 43 */ {`+false`, nil, errors.New(`prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 44 */ {`false // very simple expression`, false, nil},
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`infix operator "+" requires two operands, got 1`)},
/* 46 */ {"", nil, errors.New(`empty expression`)},
/* 47 */ {"4!", int64(24), nil},
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
/* 49 */ {"-4!", int64(-24), nil},
/* 50 */ {"1.5 < 7", true, nil},
/* 51 */ {"1.5 > 7", false, nil},
/* 52 */ {"1.5 <= 7", true, nil},
/* 53 */ {"1.5 >= 7", false, nil},
/* 54 */ {"1.5 != 7", true, nil},
/* 55 */ {"1.5 == 7", false, nil},
/* 56 */ {`"1.5" < "7"`, true, nil},
/* 57 */ {`"1.5" > "7"`, false, nil},
/* 58 */ {`"1.5" == "7"`, false, nil},
/* 59 */ {`"1.5" != "7"`, true, nil},
/* 60 */ {"1.5 < ", nil, errors.New(`infix operator "<" requires two operands, got 1`)},
/* 61 */ {"1.5 > ", nil, errors.New(`infix operator ">" requires two operands, got 1`)},
/* 62 */ {"1.5 <= ", nil, errors.New(`infix operator "<=" requires two operands, got 1`)},
/* 63 */ {"1.5 >= ", nil, errors.New(`infix operator ">=" requires two operands, got 1`)},
/* 64 */ {"1.5 != ", nil, errors.New(`infix operator "!=" requires two operands, got 1`)},
/* 65 */ {"1.5 == ", nil, errors.New(`infix operator "==" requires two operands, got 1`)},
/* 66 */ {`"1.5" < `, nil, errors.New(`infix operator "<" requires two operands, got 1`)},
/* 67 */ {`"1.5" > `, nil, errors.New(`infix operator ">" requires two operands, got 1`)},
/* 68 */ {`"1.5" == `, nil, errors.New(`infix operator "==" requires two operands, got 1`)},
/* 69 */ {`"1.5" != `, nil, errors.New(`infix operator "!=" requires two operands, got 1`)},
/* 70 */ {"+1.5", float64(1.5), nil},
/* 71 */ {"+", nil, errors.New(`prefix operator "+" requires one operand`)},
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
}
parser := NewParser(ctx)
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
if input.wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
} else {
t.Log(fmt.Sprintf("[-]Test nr %2d -- %q", i+1, input.source))
}
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
if expr, gotErr = parser.parse(scanner); gotErr == nil {
gotResult, gotErr = expr.eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
}
}
}
}

552
scanner.go Normal file
View File

@ -0,0 +1,552 @@
// expr project scanner.go
package expr
import (
"bufio"
"errors"
"io"
"strconv"
"strings"
)
type scanner struct {
current *Token
prev *Token
stream *bufio.Reader
row int
column int
translations map[Symbol]Symbol
}
func NewScanner(s io.Reader, translations map[Symbol]Symbol) (inst *scanner) {
inst = &scanner{
stream: bufio.NewReader(s),
row: 1,
column: 1,
translations: translations,
}
inst.current = inst.fetchNextToken()
return inst
}
func DefaultTranslations() map[Symbol]Symbol {
return map[Symbol]Symbol{
SymDoubleAmpersand: SymAnd,
SymKwAnd: SymAnd,
SymDoubleVertBar: SymOr,
SymKwOr: SymOr,
SymKwNot: SymNot,
SymLessGreater: SymNotEqual,
}
}
// func (self *scanner) Current() *Token {
// return self.current
// }
func (self *scanner) readChar() (ch byte, err error) {
if ch, err = self.stream.ReadByte(); err == nil {
if ch == '\n' {
self.row++
self.column = 0
} else {
self.column++
}
}
return
}
func (self *scanner) unreadChar() (err error) {
if err = self.stream.UnreadByte(); err == nil {
if self.column--; self.column == 0 {
if self.row--; self.row == 0 {
err = errors.New("unread beyond the stream boundary")
} else {
self.column = 1
}
}
}
return
}
func (self *scanner) Previous() *Token {
return self.prev
}
func (self *scanner) Next() (tk *Token) {
self.prev = self.current
tk = self.current
self.current = self.fetchNextToken()
return tk
}
func (self *scanner) fetchNextToken() (tk *Token) {
if err := self.skipBlanks(); err != nil {
return self.makeErrorToken(err)
}
escape := false
for {
ch, _ := self.readChar()
switch ch {
case '+':
if next, _ := self.peek(); next == '+' {
tk = self.moveOn(SymDoublePlus, ch, next)
} else if next == '=' {
tk = self.moveOn(SymPlusEqual, ch, next)
} else {
tk = self.makeToken(SymPlus, ch)
}
case '-':
if next, _ := self.peek(); next == '-' {
tk = self.moveOn(SymDoubleMinus, ch, next)
} else if next == '=' {
tk = self.moveOn(SymMinusEqual, ch, next)
} else {
tk = self.makeToken(SymMinus, ch)
}
case '*':
if next, _ := self.peek(); next == '*' {
tk = self.moveOn(SymDoubleStar, ch, next)
// } else if next == '/' {
// tk = self.moveOn(SymClosedComment, ch, next)
} else {
tk = self.makeToken(SymStar, ch)
}
case '/':
if next, _ := self.peek(); next == '*' {
self.readChar()
tk = self.fetchBlockComment()
} else if next == '/' {
self.readChar()
tk = self.fetchOnLineComment()
} else {
tk = self.makeToken(SymSlash, ch)
}
case '\\':
if escape {
tk = self.makeToken(SymBackSlash, ch)
escape = false
} else {
escape = true
}
case '|':
if next, _ := self.peek(); next == '|' {
tk = self.moveOn(SymDoubleVertBar, ch, next)
} else {
tk = self.makeToken(SymVertBar, ch)
}
case ',':
tk = self.makeToken(SymComma, ch)
case ':':
tk = self.makeToken(SymColon, ch)
case ';':
tk = self.makeToken(SymSemiColon, ch)
case '.':
if next, _ := self.peek(); next >= '0' && next <= '9' {
tk = self.parseNumber(ch)
} else {
tk = self.makeToken(SymDot, ch)
}
case '\'':
tk = self.makeToken(SymQuote, ch)
case '"':
if escape {
tk = self.makeToken(SymDoubleQuote, ch)
escape = false
} else {
tk = self.fetchString()
}
case '`':
tk = self.makeToken(SymBackTick, ch)
case '!':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymNotEqual, ch, next)
} else {
tk = self.makeToken(SymExclamation, ch)
}
case '?':
tk = self.makeToken(SymQuestion, ch)
case '&':
if next, _ := self.peek(); next == '&' {
tk = self.moveOn(SymDoubleAmpersand, ch, next)
} else {
tk = self.makeToken(SymAmpersand, ch)
}
case '%':
tk = self.makeToken(SymPercent, ch)
case '#':
tk = self.makeToken(SymHash, ch)
case '@':
tk = self.makeToken(SymAt, ch)
case '_':
tk = self.makeToken(SymUndescore, ch)
case '=':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymDoubleEqual, ch, next)
} else {
tk = self.makeToken(SymEqual, ch)
}
case '<':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymLessOrEqual, ch, next)
} else if next == '>' {
tk = self.moveOn(SymLessGreater, ch, next)
} else {
tk = self.makeToken(SymLess, ch)
}
case '>':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymGreaterOrEqual, ch, next)
} else {
tk = self.makeToken(SymGreater, ch)
}
case '$':
tk = self.makeToken(SymDollar, ch)
case '(':
tk = self.makeToken(SymOpenRound, ch)
case ')':
tk = self.makeToken(SymClosedRound, ch)
case '[':
tk = self.makeToken(SymOpenSquare, ch)
case ']':
tk = self.makeToken(SymClosedSquare, ch)
case '{':
tk = self.makeToken(SymOpenBrace, ch)
case '}':
tk = self.makeToken(SymClosedBrace, ch)
case 0:
if escape {
tk = self.makeErrorToken(errors.New("incomplete escape sequence"))
}
escape = false
default:
if ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
tk = self.fetchIdentifier(ch)
} else if ch >= '0' && ch <= '9' {
tk = self.parseNumber(ch)
}
}
if !escape {
break
}
}
return
}
func (self *scanner) sync(err error) error {
if err == nil {
err = self.unreadChar()
}
return err
}
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
var err error
var ch byte
var sym Symbol = SymInteger
var value any
var sb strings.Builder
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
if ch == '.' {
sym = SymFloat
sb.WriteByte(ch)
ch, err = self.readChar()
if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
}
}
if ch == 'e' || ch == 'E' {
sym = SymFloat
sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil {
if ch == '+' || ch == '-' {
sb.WriteByte(ch)
ch, err = self.readChar()
}
if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
//err = self.sync(err)
} else {
err = errors.New("expected integer exponent")
}
}
// } else {
// err = self.sync(err)
}
if err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
} else {
err = self.sync(err)
txt := sb.String()
if sym == SymFloat {
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 {
value, err = strconv.ParseInt(txt, 10, 64)
}
if err == nil {
tk = self.makeValueToken(sym, txt, value)
} else {
tk = self.makeErrorToken(err)
}
}
return
}
func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
var err error
var sb strings.Builder
for ch := firstCh; err == nil && (ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); ch, err = self.readChar() {
sb.WriteByte(ch)
}
if err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
} else if err = self.sync(err); err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
} else {
txt := sb.String()
uptxt := strings.ToUpper(txt)
if sym, ok := keywords[uptxt]; ok {
tk = self.makeKeywordToken(sym, uptxt)
} else if uptxt == `TRUE` {
tk = self.makeValueToken(SymBool, txt, true)
} else if uptxt == `FALSE` {
tk = self.makeValueToken(SymBool, txt, false)
} else if ch, _ := self.peek(); ch == '(' {
self.readChar()
tk = self.makeValueToken(SymFunction, txt+"(", uptxt)
} else {
tk = self.makeValueToken(SymIdentifier, txt, txt)
}
}
// if err != nil && err != io.EOF {
// tk = self.makeErrorToken(err)
// } else if err = self.sync(err); err != nil && err != io.EOF {
// tk = self.makeErrorToken(err)
// } else {
// txt := sb.String()
// uptxt := strings.ToUpper(txt)
// if sym, ok := keywords[uptxt]; ok {
// tk = self.makeValueToken(sym, txt, "")
// } else {
// tk = self.makeValueToken(SymIdentifier, txt, txt)
// }
// }
return
}
func (self *scanner) fetchBlockComment() *Token {
return self.fetchUntil(SymComment, false, '*', '/')
}
func (self *scanner) fetchOnLineComment() *Token {
return self.fetchUntil(SymComment, true, '\n')
}
// func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
// var err error
// var ch byte
// var sb strings.Builder
// var value string
// end := string(endings)
// endReached := false
// for ch, err = self.readChar(); err == nil && !endReached; {
// sb.WriteByte(ch)
// if sb.Len() >= len(end) && strings.HasSuffix(sb.String(), end) {
// value = sb.String()[0 : sb.Len()-len(end)]
// endReached = true
// } else {
// ch, err = self.readChar()
// }
// }
// if !endReached && allowEos {
// value = sb.String()
// endReached = true
// }
// if endReached {
// tk = self.makeValueToken(sym, "", value)
// } else {
// tk = self.makeErrorToken(err)
// }
// return
// }
// func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
// var err error
// var ch byte
// var sb strings.Builder
// var value string
// end := make([]byte, len(endings))
// length := 0
// endReached := false
// for ch, err = self.readChar(); err == nil && !endReached; {
// sb.WriteByte(ch)
// if length == len(endings) {
// for i := 0; i < length-1; i++ {
// end[i] = end[i+1]
// }
// length--
// }
// end[length] = ch
// length++
// if bytes.Equal(endings, end) {
// value = sb.String()[0 : sb.Len()-len(end)]
// endReached = true
// } else {
// ch, err = self.readChar()
// }
// }
// if !endReached && allowEos {
// value = sb.String()
// endReached = true
// }
// if endReached {
// tk = self.makeValueToken(sym, "", value)
// } else {
// tk = self.makeErrorToken(err)
// }
// return
// }
func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
var err error
var ch byte
var sb strings.Builder
var value string
ring := NewByteSlider(len(endings))
endReached := false
for ch, err = self.readChar(); err == nil && !endReached; {
sb.WriteByte(ch)
ring.PushEnd(ch)
if ring.Equal(endings) {
value = sb.String()[0 : sb.Len()-len(endings)]
endReached = true
} else {
ch, err = self.readChar()
}
}
if !endReached && allowEos {
value = sb.String()
endReached = true
}
if endReached {
tk = self.makeValueToken(sym, "", value)
} else {
tk = self.makeErrorToken(err)
}
return
}
func (self *scanner) fetchString() (tk *Token) {
var err error
var ch, prev byte
var sb strings.Builder
for ch, err = self.readChar(); err == nil; ch, err = self.readChar() {
if prev == '\\' {
switch ch {
case '"':
sb.WriteByte('"')
case 'n':
sb.WriteByte('\n')
case 'r':
sb.WriteByte('\r')
case 't':
sb.WriteByte('\t')
case '\\':
sb.WriteByte('\\')
default:
sb.WriteByte(ch)
}
prev = 0
} else if ch == '"' {
break
} else {
prev = ch
if ch != '\\' {
sb.WriteByte(ch)
}
}
}
if err != nil {
if err == io.EOF {
tk = self.makeErrorToken(errors.New("missing string termination \""))
} else {
tk = self.makeErrorToken(err)
}
} else {
txt := sb.String()
tk = self.makeValueToken(SymString, `"`+txt+`"`, txt)
}
return
}
func (self *scanner) peek() (next byte, err error) {
var one []byte
if one, err = self.stream.Peek(1); err == nil {
next = one[0]
}
return
}
func (self *scanner) skipBlanks() (err error) {
var one []byte
for one, err = self.stream.Peek(1); err == nil && one[0] <= 32; one, err = self.stream.Peek(1) {
self.readChar()
}
return
}
func (self *scanner) translate(sym Symbol) Symbol {
if self.translations != nil {
if translatedSym, ok := self.translations[sym]; ok {
return translatedSym
}
}
return sym
}
func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.translate(sym), string(chars))
for i := 1; i < len(chars); i++ {
self.readChar()
}
return
}
func (self *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.translate(sym), string(chars))
return
}
func (self *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
tk = NewToken(self.translate(sym), upperCaseKeyword)
return
}
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
tk = NewValueToken(self.translate(sym), source, value)
return
}
func (self *scanner) makeErrorToken(err error) *Token {
return NewErrorToken(self.row, self.column, err)
}

91
scanner_test.go Normal file
View File

@ -0,0 +1,91 @@
// scanner_test.go
package expr
import (
"errors"
"fmt"
"strings"
"testing"
)
func TestScanner(t *testing.T) {
type inputType struct {
source string
wantSym Symbol
wantValue any
wantErr error
}
inputs := []inputType{
/* 1 */ {`123`, SymInteger, int64(123), nil},
/* 2 */ {"=", SymEqual, nil, nil},
/* 3 */ {`--`, SymDoubleMinus, nil, nil},
/* 4 */ {`++`, SymDoublePlus, nil, nil},
/* 5 */ {`**`, SymDoubleStar, nil, nil},
/* 6 */ {`&&`, SymDoubleAmpersand, nil, nil},
/* 7 */ {`||`, SymDoubleVertBar, nil, nil},
/* 8 */ {`NOT`, SymKwNot, nil, nil},
/* 9 */ {`AND`, SymKwAnd, nil, nil},
/* 10 */ {`or`, SymKwOr, nil, nil},
/* 11 */ {`+=`, SymPlusEqual, nil, nil},
/* 12 */ {`-=`, SymMinusEqual, nil, nil},
/* 13 */ {`|`, SymVertBar, nil, nil},
/* 14 */ {`:`, SymColon, nil, nil},
/* 15 */ {`;`, SymSemiColon, nil, nil},
/* 16 */ {`.`, SymDot, nil, nil},
/* 17 */ {`.5`, SymFloat, float64(0.5), nil},
/* 18 */ {`\\`, SymBackSlash, nil, nil},
/* 19 */ {"`", SymBackTick, nil, nil},
/* 20 */ {"?", SymQuestion, nil, nil},
/* 21 */ {"&", SymAmpersand, nil, nil},
/* 22 */ {"@", SymAt, nil, nil},
/* 23 */ {`#`, SymHash, nil, nil},
/* 24 */ {`%`, SymPercent, nil, nil},
/* 25 */ {`'`, SymQuote, nil, nil},
/* 26 */ {`\"`, SymDoubleQuote, nil, nil},
/* 27 */ {`_`, SymUndescore, nil, nil},
/* 28 */ {`<>`, SymLessGreater, nil, nil},
/* 29 */ {`[`, SymOpenSquare, nil, nil},
/* 30 */ {`]`, SymClosedSquare, nil, nil},
/* 31 */ {`{`, SymOpenBrace, nil, nil},
/* 32 */ {`}`, SymClosedBrace, nil, nil},
/* 33 */ {`(`, SymOpenRound, nil, nil},
/* 34 */ {`)`, SymClosedRound, nil, nil},
/* 35 */ {`1E+2`, SymFloat, float64(100), nil},
/* 36 */ {`1E+x`, SymError, errors.New("expected integer exponent"), nil},
/* 37 */ {`$`, SymDollar, nil, nil},
/* 38 */ {`\`, SymError, errors.New("incomplete escape sequence"), nil},
/* 39 */ {`"string"`, SymString, "string", nil},
/* 39 */ {`identifier`, SymIdentifier, "identifier", nil},
}
for i, input := range inputs {
if input.wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
} else {
t.Log(fmt.Sprintf("[-]Test nr %2d -- %q", i+1, input.source))
}
r := strings.NewReader(input.source)
scanner := NewScanner(r, nil)
if tk := scanner.Next(); tk == nil {
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
if tk.Sym == SymError && input.wantSym == tk.Sym {
if tkErr, tkOk := tk.Value.(error); tkOk {
if inputErr, inputOk := input.wantValue.(error); inputOk {
if tkErr.Error() != inputErr.Error() {
t.Errorf("%d: %q -> got-error = %v, want-error: %v", i+1,
input.source, tk.Value, input.wantValue)
}
}
}
} else {
t.Errorf("%d: %q -> got = %v (value=%v [%T), want %v (value=%v [%T)", i+1,
input.source, tk.Sym, tk.Value, tk.Value, input.wantSym, input.wantValue, input.wantValue)
}
}
}
}

82
symbol.go Normal file
View File

@ -0,0 +1,82 @@
// Symbol.go
package expr
type Symbol int16
const (
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
SymNone // 0: Null value for variable of type symbol
SymError // 1: Error reading from stream
SymEos // 2: End of stream
SymMinus // 3: '-'
SymMinusEqual // 4: '-='
SymDoubleMinus // 5: '--'
SymPlus // 6: '+'
SymPlusEqual // 7: '+='
SymDoublePlus // 8: '++'
SymStar // 9: '*'
SymDoubleStar // 10: '**'
SymSlash // 11: '/'
SymBackSlash // 12: '\'
SymVertBar // 13: '|'
SymDoubleVertBar // 14: '||'
SymComma // 15: ','
SymColon // 16: ':'
SymSemiColon // 17: ';'
SymDot // 18: '.'
SymQuote // 19: '\''
SymDoubleQuote // 20: '"'
SymBackTick // 0: '`'
SymExclamation // 0: '!'
SymQuestion // 0: '?'
SymAmpersand // 0: '&&'
SymDoubleAmpersand // 0: '&&'
SymPercent // 0: '%'
SymAt // 0: '@'
SymUndescore // 0: '_'
SymEqual // 0: '='
SymDoubleEqual // 0: '=='
SymLess // 0: '<'
SymLessOrEqual // 0: '<='
SymGreater // 0: '>'
SymGreaterOrEqual // 0: '>='
SymLessGreater // 0: '<>'
SymNotEqual // 0: '!='
SymDollar // 0: '$'
SymHash // 0: '#'
SymOpenRound // 0: '('
SymClosedRound // 0: ')'
SymOpenSquare // 0: '['
SymClosedSquare // 0: ']'
SymOpenBrace // 0: '{'
SymClosedBrace // 0: '}'
SymChangeSign
SymUnchangeSign
SymIdentifier
SymBool
SymInteger
SymFloat
SymString
SymKwAnd
SymKwNot
SymKwOr
SymOr
SymAnd
SymNot
SymComment
SymFunction
// SymOpenComment // 0: '/*'
// SymClosedComment // 0: '*/'
// SymOneLineComment // 0: '//'
)
var keywords map[string]Symbol
func init() {
//keywords = make(map[string]Symbol)
keywords = map[string]Symbol{
"AND": SymKwAnd,
"OR": SymKwOr,
"NOT": SymKwNot,
}
}

View File

@ -0,0 +1,25 @@
// op-registry.go
package expr
const initialRegistryCapacity = 10
type termContructor func(tk *Token) *term
var constructorRegistry map[Symbol]termContructor = nil
func registerTermConstructor(sym Symbol, constructor termContructor) {
if constructorRegistry == nil {
constructorRegistry = make(map[Symbol]termContructor, initialRegistryCapacity)
}
constructorRegistry[sym] = constructor
}
func newTerm(tk *Token, parent *term) (inst *term) {
if constructorRegistry != nil {
if construct, exists := constructorRegistry[tk.Sym]; exists {
inst = construct(tk)
inst.setParent(parent)
}
}
return
}

229
term.go Normal file
View File

@ -0,0 +1,229 @@
// term.go
package expr
import (
"fmt"
"strings"
)
type termKind uint16
const (
kindUnknown termKind = iota
kindBool
kindInteger
kindFloat
kindString
kindIdentifier
)
type termClass uint16
const (
classNull termClass = iota
classConst
classVar
classFunc
classOperator
)
type termPriority uint32
const (
priNone termPriority = iota
priOr
priAnd
priNot
priRelational
priSum
priProduct
priSign
priFact
priValue
)
type termPosition uint8
const (
posLeaf termPosition = iota
posInfix
posPrefix
posPostfix
)
type evalFuncType func(ctx exprContext, self *term) (v any, err error)
// type iterm interface {
// getClass() termClass
// getKind() termKind
// compute(ctx exprContext) (v any, err error)
// // isOperator() bool
// // isOperand() bool
// source() string
// setParent(parentNode term)
// }
type term struct {
tk Token
class termClass
kind termKind
parent *term
children []*term
position termPosition // operator position: infix, prefix, suffix
priority termPriority // operator priority: higher value means higher priority
evalFunc evalFuncType
}
func (self *term) String() string {
var sb strings.Builder
self.toString(&sb)
return sb.String()
}
func (self *term) toString(sb *strings.Builder) {
if self.position == posLeaf {
sb.WriteString(self.tk.String())
} else {
sb.WriteByte('[')
sb.WriteString(self.tk.String())
if self.children != nil {
sb.WriteByte('(')
for i, c := range self.children {
if i > 0 {
sb.WriteByte(' ')
}
c.toString(sb)
}
sb.WriteByte(')')
}
sb.WriteByte(']')
}
}
func (self *term) getChildrenCount() (count int) {
if self.position == posLeaf || self.children == nil {
count = 0
} else {
count = len(self.children)
}
return
}
func (self *term) getRoom() (room int) {
switch self.position {
case posLeaf:
room = 0
case posInfix:
room = 2
case posPostfix, posPrefix:
room = 1
default:
panic("Invalid node position")
}
return
}
func (self *term) isComplete() bool {
return self.getChildrenCount() == self.getRoom()
}
func (self *term) removeLastChild() (child *term) {
if self.children != nil {
child = self.children[len(self.children)-1]
self.children = self.children[0 : len(self.children)-1]
} else {
panic("Can't get last child")
}
return
}
func (self *term) isLeaf() bool {
return self.position == posLeaf
}
// func (self *term) getKind() termKind {
// return self.kind
// }
// func (self *term) getClass() termClass {
// return self.class
// }
func (self *term) getPriority() termPriority {
return self.priority
}
func (self *term) setParent(parent *term) {
self.parent = parent
if parent != nil && len(parent.children) < cap(parent.children) {
parent.children = append(parent.children, self)
}
}
// func (self *term) isOperand() bool {
// return self.getClass() != classOperator
// }
// func (self *term) isOperator() bool {
// return self.getClass() == classOperator
// }
func (self *term) source() string {
return self.tk.source
}
func (self *term) compute(ctx exprContext) (v any, err error) {
if self.evalFunc == nil {
err = fmt.Errorf("undfined eval-func for %v term type", self.kind)
} else {
v, err = self.evalFunc(ctx, self)
}
return
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
return fmt.Errorf(
"left operand '%v' [%T] is not compatible with right operand '%v' [%T] with respect to operator %q",
leftValue, leftValue,
rightValue, rightValue,
self.source())
}
func (self *term) errIncompatibleType(value any) error {
return fmt.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%T]", self.source(), value, value)
}
func (self *term) checkOperands() (err error) {
switch self.position {
case posInfix:
if self.children == nil || len(self.children) != 2 || self.children[0] == nil || self.children[1] == nil {
err = fmt.Errorf("infix operator %q requires two operands, got %d", self.source(), self.getChildrenCount())
}
case posPrefix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
err = fmt.Errorf("prefix operator %q requires one operand", self.tk.String())
}
case posPostfix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
err = fmt.Errorf("postfix operator %q requires one operand", self.tk.String())
}
}
return
}
func (self *term) evalInfix(ctx exprContext) (leftValue, rightValue any, err error) {
if err = self.checkOperands(); err == nil {
if leftValue, err = self.children[0].compute(ctx); err == nil {
rightValue, err = self.children[1].compute(ctx)
}
}
return
}
func (self *term) evalPrefix(ctx exprContext) (rightValue any, err error) {
if err = self.checkOperands(); err == nil {
rightValue, err = self.children[0].compute(ctx)
}
return
}

41
term_test.go Normal file
View File

@ -0,0 +1,41 @@
// term_test.go
package expr
import (
"fmt"
"testing"
)
func TestString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr == nil {
fmt.Println("Tree:", tree)
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestGetRoom(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root room:", tree.root.getRoom())
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestGetChildrenCount(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root children count:", tree.root.getChildrenCount())
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}

55
token.go Normal file
View File

@ -0,0 +1,55 @@
// token.go
package expr
import (
"fmt"
"io"
"slices"
)
type Token struct {
Sym Symbol
source string
Value any
}
func (tk *Token) DevString() string {
if tk.Value != nil {
return fmt.Sprintf("[%d]%q{%v}", tk.Sym, tk.source, tk.Value)
}
return fmt.Sprintf("[%d]%q{}", tk.Sym, tk.source)
}
func (tk *Token) String() string {
if tk.Value != nil {
return fmt.Sprintf("%#v", tk.Value)
}
return fmt.Sprintf("%s", tk.source)
}
func NewToken(sym Symbol, source string) *Token {
return &Token{Sym: sym, source: source}
}
func NewValueToken(sym Symbol, source string, value any) *Token {
return &Token{Sym: sym, source: source, Value: value}
}
func NewErrorToken(row, col int, err error) *Token {
if err == io.EOF {
return NewToken(SymEos, "")
}
return NewValueToken(SymError, fmt.Sprintf("[%d:%d]", row, col), err)
}
func (tk *Token) IsEos() bool {
return tk.Sym == SymEos
}
func (tk *Token) IsError() bool {
return tk.Sym == SymError
}
func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
}

15
token_test.go Normal file
View File

@ -0,0 +1,15 @@
// token_test.go
package expr
import (
"fmt"
"testing"
)
func TestDevString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
fmt.Println("Token '100':", tk1.DevString())
fmt.Println("Token '+':", tk2.DevString())
}

51
utils.go Normal file
View File

@ -0,0 +1,51 @@
// utils.go
package expr
func isString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
func isInteger(v any) (ok bool) {
_, ok = v.(int64)
return ok
}
func isFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v)
}
func isNumberString(v any) (ok bool) {
return isString(v) || isNumber(v)
}
func numAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
i, _ := v.(int64)
f = float64(i)
}
return
}
func toBool(v any) (b bool, ok bool) {
ok = true
switch x := v.(type) {
case string:
b = len(x) > 0
case float64:
b = x != 0.0
case int64:
b = x != 0
case bool:
b = x
default:
ok = false
}
return
}