Added all source files
This commit is contained in:
parent
737696b71e
commit
522b880ca3
84
ast.go
Normal file
84
ast.go
Normal 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
50
ast_test.go
Normal 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
31
byte-ring.go
Normal 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
15
context.go
Normal 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
72
operand-const.go
Normal 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
33
operand-func.go
Normal 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
32
operand-var.go
Normal 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
106
operator-bool.go
Normal 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
46
operator-fact.go
Normal 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
95
operator-prod.go
Normal 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
220
operator-rel.go
Normal 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
61
operator-sign.go
Normal 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
84
operator-sum.go
Normal 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
76
parser.go
Normal 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
215
parser_test.go
Normal 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
552
scanner.go
Normal 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
91
scanner_test.go
Normal 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
82
symbol.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
25
term-constuctor-registry.go
Normal file
25
term-constuctor-registry.go
Normal 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
229
term.go
Normal 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
41
term_test.go
Normal 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
55
token.go
Normal 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
15
token_test.go
Normal 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
51
utils.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user