1
2
3
4
5 package specexpr
6
7 import (
8 "fmt"
9 "strconv"
10 "strings"
11 )
12
13
14 func ParseExpr(x string) (Expr, error) {
15 var e Expr
16 var err error
17
18 func() {
19 defer func() {
20 v := recover()
21 if v2, ok := v.(*parseError); ok {
22 err = v2
23 return
24 } else if v != nil {
25 panic(v)
26 }
27 }()
28 p := &parser{s: x}
29 p.skipSpace()
30 e = p.parseExpr()
31 if p.pos < len(p.s) {
32 p.fail("unexpected trailing characters")
33 }
34 }()
35 return e, err
36 }
37
38 type parser struct {
39 s string
40 pos int
41 }
42
43 type parseError struct {
44 msg string
45 args []any
46 pos int
47 }
48
49 func (e *parseError) Error() string {
50 return fmt.Sprintf("%s at %d", fmt.Sprintf(e.msg, e.args...), 1+e.pos)
51 }
52
53 func (p *parser) fail(msg string, args ...any) {
54 panic(&parseError{msg: msg, args: args, pos: p.pos})
55 }
56
57 func (p *parser) failAt(pos int, msg string, args ...any) {
58 panic(&parseError{msg: msg, args: args, pos: pos})
59 }
60
61 func (p *parser) skipSpace() {
62 for p.pos < len(p.s) && (p.s[p.pos] == ' ' || p.s[p.pos] == '\t' || p.s[p.pos] == '\r' || p.s[p.pos] == '\n') {
63 p.pos++
64 }
65 }
66
67 func (p *parser) peek() byte {
68 if p.pos >= len(p.s) {
69 return 0
70 }
71 return p.s[p.pos]
72 }
73
74
75
76 func (p *parser) consume(pred func(c byte) bool) (string, bool) {
77 if p.pos >= len(p.s) || !pred(p.s[p.pos]) {
78 return "", false
79 }
80 start := p.pos
81 p.pos++
82 for p.pos < len(p.s) && pred(p.s[p.pos]) {
83 p.pos++
84 }
85 return p.s[start:p.pos], true
86 }
87
88 func isDigit(c byte) bool {
89 return c >= '0' && c <= '9'
90 }
91
92 func isAlpha(c byte) bool {
93 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
94 }
95
96
97
98 func (p *parser) try(s string) bool {
99 if !strings.HasPrefix(p.s[p.pos:], s) {
100 return false
101 }
102 p.pos += len(s)
103 p.skipSpace()
104 return true
105 }
106
107 func (p *parser) peekShape() bool {
108
109 if p.peek() == '{' {
110 return true
111 }
112 start := p.pos
113 if _, ok := p.consume(isAlpha); !ok {
114 return false
115 }
116
117 ok := p.pos < len(p.s) && (isDigit(p.s[p.pos]) || p.s[p.pos] == '{')
118 p.pos = start
119 return ok
120 }
121
122 func (p *parser) parseExpr() Expr {
123 return p.parseComparison()
124 }
125
126 func (p *parser) parseComparison() Expr {
127 x := p.parseMulDiv()
128 var op BinOp
129 switch {
130 case p.try("="):
131 op = OpEqual
132 case p.try("!="):
133 op = OpNotEqual
134 case p.try(">="):
135 op = OpGreaterOrEqual
136 case p.try(">"):
137 op = OpGreaterThan
138 case p.try("<="):
139 op = OpLessOrEqual
140 case p.try("<"):
141 op = OpLessThan
142 default:
143 return x
144 }
145
146 y := p.parseMulDiv()
147 return &BinExpr{op, x, y}
148 }
149
150 func (p *parser) parseMulDiv() Expr {
151 x := p.parsePrimary()
152 loop:
153 for {
154 var op BinOp
155 switch {
156 case p.try("*"):
157 op = OpTimes
158 case p.try("/"):
159 op = OpDiv
160 default:
161 break loop
162 }
163 y := p.parsePrimary()
164 x = &BinExpr{op, x, y}
165 }
166 return x
167 }
168
169 func (p *parser) parsePrimary() Expr {
170 if p.try("(") {
171 e := p.parseExpr()
172 if !p.try(")") {
173 p.fail("expected ')'")
174 }
175 return e
176 }
177
178
179 if p.peekShape() {
180 return p.parseSymShape()
181 }
182
183
184 b := p.peek()
185 if isDigit(b) {
186 return p.parseNumber()
187 }
188
189
190 if isAlpha(b) {
191 name, _ := p.consume(isAlpha)
192 p.skipSpace()
193 return Variable(name)
194 }
195
196 if b == 0 {
197 p.fail("unexpected end")
198 }
199 p.fail("unexpected character '%c'", b)
200 panic("not reachable")
201 }
202
203 func (p *parser) parseNumber() Int {
204 nStr, ok := p.consume(isDigit)
205 if !ok {
206 p.fail("expected number")
207 }
208 num, err := strconv.Atoi(nStr)
209 if err != nil {
210 p.fail("%s", err)
211 }
212 p.skipSpace()
213 return Int(num)
214 }
215
216
217
218
219
220
221 func (p *parser) parseSymShape() *Apply {
222 var b, n Expr
223
224 trySymPart := func() Expr {
225 openPos := p.pos
226 if !p.try("{") {
227 return nil
228 }
229
230 x := p.parseExpr()
231 if !p.try("}") {
232 p.failAt(openPos, "'{' missing close '}' in symbolic shape")
233 }
234 return x
235 }
236
237
238 if b = trySymPart(); b == nil {
239 base, ok := p.consume(isAlpha)
240 if !ok {
241 p.fail("expected shape base name matching [a-zA-Z]+")
242 }
243 b = &Literal{strings.ToLower(base)}
244 }
245
246
247 if n = trySymPart(); n == nil {
248 n = p.parseNumber()
249 }
250
251 elem := MakeBasic(b, n)
252
253
254 var x *Apply
255
256 switch p.peek() {
257 case 's':
258 p.pos++
259 x = MakeVector(elem, VW())
260 case 'x':
261 p.pos++
262 l := trySymPart()
263 if l == nil {
264
265 l = p.parseNumber()
266 }
267 x = makeVectorL(elem, l)
268 case 'w':
269 p.pos++
270 w := trySymPart()
271 if w == nil {
272 w = p.parseNumber()
273 }
274 x = MakeVector(elem, w)
275 default:
276
277 x = elem
278 }
279
280 p.skipSpace()
281 return x
282 }
283
View as plain text