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