Add initial implementation

This commit is contained in:
Pavle Portic 2024-01-08 23:33:16 +01:00
commit 02a7661d85
Signed by: TheEdgeOfRage
GPG Key ID: 66AD4BA646FBC0D2
7 changed files with 437 additions and 0 deletions

38
cmd/main.go Normal file
View File

@ -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
}
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module gitea.theedgeofrage.com/theedgeofrage/rpn
go 1.21.5

143
rpn/lexer.go Normal file
View File

@ -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
}

39
rpn/parser.go Normal file
View File

@ -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
}

108
rpn/rpn.go Normal file
View File

@ -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()
}

71
rpn/stack.go Normal file
View File

@ -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)
}
}
}

35
rpn/token.go Normal file
View File

@ -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
}