02a7661d85026c69e2d60fd203116df0b0a07fe1

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

Add initial implementation

Diff

This diff is truncated to protect this page.

  1diff --git a/cmd/main.go b/cmd/main.go
  2new file mode 100644
  3index 0000000000000000000000000000000000000000..eca8fdcf42fd7e407a29d86f9a8dd6adad01f30e
  4--- /dev/null
  5+++ b/cmd/main.go
  6@@ -0,0 +1,38 @@
  7+package main
  8+
  9+import (
 10+	"bufio"
 11+	"errors"
 12+	"fmt"
 13+	"io"
 14+	"os"
 15+
 16+	"gitea.theedgeofrage.com/theedgeofrage/rpn/rpn"
 17+)
 18+
 19+func main() {
 20+	rpn := rpn.NewRPN()
 21+	reader := bufio.NewReader(os.Stdin)
 22+	for {
 23+		rpn.PrintStack()
 24+		// input := ""
 25+		// _, err := fmt.Scanln(&input)
 26+
 27+		fmt.Print("> ")
 28+		input, err := reader.ReadString('\n')
 29+		if err != nil {
 30+			if errors.Is(err, io.EOF) {
 31+				fmt.Println()
 32+				return
 33+			}
 34+			fmt.Println(err)
 35+			continue
 36+		}
 37+
 38+		err = rpn.Eval(input)
 39+		if err != nil {
 40+			fmt.Println(err)
 41+			continue
 42+		}
 43+	}
 44+}
 45diff --git a/go.mod b/go.mod
 46new file mode 100644
 47index 0000000000000000000000000000000000000000..9f077233d21dfb39f807a297305fbcddb0b718d5
 48--- /dev/null
 49+++ b/go.mod
 50@@ -0,0 +1,3 @@
 51+module gitea.theedgeofrage.com/theedgeofrage/rpn
 52+
 53+go 1.21.5
 54diff --git a/rpn/lexer.go b/rpn/lexer.go
 55new file mode 100644
 56index 0000000000000000000000000000000000000000..1a01c15ec9fb20071821a45958e6f622e43637c5
 57--- /dev/null
 58+++ b/rpn/lexer.go
 59@@ -0,0 +1,143 @@
 60+package rpn
 61+
 62+import (
 63+	"fmt"
 64+	"math"
 65+	"strconv"
 66+	"unicode"
 67+)
 68+
 69+type Lexer struct {
 70+	input *Input
 71+}
 72+
 73+func (l *Lexer) parseNumber() (float64, error) {
 74+	numStr := ""
 75+	char := l.input.NextChar()
 76+	for {
 77+		if !unicode.IsNumber(char) && char != '.' {
 78+			break
 79+		}
 80+
 81+		numStr += string(char)
 82+		char = l.input.Eat()
 83+	}
 84+
 85+	num, err := strconv.ParseFloat(numStr, 64)
 86+	if err != nil {
 87+		return 0, err
 88+	}
 89+
 90+	return num, nil
 91+}
 92+
 93+func (l *Lexer) parseWord() (*Token, error) {
 94+	word := ""
 95+	char := l.input.NextChar()
 96+	for {
 97+		if !unicode.IsLetter(char) {
 98+			break
 99+		}
100+
101+		word += string(char)
102+		char = l.input.Eat()
103+	}
104+
105+	switch word {
106+	case "sqrt":
107+		return &Token{unaryOp, sqrt, 0}, nil
108+	case "dec":
109+		return &Token{dec, 0, 0}, nil
110+	case "bin":
111+		return &Token{bin, 0, 0}, nil
112+	case "hex":
113+		return &Token{hex, 0, 0}, nil
114+	case "pi":
115+		return &Token{number, 0, math.Pi}, nil
116+	case "pop":
117+		return &Token{pop, 0, 0}, nil
118+	case "swap":
119+		return &Token{swap, 0, 0}, nil
120+	case "clr":
121+		return &Token{clr, 0, 0}, nil
122+	case "help":
123+		return &Token{help, 0, 0}, nil
124+	case "exit":
125+		return &Token{exit, 0, 0}, nil
126+	default:
127+		return nil, fmt.Errorf("Unknown input: %s", word)
128+	}
129+}
130+
131+func (l *Lexer) Parse(input string) ([]*Token, error) {
132+	var err error
133+	var num float64
134+	var token *Token
135+	l.input = NewInput(input)
136+	tokens := []*Token{}
137+	for {
138+		char := l.input.NextChar()
139+		if char == 0 {
140+			break
141+		}
142+
143+		switch char {
144+		case '+':
145+			l.input.Eat()
146+			token = &Token{binaryOp, plus, 0}
147+		case '-':
148+			char = l.input.Eat()
149+			if unicode.IsNumber(char) {
150+				num, err = l.parseNumber()
151+				token = &Token{number, 0, -num}
152+				if err != nil {
153+					return nil, err
154+				}
155+			} else {
156+				token = &Token{binaryOp, minus, 0}
157+			}
158+		case '*':
159diff --git a/rpn/parser.go b/rpn/parser.go
160new file mode 100644
161index 0000000000000000000000000000000000000000..b7c02f12533ab0de5db4c93b9c499db7708ff272
162--- /dev/null
163+++ b/rpn/parser.go
164@@ -0,0 +1,39 @@
165+package rpn
166+
167+type Input struct {
168+	characters []rune
169+	nextChar   rune
170+}
171+
172+func NewInput(input string) *Input {
173+	characters := []rune(input)
174+	if len(characters) == 0 {
175+		return nil
176+	}
177+
178+	i := &Input{
179+		characters: characters,
180+		nextChar:   characters[0],
181+	}
182+	return i
183+}
184+
185+// Eat consumes a character from the input and returns the next one.
186+func (i *Input) Eat() rune {
187+	if len(i.characters) == 0 {
188+		return 0
189+	}
190+
191+	i.characters = i.characters[1:]
192+	if len(i.characters) == 0 {
193+		i.nextChar = 0
194+	} else {
195+		i.nextChar = i.characters[0]
196+	}
197+
198+	return i.nextChar
199+}
200+
201+func (i *Input) NextChar() rune {
202+	return i.nextChar
203+}
204diff --git a/rpn/rpn.go b/rpn/rpn.go
205new file mode 100644
206index 0000000000000000000000000000000000000000..558c8d5f6db0d706fb8df65062410019b1a0e004
207--- /dev/null
208+++ b/rpn/rpn.go
209@@ -0,0 +1,108 @@
210+package rpn
211+
212+import (
213+	"errors"
214+	"math"
215+)
216+
217+var ErrExit = errors.New("exit")
218+
219+type RPN struct {
220+	stack *Stack
221+	lexer *Lexer
222+}
223+
224+func NewRPN() *RPN {
225+	return &RPN{
226+		stack: &Stack{},
227+		lexer: &Lexer{},
228+	}
229+}
230+
231+func printHelp() {
232+	println("RPN Calculator")
233+	println("==============")
234+	println("Commands:")
235+	println("  +, -, *, /, %, ^, sqrt")
236+	println("  pop, swap")
237+	println("  help")
238+	println("  exit (or Ctrl-D)")
239+	println("Examples:")
240+	println("  1 2 + == 3")
241+	println("  2 3 4 + *")
242+	println("  2 3 4 + * 5 /")
243+	println("  9 sqrt")
244+	println("  2 3 swap -")
245+	println("  exit")
246+}
247+
248+func (r *RPN) Eval(input string) error {
249+	tokens, err := r.lexer.Parse(input)
250+	if err != nil {
251+		return err
252+	}
253+
254+	for _, token := range tokens {
255+		switch token.Type {
256+		case number:
257+			r.stack.Push(token.Value)
258+		case binaryOp:
259+			a, b, err := r.stack.Pop2()
260+			if err != nil {
261+				return err
262+			}
263+			switch token.Operator {
264+			case plus:
265+				r.stack.Push(b + a)
266+			case minus:
267+				r.stack.Push(b - a)
268+			case multiply:
269+				r.stack.Push(b * a)
270+			case divide:
271+				if a == 0 {
272+					r.stack.Push(b)
273+					r.stack.Push(a)
274+					return errors.New("can't divide by zero")
275+				}
276+				r.stack.Push(b / a)
277+			case mod:
278+				r.stack.Push(float64(int64(b) % int64(a)))
279+			case power:
280+				r.stack.Push(math.Pow(b, a))
281+			}
282+		case unaryOp:
283+			a, err := r.stack.Pop()
284+			if err != nil {
285+				return err
286+			}
287+			switch token.Operator {
288+			case sqrt:
289+				r.stack.Push(math.Sqrt(a))
290+			}
291+		case pop:
292+			_, err := r.stack.Pop()
293+			if err != nil {
294+				return err
295+			}
296+		case swap:
297+			err := r.stack.Swap()
298+			if err != nil {
299+				return err
300+			}
301+		case clr:
302+			r.stack.Clear()
303+		case help:
304+			printHelp()
305+		case exit:
306+			return ErrExit
307+		default:
308+			return errors.New("Unknown operation")
309diff --git a/rpn/stack.go b/rpn/stack.go
310new file mode 100644
311index 0000000000000000000000000000000000000000..318334905bf475a5590168b26de4f2ca8faffe62
312--- /dev/null
313+++ b/rpn/stack.go
314@@ -0,0 +1,71 @@
315+package rpn
316+
317+import (
318+	"errors"
319+	"fmt"
320+	"math"
321+)
322+
323+var (
324+	ErrStackEmpty      = errors.New("stack is empty")
325+	ErrNotEnoughValues = errors.New("not enough values on stack")
326+)
327+
328+type Stack struct {
329+	values []float64
330+}
331+
332+func (s *Stack) Push(value float64) {
333+	s.values = append(s.values, value)
334+}
335+
336+func (s *Stack) Pop() (float64, error) {
337+	count := s.Len()
338+	if count == 0 {
339+		return 0, ErrStackEmpty
340+	}
341+	value := s.values[count-1]
342+	s.values = s.values[:count-1]
343+	return value, nil
344+}
345+
346+func (s *Stack) Pop2() (float64, float64, error) {
347+	count := s.Len()
348+	if count < 2 {
349+		return 0, 0, ErrNotEnoughValues
350+	}
351+	a := s.values[count-1]
352+	b := s.values[count-2]
353+	s.values = s.values[:count-2]
354+	return a, b, nil
355+}
356+
357+func (s *Stack) Len() int {
358+	return len(s.values)
359+}
360+
361+func (s *Stack) Swap() error {
362+	count := s.Len()
363+	if count < 2 {
364+		return ErrNotEnoughValues
365+	}
366+	a := s.values[count-1]
367+	b := s.values[count-2]
368+	s.values[count-1] = b
369+	s.values[count-2] = a
370+	return nil
371+}
372+
373+func (s *Stack) Clear() {
374+	s.values = []float64{}
375+}
376+
377+func (s *Stack) Print() {
378+	for _, value := range s.values {
379+		if value == math.Trunc(value) {
380+			fmt.Printf("%d\n", int64(value))
381+		} else {
382+			fmt.Printf("%f\n", value)
383+		}
384+	}
385+}
386diff --git a/rpn/token.go b/rpn/token.go
387new file mode 100644
388index 0000000000000000000000000000000000000000..1d0623ca4115f20a14a6997c3440eef3c198ad93
389--- /dev/null
390+++ b/rpn/token.go
391@@ -0,0 +1,35 @@
392+package rpn
393+
394+type TokenType int
395+
396+const (
397+	number TokenType = iota
398+	binaryOp
399+	unaryOp
400+	dec
401+	bin
402+	hex
403+	pop
404+	swap
405+	clr
406+	help
407+	exit
408+)
409+
410+type Operator int
411+
412+const (
413+	plus Operator = iota
414+	minus
415+	multiply
416+	divide
417+	mod
418+	power
419+	sqrt
420+)
421+
422+type Token struct {
423+	Type     TokenType
424+	Operator Operator
425+	Value    float64
426+}