Improve error messages
This commit is contained in:
parent
cef6cde8fa
commit
a4a3d05ddd
55
rpn/lexer.go
55
rpn/lexer.go
|
@ -11,7 +11,7 @@ type Lexer struct {
|
||||||
input *Input
|
input *Input
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Lexer) parseNumber() (float64, error) {
|
func (l *Lexer) parseNumber(negative bool) (*Token, error) {
|
||||||
numStr := ""
|
numStr := ""
|
||||||
char := l.input.NextChar()
|
char := l.input.NextChar()
|
||||||
for {
|
for {
|
||||||
|
@ -25,10 +25,14 @@ func (l *Lexer) parseNumber() (float64, error) {
|
||||||
|
|
||||||
num, err := strconv.ParseFloat(numStr, 64)
|
num, err := strconv.ParseFloat(numStr, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return nil, err
|
||||||
|
}
|
||||||
|
if negative {
|
||||||
|
numStr = "-" + numStr
|
||||||
|
num = -num
|
||||||
}
|
}
|
||||||
|
|
||||||
return num, nil
|
return &Token{number, 0, num, numStr}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Lexer) parseWord() (*Token, error) {
|
func (l *Lexer) parseWord() (*Token, error) {
|
||||||
|
@ -45,25 +49,25 @@ func (l *Lexer) parseWord() (*Token, error) {
|
||||||
|
|
||||||
switch word {
|
switch word {
|
||||||
case "sqrt":
|
case "sqrt":
|
||||||
return &Token{unaryOp, sqrt, 0}, nil
|
return &Token{unaryOp, sqrt, 0, word}, nil
|
||||||
case "dec":
|
case "dec":
|
||||||
return &Token{dec, 0, 0}, nil
|
return &Token{dec, 0, 0, word}, nil
|
||||||
case "bin":
|
case "bin":
|
||||||
return &Token{bin, 0, 0}, nil
|
return &Token{bin, 0, 0, word}, nil
|
||||||
case "hex":
|
case "hex":
|
||||||
return &Token{hex, 0, 0}, nil
|
return &Token{hex, 0, 0, word}, nil
|
||||||
case "pi":
|
case "pi":
|
||||||
return &Token{number, 0, math.Pi}, nil
|
return &Token{number, 0, math.Pi, word}, nil
|
||||||
case "pop":
|
case "pop":
|
||||||
return &Token{pop, 0, 0}, nil
|
return &Token{pop, 0, 0, word}, nil
|
||||||
case "swap":
|
case "swap":
|
||||||
return &Token{swap, 0, 0}, nil
|
return &Token{swap, 0, 0, word}, nil
|
||||||
case "clr":
|
case "clr":
|
||||||
return &Token{clr, 0, 0}, nil
|
return &Token{clr, 0, 0, word}, nil
|
||||||
case "help":
|
case "help":
|
||||||
return &Token{help, 0, 0}, nil
|
return &Token{help, 0, 0, word}, nil
|
||||||
case "exit":
|
case "exit":
|
||||||
return &Token{exit, 0, 0}, nil
|
return &Token{exit, 0, 0, word}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unknown input: %s", word)
|
return nil, fmt.Errorf("Unknown input: %s", word)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +75,6 @@ func (l *Lexer) parseWord() (*Token, error) {
|
||||||
|
|
||||||
func (l *Lexer) Parse(input string) ([]*Token, error) {
|
func (l *Lexer) Parse(input string) ([]*Token, error) {
|
||||||
var err error
|
var err error
|
||||||
var num float64
|
|
||||||
var token *Token
|
var token *Token
|
||||||
l.input = NewInput(input)
|
l.input = NewInput(input)
|
||||||
tokens := []*Token{}
|
tokens := []*Token{}
|
||||||
|
@ -84,30 +87,29 @@ func (l *Lexer) Parse(input string) ([]*Token, error) {
|
||||||
switch char {
|
switch char {
|
||||||
case '+':
|
case '+':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
token = &Token{binaryOp, plus, 0}
|
token = &Token{binaryOp, plus, 0, string(char)}
|
||||||
case '-':
|
case '-':
|
||||||
char = l.input.Eat()
|
char = l.input.Eat()
|
||||||
if unicode.IsNumber(char) {
|
if unicode.IsNumber(char) {
|
||||||
num, err = l.parseNumber()
|
token, err = l.parseNumber(true)
|
||||||
token = &Token{number, 0, -num}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = &Token{binaryOp, minus, 0}
|
token = &Token{binaryOp, minus, 0, string(char)}
|
||||||
}
|
}
|
||||||
case '*':
|
case '*':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
token = &Token{binaryOp, multiply, 0}
|
token = &Token{binaryOp, multiply, 0, string(char)}
|
||||||
case '/':
|
case '/':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
token = &Token{binaryOp, divide, 0}
|
token = &Token{binaryOp, divide, 0, string(char)}
|
||||||
case '%':
|
case '%':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
token = &Token{binaryOp, mod, 0}
|
token = &Token{binaryOp, mod, 0, string(char)}
|
||||||
case '^':
|
case '^':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
token = &Token{binaryOp, power, 0}
|
token = &Token{binaryOp, power, 0, string(char)}
|
||||||
case ' ':
|
case ' ':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
continue
|
continue
|
||||||
|
@ -115,24 +117,23 @@ func (l *Lexer) Parse(input string) ([]*Token, error) {
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
continue
|
continue
|
||||||
case 0x04: // Ctrl-D
|
case 0x04: // Ctrl-D
|
||||||
token = &Token{exit, 0, 0}
|
token = &Token{exit, 0, 0, "^D"}
|
||||||
case '?':
|
case '?':
|
||||||
l.input.Eat()
|
l.input.Eat()
|
||||||
token = &Token{help, 0, 0}
|
token = &Token{help, 0, 0, string(char)}
|
||||||
default:
|
default:
|
||||||
if unicode.IsNumber(char) {
|
if unicode.IsNumber(char) {
|
||||||
num, err = l.parseNumber()
|
token, err = l.parseNumber(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
token = &Token{number, 0, num}
|
|
||||||
} else if unicode.IsLetter(char) {
|
} else if unicode.IsLetter(char) {
|
||||||
token, err = l.parseWord()
|
token, err = l.parseWord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Unknown input: %c", char)
|
return nil, fmt.Errorf("Invalid input: %c", char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
rpn/rpn.go
15
rpn/rpn.go
|
@ -1,12 +1,10 @@
|
||||||
package rpn
|
package rpn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrExit = errors.New("exit")
|
|
||||||
|
|
||||||
type RPN struct {
|
type RPN struct {
|
||||||
stack *Stack
|
stack *Stack
|
||||||
lexer *Lexer
|
lexer *Lexer
|
||||||
|
@ -62,10 +60,15 @@ func (r *RPN) Eval(input string) error {
|
||||||
if a == 0 {
|
if a == 0 {
|
||||||
r.stack.Push(b)
|
r.stack.Push(b)
|
||||||
r.stack.Push(a)
|
r.stack.Push(a)
|
||||||
return errors.New("can't divide by zero")
|
return fmt.Errorf("Can't divide by zero")
|
||||||
}
|
}
|
||||||
r.stack.Push(b / a)
|
r.stack.Push(b / a)
|
||||||
case mod:
|
case mod:
|
||||||
|
if a == 0 {
|
||||||
|
r.stack.Push(b)
|
||||||
|
r.stack.Push(a)
|
||||||
|
return fmt.Errorf("Can't divide by zero")
|
||||||
|
}
|
||||||
r.stack.Push(float64(int64(b) % int64(a)))
|
r.stack.Push(float64(int64(b) % int64(a)))
|
||||||
case power:
|
case power:
|
||||||
r.stack.Push(math.Pow(b, a))
|
r.stack.Push(math.Pow(b, a))
|
||||||
|
@ -94,9 +97,9 @@ func (r *RPN) Eval(input string) error {
|
||||||
case help:
|
case help:
|
||||||
printHelp()
|
printHelp()
|
||||||
case exit:
|
case exit:
|
||||||
return ErrExit
|
return fmt.Errorf("exit")
|
||||||
default:
|
default:
|
||||||
return errors.New("Unknown operation")
|
return fmt.Errorf("Operation not implemented: %s", token.Original)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
rpn/stack.go
12
rpn/stack.go
|
@ -1,15 +1,11 @@
|
||||||
package rpn
|
package rpn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ErrNotEnoughValuesTmpl = "Need at least %d values on the stack to perform this operation"
|
||||||
ErrStackEmpty = errors.New("stack is empty")
|
|
||||||
ErrNotEnoughValues = errors.New("not enough values on stack")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Stack struct {
|
type Stack struct {
|
||||||
values []float64
|
values []float64
|
||||||
|
@ -22,7 +18,7 @@ func (s *Stack) Push(value float64) {
|
||||||
func (s *Stack) Pop() (float64, error) {
|
func (s *Stack) Pop() (float64, error) {
|
||||||
count := s.Len()
|
count := s.Len()
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return 0, ErrStackEmpty
|
return 0, fmt.Errorf(ErrNotEnoughValuesTmpl, 1)
|
||||||
}
|
}
|
||||||
value := s.values[count-1]
|
value := s.values[count-1]
|
||||||
s.values = s.values[:count-1]
|
s.values = s.values[:count-1]
|
||||||
|
@ -32,7 +28,7 @@ func (s *Stack) Pop() (float64, error) {
|
||||||
func (s *Stack) Pop2() (float64, float64, error) {
|
func (s *Stack) Pop2() (float64, float64, error) {
|
||||||
count := s.Len()
|
count := s.Len()
|
||||||
if count < 2 {
|
if count < 2 {
|
||||||
return 0, 0, ErrNotEnoughValues
|
return 0, 0, fmt.Errorf(ErrNotEnoughValuesTmpl, 2)
|
||||||
}
|
}
|
||||||
a := s.values[count-1]
|
a := s.values[count-1]
|
||||||
b := s.values[count-2]
|
b := s.values[count-2]
|
||||||
|
@ -47,7 +43,7 @@ func (s *Stack) Len() int {
|
||||||
func (s *Stack) Swap() error {
|
func (s *Stack) Swap() error {
|
||||||
count := s.Len()
|
count := s.Len()
|
||||||
if count < 2 {
|
if count < 2 {
|
||||||
return ErrNotEnoughValues
|
return fmt.Errorf(ErrNotEnoughValuesTmpl, 2)
|
||||||
}
|
}
|
||||||
a := s.values[count-1]
|
a := s.values[count-1]
|
||||||
b := s.values[count-2]
|
b := s.values[count-2]
|
||||||
|
|
|
@ -32,4 +32,5 @@ type Token struct {
|
||||||
Type TokenType
|
Type TokenType
|
||||||
Operator Operator
|
Operator Operator
|
||||||
Value float64
|
Value float64
|
||||||
|
Original string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue