lexer.go
2748 bytes
1package rpn
2
3import (
4 "fmt"
5 "math"
6 "strconv"
7 "unicode"
8)
9
10type Lexer struct {
11 input *Input
12}
13
14func (l *Lexer) parseNumber(negative bool) (*Token, error) {
15 numStr := ""
16 char := l.input.NextChar()
17 for unicode.IsNumber(char) || char == '.' {
18 numStr += string(char)
19 char = l.input.Eat()
20 }
21
22 num, err := strconv.ParseFloat(numStr, 64)
23 if err != nil {
24 return nil, err
25 }
26 if negative {
27 numStr = "-" + numStr
28 num = -num
29 }
30
31 return &Token{number, 0, num, numStr}, nil
32}
33
34func (l *Lexer) parseWord() (*Token, error) {
35 word := ""
36 char := l.input.NextChar()
37 for unicode.IsLetter(char) {
38 word += string(char)
39 char = l.input.Eat()
40 }
41
42 switch word {
43 case "sqrt":
44 return &Token{unaryOp, sqrt, 0, word}, nil
45 case "dec":
46 return &Token{dec, 0, 0, word}, nil
47 case "bin":
48 return &Token{bin, 0, 0, word}, nil
49 case "hex":
50 return &Token{hex, 0, 0, word}, nil
51 case "pi":
52 return &Token{number, 0, math.Pi, word}, nil
53 case "pop":
54 return &Token{pop, 0, 0, word}, nil
55 case "swap":
56 return &Token{swap, 0, 0, word}, nil
57 case "clr":
58 return &Token{clr, 0, 0, word}, nil
59 case "help":
60 return &Token{help, 0, 0, word}, nil
61 case "exit":
62 return &Token{exit, 0, 0, word}, nil
63 default:
64 return nil, fmt.Errorf("unknown input: %s", word)
65 }
66}
67
68func (l *Lexer) Parse(input string) ([]*Token, error) {
69 var err error
70 var token *Token
71 l.input = NewInput(input)
72 if l.input == nil {
73 return nil, nil
74 }
75 tokens := []*Token{}
76 for {
77 char := l.input.NextChar()
78 if char == 0 {
79 break
80 }
81
82 switch char {
83 case '+':
84 l.input.Eat()
85 token = &Token{binaryOp, plus, 0, string(char)}
86 case '-':
87 char = l.input.Eat()
88 if unicode.IsNumber(char) {
89 token, err = l.parseNumber(true)
90 if err != nil {
91 return nil, err
92 }
93 } else {
94 token = &Token{binaryOp, minus, 0, string(char)}
95 }
96 case '*':
97 l.input.Eat()
98 token = &Token{binaryOp, multiply, 0, string(char)}
99 case '/':
100 l.input.Eat()
101 token = &Token{binaryOp, divide, 0, string(char)}
102 case '%':
103 l.input.Eat()
104 token = &Token{binaryOp, mod, 0, string(char)}
105 case '^':
106 l.input.Eat()
107 token = &Token{binaryOp, power, 0, string(char)}
108 case ' ':
109 l.input.Eat()
110 continue
111 case 0x0a:
112 l.input.Eat()
113 continue
114 case 0x04: // Ctrl-D
115 token = &Token{exit, 0, 0, "^D"}
116 case '?':
117 l.input.Eat()
118 token = &Token{help, 0, 0, string(char)}
119 default:
120 if unicode.IsNumber(char) {
121 token, err = l.parseNumber(false)
122 if err != nil {
123 return nil, err
124 }
125 } else if unicode.IsLetter(char) {
126 token, err = l.parseWord()
127 if err != nil {
128 return nil, err
129 }
130 } else {
131 return nil, fmt.Errorf("invalid input: %c", char)
132 }
133 }
134
135 tokens = append(tokens, token)
136 }
137
138 return tokens, nil
139}