first commit

This commit is contained in:
Alexander Kiryukhin 2022-07-01 02:16:07 +03:00
commit cc4c01c69c
No known key found for this signature in database
GPG key ID: B0DA6283C40CB2CB
8 changed files with 883 additions and 0 deletions

536
defaults.go Normal file
View file

@ -0,0 +1,536 @@
package expression
import (
"fmt"
"go/token"
"strconv"
)
var DefaultOperators = map[token.Token]Operator{
token.ADD: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
if !a.IsNumber() {
return fmt.Errorf("Token %s must be number", a.Literal)
}
if !b.IsNumber() {
return fmt.Errorf("Token %s must be number", b.Literal)
}
n1, isInt1 := a.Int()
n2, isInt2 := b.Int()
switch {
case isInt1 && isInt2:
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 + n1),
Pos: b.Pos,
})
default:
stack.Push(Token{
Token: token.FLOAT,
Literal: strconv.FormatFloat((b.Float() + a.Float()), 'g', 5, 64),
Pos: b.Pos,
})
}
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.SUB: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
if !a.IsNumber() {
return fmt.Errorf("Token %s must be number", a.Literal)
}
if !b.IsNumber() {
return fmt.Errorf("Token %s must be number", b.Literal)
}
n1, isInt1 := a.Int()
n2, isInt2 := b.Int()
switch {
case isInt1 && isInt2:
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 - n1),
Pos: b.Pos,
})
default:
stack.Push(Token{
Token: token.FLOAT,
Literal: strconv.FormatFloat((b.Float() - a.Float()), 'g', 5, 64),
Pos: b.Pos,
})
}
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.MUL: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
if !a.IsNumber() {
return fmt.Errorf("Token %s must be number", a.Literal)
}
if !b.IsNumber() {
return fmt.Errorf("Token %s must be number", b.Literal)
}
n1, isInt1 := a.Int()
n2, isInt2 := b.Int()
switch {
case isInt1 && isInt2:
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 * n1),
Pos: b.Pos,
})
default:
stack.Push(Token{
Token: token.FLOAT,
Literal: strconv.FormatFloat((b.Float() * a.Float()), 'g', 5, 64),
Pos: b.Pos,
})
}
return nil
},
priority: 20,
isLeftAssoc: false,
},
token.QUO: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
if !a.IsNumber() {
return fmt.Errorf("Token %s must be number", a.Literal)
}
if !b.IsNumber() {
return fmt.Errorf("Token %s must be number", b.Literal)
}
n1, isInt1 := a.Int()
n2, isInt2 := b.Int()
switch {
case isInt1 && isInt2:
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 / n1),
Pos: b.Pos,
})
default:
stack.Push(Token{
Token: token.FLOAT,
Literal: strconv.FormatFloat((b.Float() / a.Float()), 'g', 5, 64),
Pos: b.Pos,
})
}
return nil
},
priority: 20,
isLeftAssoc: false,
},
token.REM: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
if !a.IsNumber() {
return fmt.Errorf("Token %s must be number", a.Literal)
}
if !b.IsNumber() {
return fmt.Errorf("Token %s must be number", b.Literal)
}
n1, isInt1 := a.Int()
n2, isInt2 := b.Int()
switch {
case isInt1 && isInt2:
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 % n1),
Pos: b.Pos,
})
default:
return fmt.Errorf("rem operation valid only for ints")
}
return nil
},
priority: 20,
isLeftAssoc: false,
},
token.AND: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 & n1),
Pos: b.Pos,
})
return nil
},
priority: 20,
isLeftAssoc: false,
},
token.OR: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 | n1),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.XOR: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 ^ n1),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.SHL: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 << n1),
Pos: b.Pos,
})
return nil
},
priority: 30,
isLeftAssoc: false,
},
token.SHR: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(n2 >> n1),
Pos: b.Pos,
})
return nil
},
priority: 30,
isLeftAssoc: false,
},
token.LAND: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := 0
if n1 != 0 && n2 != 0 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 20,
isLeftAssoc: false,
},
token.LOR: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := 0
if n1 != 0 || n2 != 0 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.EQL: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
r := 0
if a.Literal == b.Literal {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.LSS: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := 0
if n2 < n1 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.GTR: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := 0
if n2 > n1 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.NEQ: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
r := 0
if a.Literal != b.Literal {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.LEQ: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := 0
if n2 <= n1 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.GEQ: {
fn: func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := 0
if n2 >= n1 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
priority: 10,
isLeftAssoc: false,
},
token.NOT: {
fn: func(stack *Stack) error {
a := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
r := 0
if n1 == 0 {
r = 1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: a.Pos,
})
return nil
},
priority: 40,
isLeftAssoc: false,
},
}
var DefaultFunctions = map[string]func(stack *Stack) error{
"max": func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := n2
if n2 < n1 {
r = n1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
"min": func(stack *Stack) error {
a := stack.Pop()
b := stack.Pop()
n1, isInt1 := a.Int()
if !isInt1 {
return fmt.Errorf("Token %s must be integer", a.Literal)
}
n2, isInt2 := b.Int()
if !isInt2 {
return fmt.Errorf("Token %s must be integer", b.Literal)
}
r := n2
if n2 > n1 {
r = n1
}
stack.Push(Token{
Token: token.INT,
Literal: strconv.Itoa(r),
Pos: b.Pos,
})
return nil
},
}

51
evaluator.go Normal file
View file

@ -0,0 +1,51 @@
package expression
import (
"go/scanner"
"go/token"
)
type Evaluator struct {
operators map[token.Token]Operator
functions map[string]func(stack *Stack) error
}
func New() *Evaluator {
return &Evaluator{
operators: DefaultOperators,
functions: DefaultFunctions,
}
}
func (e *Evaluator) Eval(expression string) (any, error) {
s := scanner.Scanner{}
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(expression))
s.Init(file, []byte(expression), nil, scanner.ScanComments)
tokens := make(chan Token)
go func() {
for {
pos, tok, lit := s.Scan()
if tok == token.SEMICOLON {
continue
}
if tok == token.EOF {
break
}
tokens <- Token{
Token: tok,
Literal: lit,
Pos: int(pos),
}
}
close(tokens)
}()
rpnTokens := e.ToPRN(tokens)
return e.execute(rpnTokens)
}
type Operator struct {
fn func(stack *Stack) error
priority int
isLeftAssoc bool
}

56
evaluator_test.go Normal file
View file

@ -0,0 +1,56 @@
package expression
import (
"reflect"
"testing"
)
func TestEvaluator_Eval(t *testing.T) {
type args struct {
expression string
}
tests := []struct {
name string
args args
want any
wantErr bool
}{
{
name: "simple math",
args: args{
expression: "2 + 2 * 2 + max(4,9)",
},
want: 2 + 2*2 + 9,
wantErr: false,
},
{
name: "simple math 2",
args: args{
expression: "10 % 5",
},
want: 10 % 5,
wantErr: false,
},
{
name: "simple math 3",
args: args{
expression: "10 / 5",
},
want: 10 / 5,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := New()
got, err := e.Eval(tt.args.expression)
if (err != nil) != tt.wantErr {
t.Errorf("Evaluator.Eval() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Evaluator.Eval() = %v, want %v", got, tt.want)
}
})
}
}

45
execute.go Normal file
View file

@ -0,0 +1,45 @@
package expression
import (
"fmt"
"go/token"
"strings"
)
func (e *Evaluator) execute(tokens chan Token) (any, error) {
stack := Stack{}
for tok := range tokens {
switch {
case tok.IsNumber():
stack.Push(tok)
case tok.IsOperator():
op := e.operators[tok.Token]
if err := op.fn(&stack); err != nil {
return nil, err
}
case tok.IsFunc():
fn, fnEsist := e.functions[strings.ToLower(tok.Literal)]
if !fnEsist {
return nil, fmt.Errorf("unknown function %s at %d", tok.Literal, tok.Pos)
}
if err := fn(&stack); err != nil {
return nil, err
}
case tok.IsError():
return nil, fmt.Errorf("Error at token %d: %w", tok.Pos, tok.Error())
}
}
if len(stack) != 1 {
return nil, fmt.Errorf("Expected exact one returning value, go %+v", stack)
}
result := stack.Pop()
switch result.Token {
case token.INT:
n, _ := result.Int()
return n, nil
case token.FLOAT:
return result.Float(), nil
default:
return result.Literal, nil
}
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module go.neonxp.dev/expression
go 1.18

104
infixrpn.go Normal file
View file

@ -0,0 +1,104 @@
package expression
import (
"fmt"
"go/token"
)
func (e *Evaluator) ToPRN(in <-chan Token) chan Token {
out := make(chan Token)
stack := &Stack{}
go func() {
defer func() {
for !stack.Empty() {
tok := stack.Pop()
if tok.LP() {
out <- Token{
Token: token.ILLEGAL,
Literal: "no closing parenthesis",
Pos: tok.Pos,
}
} else {
out <- tok
}
}
close(out)
}()
for tok := range in {
switch {
case tok.Token == token.ILLEGAL:
return
case tok.IsNumber():
out <- tok
case tok.IsFunc():
stack.Push(tok)
case tok.IsSeparator():
for {
if stack.Empty() {
out <- Token{
Token: token.ILLEGAL,
Literal: "no opening parenthesis",
Pos: tok.Pos,
}
return
}
if stack.Head().LP() {
break
}
out <- tok
}
case tok.IsOperator():
op1 := e.operators[tok.Token]
for {
if stack.Empty() {
break
}
if stack.Head().IsOperator() {
op2, hasOp := e.operators[stack.Head().Token]
if !hasOp {
out <- Token{
Token: token.ILLEGAL,
Literal: fmt.Sprintf("unknown operator: %s", stack.Head().Literal),
Pos: tok.Pos,
}
return
}
if op2.priority > op1.priority {
out <- stack.Pop()
continue
} else {
break
}
} else {
break
}
}
stack.Push(tok)
case tok.LP():
stack.Push(tok)
case tok.RP():
for {
if stack.Empty() {
out <- Token{
Token: token.ILLEGAL,
Literal: "no opening parenthesis",
Pos: tok.Pos,
}
return
}
if stack.Head().LP() {
break
}
out <- tok
}
stack.Pop()
if stack.Head().IsFunc() {
out <- stack.Pop()
}
}
}
}()
return out
}

27
stack.go Normal file
View file

@ -0,0 +1,27 @@
package expression
type Stack []Token
func (s *Stack) Push(item Token) {
*s = append(*s, item)
}
func (s *Stack) Pop() (item Token) {
if len(*s) == 0 {
return
}
*s, item = (*s)[:len(*s)-1], (*s)[len(*s)-1]
return item
}
func (s *Stack) Empty() bool {
return len(*s) == 0
}
func (s *Stack) Head() (item *Token) {
if s.Empty() {
return nil
}
return &((*s)[len(*s)-1])
}

61
token.go Normal file
View file

@ -0,0 +1,61 @@
package expression
import (
"fmt"
"go/token"
"strconv"
)
type Token struct {
Token token.Token
Literal string
Pos int
}
func (t *Token) Int() (int, bool) {
if t.Token != token.INT {
return 0, false
}
i, _ := strconv.Atoi(t.Literal)
return i, true
}
func (t *Token) Float() float64 {
i, _ := strconv.ParseFloat(t.Literal, 64)
return i
}
func (t *Token) IsNumber() bool {
return t.Token == token.INT || t.Token == token.FLOAT
}
func (t *Token) LP() bool {
return t.Token == token.LPAREN
}
func (t *Token) RP() bool {
return t.Token == token.RPAREN
}
func (t *Token) IsFunc() bool {
return t.Token == token.IDENT
}
func (t *Token) IsSeparator() bool {
return t.Token == token.COMMA
}
func (t *Token) IsOperator() bool {
return t.Token.IsOperator() && !t.LP() && !t.RP()
}
func (t *Token) IsError() bool {
return t.Token != token.ILLEGAL
}
func (t *Token) Error() error {
if t.Token != token.ILLEGAL {
return nil
}
return fmt.Errorf(t.Literal)
}