From 522b880ca37276c323f997fc882f62d4e5e7245a Mon Sep 17 00:00:00 2001
From: Celestino Amoroso <celestino.amoroso@gmail.com>
Date: Tue, 26 Mar 2024 07:00:53 +0100
Subject: [PATCH] Added all source files

---
 ast.go                      |  84 ++++++
 ast_test.go                 |  50 ++++
 byte-ring.go                |  31 ++
 context.go                  |  15 +
 operand-const.go            |  72 +++++
 operand-func.go             |  33 +++
 operand-var.go              |  32 +++
 operator-bool.go            | 106 +++++++
 operator-fact.go            |  46 +++
 operator-prod.go            |  95 +++++++
 operator-rel.go             | 220 ++++++++++++++
 operator-sign.go            |  61 ++++
 operator-sum.go             |  84 ++++++
 parser.go                   |  76 +++++
 parser_test.go              | 215 ++++++++++++++
 scanner.go                  | 552 ++++++++++++++++++++++++++++++++++++
 scanner_test.go             |  91 ++++++
 symbol.go                   |  82 ++++++
 term-constuctor-registry.go |  25 ++
 term.go                     | 229 +++++++++++++++
 term_test.go                |  41 +++
 token.go                    |  55 ++++
 token_test.go               |  15 +
 utils.go                    |  51 ++++
 24 files changed, 2361 insertions(+)
 create mode 100644 ast.go
 create mode 100644 ast_test.go
 create mode 100644 byte-ring.go
 create mode 100644 context.go
 create mode 100644 operand-const.go
 create mode 100644 operand-func.go
 create mode 100644 operand-var.go
 create mode 100644 operator-bool.go
 create mode 100644 operator-fact.go
 create mode 100644 operator-prod.go
 create mode 100644 operator-rel.go
 create mode 100644 operator-sign.go
 create mode 100644 operator-sum.go
 create mode 100644 parser.go
 create mode 100644 parser_test.go
 create mode 100644 scanner.go
 create mode 100644 scanner_test.go
 create mode 100644 symbol.go
 create mode 100644 term-constuctor-registry.go
 create mode 100644 term.go
 create mode 100644 term_test.go
 create mode 100644 token.go
 create mode 100644 token_test.go
 create mode 100644 utils.go

diff --git a/ast.go b/ast.go
new file mode 100644
index 0000000..22d53f7
--- /dev/null
+++ b/ast.go
@@ -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
+}
diff --git a/ast_test.go b/ast_test.go
new file mode 100644
index 0000000..b1a97cd
--- /dev/null
+++ b/ast_test.go
@@ -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)
+	}
+}
diff --git a/byte-ring.go b/byte-ring.go
new file mode 100644
index 0000000..7428727
--- /dev/null
+++ b/byte-ring.go
@@ -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)
+}
diff --git a/context.go b/context.go
new file mode 100644
index 0000000..bae6baa
--- /dev/null
+++ b/context.go
@@ -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)
+}
diff --git a/operand-const.go b/operand-const.go
new file mode 100644
index 0000000..30ce0e0
--- /dev/null
+++ b/operand-const.go
@@ -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)
+}
diff --git a/operand-func.go b/operand-func.go
new file mode 100644
index 0000000..ef54bee
--- /dev/null
+++ b/operand-func.go
@@ -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
+}
diff --git a/operand-var.go b/operand-var.go
new file mode 100644
index 0000000..abc484d
--- /dev/null
+++ b/operand-var.go
@@ -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)
+}
diff --git a/operator-bool.go b/operator-bool.go
new file mode 100644
index 0000000..3ab08b7
--- /dev/null
+++ b/operator-bool.go
@@ -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)
+}
diff --git a/operator-fact.go b/operator-fact.go
new file mode 100644
index 0000000..4c0050b
--- /dev/null
+++ b/operator-fact.go
@@ -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)
+}
diff --git a/operator-prod.go b/operator-prod.go
new file mode 100644
index 0000000..1502972
--- /dev/null
+++ b/operator-prod.go
@@ -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)
+}
diff --git a/operator-rel.go b/operator-rel.go
new file mode 100644
index 0000000..6e3d249
--- /dev/null
+++ b/operator-rel.go
@@ -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)
+}
diff --git a/operator-sign.go b/operator-sign.go
new file mode 100644
index 0000000..bea8f14
--- /dev/null
+++ b/operator-sign.go
@@ -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)
+}
diff --git a/operator-sum.go b/operator-sum.go
new file mode 100644
index 0000000..a4cb947
--- /dev/null
+++ b/operator-sum.go
@@ -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)
+}
diff --git a/parser.go b/parser.go
new file mode 100644
index 0000000..6f8d0e0
--- /dev/null
+++ b/parser.go
@@ -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
+}
diff --git a/parser_test.go b/parser_test.go
new file mode 100644
index 0000000..8a7a27b
--- /dev/null
+++ b/parser_test.go
@@ -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)
+			}
+		}
+	}
+}
diff --git a/scanner.go b/scanner.go
new file mode 100644
index 0000000..3ed1c1b
--- /dev/null
+++ b/scanner.go
@@ -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)
+}
diff --git a/scanner_test.go b/scanner_test.go
new file mode 100644
index 0000000..8a732be
--- /dev/null
+++ b/scanner_test.go
@@ -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)
+			}
+		}
+	}
+}
diff --git a/symbol.go b/symbol.go
new file mode 100644
index 0000000..f023b17
--- /dev/null
+++ b/symbol.go
@@ -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,
+	}
+}
diff --git a/term-constuctor-registry.go b/term-constuctor-registry.go
new file mode 100644
index 0000000..d7c699a
--- /dev/null
+++ b/term-constuctor-registry.go
@@ -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
+}
diff --git a/term.go b/term.go
new file mode 100644
index 0000000..8e8fa3d
--- /dev/null
+++ b/term.go
@@ -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
+}
diff --git a/term_test.go b/term_test.go
new file mode 100644
index 0000000..1363a27
--- /dev/null
+++ b/term_test.go
@@ -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)
+	}
+}
diff --git a/token.go b/token.go
new file mode 100644
index 0000000..dacddb0
--- /dev/null
+++ b/token.go
@@ -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)
+}
diff --git a/token_test.go b/token_test.go
new file mode 100644
index 0000000..35bd5d1
--- /dev/null
+++ b/token_test.go
@@ -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())
+}
diff --git a/utils.go b/utils.go
new file mode 100644
index 0000000..673c221
--- /dev/null
+++ b/utils.go
@@ -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
+}