first commit
This commit is contained in:
commit
cc4c01c69c
8 changed files with 883 additions and 0 deletions
536
defaults.go
Normal file
536
defaults.go
Normal 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
51
evaluator.go
Normal 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
56
evaluator_test.go
Normal 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
45
execute.go
Normal 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
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module go.neonxp.dev/expression
|
||||
|
||||
go 1.18
|
104
infixrpn.go
Normal file
104
infixrpn.go
Normal 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
27
stack.go
Normal 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
61
token.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue