Add initial implementation
This commit is contained in:
commit
02a7661d85
|
@ -0,0 +1,38 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitea.theedgeofrage.com/theedgeofrage/rpn/rpn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
rpn := rpn.NewRPN()
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
for {
|
||||||
|
rpn.PrintStack()
|
||||||
|
// input := ""
|
||||||
|
// _, err := fmt.Scanln(&input)
|
||||||
|
|
||||||
|
fmt.Print("> ")
|
||||||
|
input, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
fmt.Println()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rpn.Eval(input)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module gitea.theedgeofrage.com/theedgeofrage/rpn
|
||||||
|
|
||||||
|
go 1.21.5
|
|
@ -0,0 +1,143 @@
|
||||||
|
package rpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
input *Input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) parseNumber() (float64, error) {
|
||||||
|
numStr := ""
|
||||||
|
char := l.input.NextChar()
|
||||||
|
for {
|
||||||
|
if !unicode.IsNumber(char) && char != '.' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
numStr += string(char)
|
||||||
|
char = l.input.Eat()
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := strconv.ParseFloat(numStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return num, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) parseWord() (*Token, error) {
|
||||||
|
word := ""
|
||||||
|
char := l.input.NextChar()
|
||||||
|
for {
|
||||||
|
if !unicode.IsLetter(char) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
word += string(char)
|
||||||
|
char = l.input.Eat()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch word {
|
||||||
|
case "sqrt":
|
||||||
|
return &Token{unaryOp, sqrt, 0}, nil
|
||||||
|
case "dec":
|
||||||
|
return &Token{dec, 0, 0}, nil
|
||||||
|
case "bin":
|
||||||
|
return &Token{bin, 0, 0}, nil
|
||||||
|
case "hex":
|
||||||
|
return &Token{hex, 0, 0}, nil
|
||||||
|
case "pi":
|
||||||
|
return &Token{number, 0, math.Pi}, nil
|
||||||
|
case "pop":
|
||||||
|
return &Token{pop, 0, 0}, nil
|
||||||
|
case "swap":
|
||||||
|
return &Token{swap, 0, 0}, nil
|
||||||
|
case "clr":
|
||||||
|
return &Token{clr, 0, 0}, nil
|
||||||
|
case "help":
|
||||||
|
return &Token{help, 0, 0}, nil
|
||||||
|
case "exit":
|
||||||
|
return &Token{exit, 0, 0}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown input: %s", word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) Parse(input string) ([]*Token, error) {
|
||||||
|
var err error
|
||||||
|
var num float64
|
||||||
|
var token *Token
|
||||||
|
l.input = NewInput(input)
|
||||||
|
tokens := []*Token{}
|
||||||
|
for {
|
||||||
|
char := l.input.NextChar()
|
||||||
|
if char == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch char {
|
||||||
|
case '+':
|
||||||
|
l.input.Eat()
|
||||||
|
token = &Token{binaryOp, plus, 0}
|
||||||
|
case '-':
|
||||||
|
char = l.input.Eat()
|
||||||
|
if unicode.IsNumber(char) {
|
||||||
|
num, err = l.parseNumber()
|
||||||
|
token = &Token{number, 0, -num}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = &Token{binaryOp, minus, 0}
|
||||||
|
}
|
||||||
|
case '*':
|
||||||
|
l.input.Eat()
|
||||||
|
token = &Token{binaryOp, multiply, 0}
|
||||||
|
case '/':
|
||||||
|
l.input.Eat()
|
||||||
|
token = &Token{binaryOp, divide, 0}
|
||||||
|
case '%':
|
||||||
|
l.input.Eat()
|
||||||
|
token = &Token{binaryOp, mod, 0}
|
||||||
|
case '^':
|
||||||
|
l.input.Eat()
|
||||||
|
token = &Token{binaryOp, power, 0}
|
||||||
|
case ' ':
|
||||||
|
l.input.Eat()
|
||||||
|
continue
|
||||||
|
case 0x0a:
|
||||||
|
l.input.Eat()
|
||||||
|
continue
|
||||||
|
case 0x04: // Ctrl-D
|
||||||
|
token = &Token{exit, 0, 0}
|
||||||
|
case '?':
|
||||||
|
l.input.Eat()
|
||||||
|
token = &Token{help, 0, 0}
|
||||||
|
default:
|
||||||
|
if unicode.IsNumber(char) {
|
||||||
|
num, err = l.parseNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token = &Token{number, 0, num}
|
||||||
|
} else if unicode.IsLetter(char) {
|
||||||
|
token, err = l.parseWord()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Unknown input: %c", char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens = append(tokens, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens, nil
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package rpn
|
||||||
|
|
||||||
|
type Input struct {
|
||||||
|
characters []rune
|
||||||
|
nextChar rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInput(input string) *Input {
|
||||||
|
characters := []rune(input)
|
||||||
|
if len(characters) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &Input{
|
||||||
|
characters: characters,
|
||||||
|
nextChar: characters[0],
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eat consumes a character from the input and returns the next one.
|
||||||
|
func (i *Input) Eat() rune {
|
||||||
|
if len(i.characters) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
i.characters = i.characters[1:]
|
||||||
|
if len(i.characters) == 0 {
|
||||||
|
i.nextChar = 0
|
||||||
|
} else {
|
||||||
|
i.nextChar = i.characters[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.nextChar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) NextChar() rune {
|
||||||
|
return i.nextChar
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package rpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrExit = errors.New("exit")
|
||||||
|
|
||||||
|
type RPN struct {
|
||||||
|
stack *Stack
|
||||||
|
lexer *Lexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRPN() *RPN {
|
||||||
|
return &RPN{
|
||||||
|
stack: &Stack{},
|
||||||
|
lexer: &Lexer{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printHelp() {
|
||||||
|
println("RPN Calculator")
|
||||||
|
println("==============")
|
||||||
|
println("Commands:")
|
||||||
|
println(" +, -, *, /, %, ^, sqrt")
|
||||||
|
println(" pop, swap")
|
||||||
|
println(" help")
|
||||||
|
println(" exit (or Ctrl-D)")
|
||||||
|
println("Examples:")
|
||||||
|
println(" 1 2 + == 3")
|
||||||
|
println(" 2 3 4 + *")
|
||||||
|
println(" 2 3 4 + * 5 /")
|
||||||
|
println(" 9 sqrt")
|
||||||
|
println(" 2 3 swap -")
|
||||||
|
println(" exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RPN) Eval(input string) error {
|
||||||
|
tokens, err := r.lexer.Parse(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range tokens {
|
||||||
|
switch token.Type {
|
||||||
|
case number:
|
||||||
|
r.stack.Push(token.Value)
|
||||||
|
case binaryOp:
|
||||||
|
a, b, err := r.stack.Pop2()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch token.Operator {
|
||||||
|
case plus:
|
||||||
|
r.stack.Push(b + a)
|
||||||
|
case minus:
|
||||||
|
r.stack.Push(b - a)
|
||||||
|
case multiply:
|
||||||
|
r.stack.Push(b * a)
|
||||||
|
case divide:
|
||||||
|
if a == 0 {
|
||||||
|
r.stack.Push(b)
|
||||||
|
r.stack.Push(a)
|
||||||
|
return errors.New("can't divide by zero")
|
||||||
|
}
|
||||||
|
r.stack.Push(b / a)
|
||||||
|
case mod:
|
||||||
|
r.stack.Push(float64(int64(b) % int64(a)))
|
||||||
|
case power:
|
||||||
|
r.stack.Push(math.Pow(b, a))
|
||||||
|
}
|
||||||
|
case unaryOp:
|
||||||
|
a, err := r.stack.Pop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch token.Operator {
|
||||||
|
case sqrt:
|
||||||
|
r.stack.Push(math.Sqrt(a))
|
||||||
|
}
|
||||||
|
case pop:
|
||||||
|
_, err := r.stack.Pop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case swap:
|
||||||
|
err := r.stack.Swap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case clr:
|
||||||
|
r.stack.Clear()
|
||||||
|
case help:
|
||||||
|
printHelp()
|
||||||
|
case exit:
|
||||||
|
return ErrExit
|
||||||
|
default:
|
||||||
|
return errors.New("Unknown operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RPN) PrintStack() {
|
||||||
|
r.stack.Print()
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package rpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrStackEmpty = errors.New("stack is empty")
|
||||||
|
ErrNotEnoughValues = errors.New("not enough values on stack")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stack struct {
|
||||||
|
values []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Push(value float64) {
|
||||||
|
s.values = append(s.values, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Pop() (float64, error) {
|
||||||
|
count := s.Len()
|
||||||
|
if count == 0 {
|
||||||
|
return 0, ErrStackEmpty
|
||||||
|
}
|
||||||
|
value := s.values[count-1]
|
||||||
|
s.values = s.values[:count-1]
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Pop2() (float64, float64, error) {
|
||||||
|
count := s.Len()
|
||||||
|
if count < 2 {
|
||||||
|
return 0, 0, ErrNotEnoughValues
|
||||||
|
}
|
||||||
|
a := s.values[count-1]
|
||||||
|
b := s.values[count-2]
|
||||||
|
s.values = s.values[:count-2]
|
||||||
|
return a, b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Swap() error {
|
||||||
|
count := s.Len()
|
||||||
|
if count < 2 {
|
||||||
|
return ErrNotEnoughValues
|
||||||
|
}
|
||||||
|
a := s.values[count-1]
|
||||||
|
b := s.values[count-2]
|
||||||
|
s.values[count-1] = b
|
||||||
|
s.values[count-2] = a
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Clear() {
|
||||||
|
s.values = []float64{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Print() {
|
||||||
|
for _, value := range s.values {
|
||||||
|
if value == math.Trunc(value) {
|
||||||
|
fmt.Printf("%d\n", int64(value))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%f\n", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package rpn
|
||||||
|
|
||||||
|
type TokenType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
number TokenType = iota
|
||||||
|
binaryOp
|
||||||
|
unaryOp
|
||||||
|
dec
|
||||||
|
bin
|
||||||
|
hex
|
||||||
|
pop
|
||||||
|
swap
|
||||||
|
clr
|
||||||
|
help
|
||||||
|
exit
|
||||||
|
)
|
||||||
|
|
||||||
|
type Operator int
|
||||||
|
|
||||||
|
const (
|
||||||
|
plus Operator = iota
|
||||||
|
minus
|
||||||
|
multiply
|
||||||
|
divide
|
||||||
|
mod
|
||||||
|
power
|
||||||
|
sqrt
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Type TokenType
|
||||||
|
Operator Operator
|
||||||
|
Value float64
|
||||||
|
}
|
Loading…
Reference in New Issue