1 |
yakumo_izuru |
1.1 |
package parser |
2 |
|
|
|
3 |
|
|
import ( |
4 |
|
|
"bufio" |
5 |
|
|
"container/list" |
6 |
|
|
"fmt" |
7 |
|
|
"io" |
8 |
|
|
"regexp" |
9 |
|
|
) |
10 |
|
|
|
11 |
|
|
const ( |
12 |
|
|
tokEOF = -(iota + 1) |
13 |
|
|
tokDoctype |
14 |
|
|
tokComment |
15 |
|
|
tokIndent |
16 |
|
|
tokOutdent |
17 |
|
|
tokBlank |
18 |
|
|
tokId |
19 |
|
|
tokClassName |
20 |
|
|
tokTag |
21 |
|
|
tokText |
22 |
|
|
tokAttribute |
23 |
|
|
tokIf |
24 |
|
|
tokElse |
25 |
|
|
tokEach |
26 |
|
|
tokAssignment |
27 |
|
|
tokImport |
28 |
|
|
tokNamedBlock |
29 |
|
|
tokExtends |
30 |
|
|
tokMixin |
31 |
|
|
tokMixinCall |
32 |
|
|
) |
33 |
|
|
|
34 |
|
|
const ( |
35 |
|
|
scnNewLine = iota |
36 |
|
|
scnLine |
37 |
|
|
scnEOF |
38 |
|
|
) |
39 |
|
|
|
40 |
|
|
type scanner struct { |
41 |
|
|
reader *bufio.Reader |
42 |
|
|
indentStack *list.List |
43 |
|
|
stash *list.List |
44 |
|
|
|
45 |
|
|
state int32 |
46 |
|
|
buffer string |
47 |
|
|
|
48 |
|
|
line int |
49 |
|
|
col int |
50 |
|
|
lastTokenLine int |
51 |
|
|
lastTokenCol int |
52 |
|
|
lastTokenSize int |
53 |
|
|
|
54 |
|
|
readRaw bool |
55 |
|
|
} |
56 |
|
|
|
57 |
|
|
type token struct { |
58 |
|
|
Kind rune |
59 |
|
|
Value string |
60 |
|
|
Data map[string]string |
61 |
|
|
} |
62 |
|
|
|
63 |
|
|
func newScanner(r io.Reader) *scanner { |
64 |
|
|
s := new(scanner) |
65 |
|
|
s.reader = bufio.NewReader(r) |
66 |
|
|
s.indentStack = list.New() |
67 |
|
|
s.stash = list.New() |
68 |
|
|
s.state = scnNewLine |
69 |
|
|
s.line = -1 |
70 |
|
|
s.col = 0 |
71 |
|
|
|
72 |
|
|
return s |
73 |
|
|
} |
74 |
|
|
|
75 |
|
|
func (s *scanner) Pos() SourcePosition { |
76 |
|
|
return SourcePosition{s.lastTokenLine + 1, s.lastTokenCol + 1, s.lastTokenSize, ""} |
77 |
|
|
} |
78 |
|
|
|
79 |
|
|
// Returns next token found in buffer |
80 |
|
|
func (s *scanner) Next() *token { |
81 |
|
|
if s.readRaw { |
82 |
|
|
s.readRaw = false |
83 |
|
|
return s.NextRaw() |
84 |
|
|
} |
85 |
|
|
|
86 |
|
|
s.ensureBuffer() |
87 |
|
|
|
88 |
|
|
if stashed := s.stash.Front(); stashed != nil { |
89 |
|
|
tok := stashed.Value.(*token) |
90 |
|
|
s.stash.Remove(stashed) |
91 |
|
|
return tok |
92 |
|
|
} |
93 |
|
|
|
94 |
|
|
switch s.state { |
95 |
|
|
case scnEOF: |
96 |
|
|
if outdent := s.indentStack.Back(); outdent != nil { |
97 |
|
|
s.indentStack.Remove(outdent) |
98 |
|
|
return &token{tokOutdent, "", nil} |
99 |
|
|
} |
100 |
|
|
|
101 |
|
|
return &token{tokEOF, "", nil} |
102 |
|
|
case scnNewLine: |
103 |
|
|
s.state = scnLine |
104 |
|
|
|
105 |
|
|
if tok := s.scanIndent(); tok != nil { |
106 |
|
|
return tok |
107 |
|
|
} |
108 |
|
|
|
109 |
|
|
return s.Next() |
110 |
|
|
case scnLine: |
111 |
|
|
if tok := s.scanMixin(); tok != nil { |
112 |
|
|
return tok |
113 |
|
|
} |
114 |
|
|
|
115 |
|
|
if tok := s.scanMixinCall(); tok != nil { |
116 |
|
|
return tok |
117 |
|
|
} |
118 |
|
|
|
119 |
|
|
if tok := s.scanDoctype(); tok != nil { |
120 |
|
|
return tok |
121 |
|
|
} |
122 |
|
|
|
123 |
|
|
if tok := s.scanCondition(); tok != nil { |
124 |
|
|
return tok |
125 |
|
|
} |
126 |
|
|
|
127 |
|
|
if tok := s.scanEach(); tok != nil { |
128 |
|
|
return tok |
129 |
|
|
} |
130 |
|
|
|
131 |
|
|
if tok := s.scanImport(); tok != nil { |
132 |
|
|
return tok |
133 |
|
|
} |
134 |
|
|
|
135 |
|
|
if tok := s.scanExtends(); tok != nil { |
136 |
|
|
return tok |
137 |
|
|
} |
138 |
|
|
|
139 |
|
|
if tok := s.scanBlock(); tok != nil { |
140 |
|
|
return tok |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
if tok := s.scanAssignment(); tok != nil { |
144 |
|
|
return tok |
145 |
|
|
} |
146 |
|
|
|
147 |
|
|
if tok := s.scanTag(); tok != nil { |
148 |
|
|
return tok |
149 |
|
|
} |
150 |
|
|
|
151 |
|
|
if tok := s.scanId(); tok != nil { |
152 |
|
|
return tok |
153 |
|
|
} |
154 |
|
|
|
155 |
|
|
if tok := s.scanClassName(); tok != nil { |
156 |
|
|
return tok |
157 |
|
|
} |
158 |
|
|
|
159 |
|
|
if tok := s.scanAttribute(); tok != nil { |
160 |
|
|
return tok |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
if tok := s.scanComment(); tok != nil { |
164 |
|
|
return tok |
165 |
|
|
} |
166 |
|
|
|
167 |
|
|
if tok := s.scanText(); tok != nil { |
168 |
|
|
return tok |
169 |
|
|
} |
170 |
|
|
} |
171 |
|
|
|
172 |
|
|
return nil |
173 |
|
|
} |
174 |
|
|
|
175 |
|
|
func (s *scanner) NextRaw() *token { |
176 |
|
|
result := "" |
177 |
|
|
level := 0 |
178 |
|
|
|
179 |
|
|
for { |
180 |
|
|
s.ensureBuffer() |
181 |
|
|
|
182 |
|
|
switch s.state { |
183 |
|
|
case scnEOF: |
184 |
|
|
return &token{tokText, result, map[string]string{"Mode": "raw"}} |
185 |
|
|
case scnNewLine: |
186 |
|
|
s.state = scnLine |
187 |
|
|
|
188 |
|
|
if tok := s.scanIndent(); tok != nil { |
189 |
|
|
if tok.Kind == tokIndent { |
190 |
|
|
level++ |
191 |
|
|
} else if tok.Kind == tokOutdent { |
192 |
|
|
level-- |
193 |
|
|
} else { |
194 |
|
|
result = result + "\n" |
195 |
|
|
continue |
196 |
|
|
} |
197 |
|
|
|
198 |
|
|
if level < 0 { |
199 |
|
|
s.stash.PushBack(&token{tokOutdent, "", nil}) |
200 |
|
|
|
201 |
|
|
if len(result) > 0 && result[len(result)-1] == '\n' { |
202 |
|
|
result = result[:len(result)-1] |
203 |
|
|
} |
204 |
|
|
|
205 |
|
|
return &token{tokText, result, map[string]string{"Mode": "raw"}} |
206 |
|
|
} |
207 |
|
|
} |
208 |
|
|
case scnLine: |
209 |
|
|
if len(result) > 0 { |
210 |
|
|
result = result + "\n" |
211 |
|
|
} |
212 |
|
|
for i := 0; i < level; i++ { |
213 |
|
|
result += "\t" |
214 |
|
|
} |
215 |
|
|
result = result + s.buffer |
216 |
|
|
s.consume(len(s.buffer)) |
217 |
|
|
} |
218 |
|
|
} |
219 |
|
|
|
220 |
|
|
return nil |
221 |
|
|
} |
222 |
|
|
|
223 |
|
|
var rgxIndent = regexp.MustCompile(`^(\s+)`) |
224 |
|
|
|
225 |
|
|
func (s *scanner) scanIndent() *token { |
226 |
|
|
if len(s.buffer) == 0 { |
227 |
|
|
return &token{tokBlank, "", nil} |
228 |
|
|
} |
229 |
|
|
|
230 |
|
|
var head *list.Element |
231 |
|
|
for head = s.indentStack.Front(); head != nil; head = head.Next() { |
232 |
|
|
value := head.Value.(*regexp.Regexp) |
233 |
|
|
|
234 |
|
|
if match := value.FindString(s.buffer); len(match) != 0 { |
235 |
|
|
s.consume(len(match)) |
236 |
|
|
} else { |
237 |
|
|
break |
238 |
|
|
} |
239 |
|
|
} |
240 |
|
|
|
241 |
|
|
newIndent := rgxIndent.FindString(s.buffer) |
242 |
|
|
|
243 |
|
|
if len(newIndent) != 0 && head == nil { |
244 |
|
|
s.indentStack.PushBack(regexp.MustCompile(regexp.QuoteMeta(newIndent))) |
245 |
|
|
s.consume(len(newIndent)) |
246 |
|
|
return &token{tokIndent, newIndent, nil} |
247 |
|
|
} |
248 |
|
|
|
249 |
|
|
if len(newIndent) == 0 && head != nil { |
250 |
|
|
for head != nil { |
251 |
|
|
next := head.Next() |
252 |
|
|
s.indentStack.Remove(head) |
253 |
|
|
if next == nil { |
254 |
|
|
return &token{tokOutdent, "", nil} |
255 |
|
|
} else { |
256 |
|
|
s.stash.PushBack(&token{tokOutdent, "", nil}) |
257 |
|
|
} |
258 |
|
|
head = next |
259 |
|
|
} |
260 |
|
|
} |
261 |
|
|
|
262 |
|
|
if len(newIndent) != 0 && head != nil { |
263 |
|
|
panic("Mismatching indentation. Please use a coherent indent schema.") |
264 |
|
|
} |
265 |
|
|
|
266 |
|
|
return nil |
267 |
|
|
} |
268 |
|
|
|
269 |
|
|
var rgxDoctype = regexp.MustCompile(`^(!!!|doctype)\s*(.*)`) |
270 |
|
|
|
271 |
|
|
func (s *scanner) scanDoctype() *token { |
272 |
|
|
if sm := rgxDoctype.FindStringSubmatch(s.buffer); len(sm) != 0 { |
273 |
|
|
if len(sm[2]) == 0 { |
274 |
|
|
sm[2] = "html" |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
s.consume(len(sm[0])) |
278 |
|
|
return &token{tokDoctype, sm[2], nil} |
279 |
|
|
} |
280 |
|
|
|
281 |
|
|
return nil |
282 |
|
|
} |
283 |
|
|
|
284 |
|
|
var rgxIf = regexp.MustCompile(`^if\s+(.+)$`) |
285 |
|
|
var rgxElse = regexp.MustCompile(`^else\s*`) |
286 |
|
|
|
287 |
|
|
func (s *scanner) scanCondition() *token { |
288 |
|
|
if sm := rgxIf.FindStringSubmatch(s.buffer); len(sm) != 0 { |
289 |
|
|
s.consume(len(sm[0])) |
290 |
|
|
return &token{tokIf, sm[1], nil} |
291 |
|
|
} |
292 |
|
|
|
293 |
|
|
if sm := rgxElse.FindStringSubmatch(s.buffer); len(sm) != 0 { |
294 |
|
|
s.consume(len(sm[0])) |
295 |
|
|
return &token{tokElse, "", nil} |
296 |
|
|
} |
297 |
|
|
|
298 |
|
|
return nil |
299 |
|
|
} |
300 |
|
|
|
301 |
|
|
var rgxEach = regexp.MustCompile(`^each\s+(\$[\w0-9\-_]*)(?:\s*,\s*(\$[\w0-9\-_]*))?\s+in\s+(.+)$`) |
302 |
|
|
|
303 |
|
|
func (s *scanner) scanEach() *token { |
304 |
|
|
if sm := rgxEach.FindStringSubmatch(s.buffer); len(sm) != 0 { |
305 |
|
|
s.consume(len(sm[0])) |
306 |
|
|
return &token{tokEach, sm[3], map[string]string{"X": sm[1], "Y": sm[2]}} |
307 |
|
|
} |
308 |
|
|
|
309 |
|
|
return nil |
310 |
|
|
} |
311 |
|
|
|
312 |
|
|
var rgxAssignment = regexp.MustCompile(`^(\$[\w0-9\-_]*)?\s*=\s*(.+)$`) |
313 |
|
|
|
314 |
|
|
func (s *scanner) scanAssignment() *token { |
315 |
|
|
if sm := rgxAssignment.FindStringSubmatch(s.buffer); len(sm) != 0 { |
316 |
|
|
s.consume(len(sm[0])) |
317 |
|
|
return &token{tokAssignment, sm[2], map[string]string{"X": sm[1]}} |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
return nil |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
var rgxComment = regexp.MustCompile(`^\/\/(-)?\s*(.*)$`) |
324 |
|
|
|
325 |
|
|
func (s *scanner) scanComment() *token { |
326 |
|
|
if sm := rgxComment.FindStringSubmatch(s.buffer); len(sm) != 0 { |
327 |
|
|
mode := "embed" |
328 |
|
|
if len(sm[1]) != 0 { |
329 |
|
|
mode = "silent" |
330 |
|
|
} |
331 |
|
|
|
332 |
|
|
s.consume(len(sm[0])) |
333 |
|
|
return &token{tokComment, sm[2], map[string]string{"Mode": mode}} |
334 |
|
|
} |
335 |
|
|
|
336 |
|
|
return nil |
337 |
|
|
} |
338 |
|
|
|
339 |
|
|
var rgxId = regexp.MustCompile(`^#([\w-]+)(?:\s*\?\s*(.*)$)?`) |
340 |
|
|
|
341 |
|
|
func (s *scanner) scanId() *token { |
342 |
|
|
if sm := rgxId.FindStringSubmatch(s.buffer); len(sm) != 0 { |
343 |
|
|
s.consume(len(sm[0])) |
344 |
|
|
return &token{tokId, sm[1], map[string]string{"Condition": sm[2]}} |
345 |
|
|
} |
346 |
|
|
|
347 |
|
|
return nil |
348 |
|
|
} |
349 |
|
|
|
350 |
|
|
var rgxClassName = regexp.MustCompile(`^\.([\w-]+)(?:\s*\?\s*(.*)$)?`) |
351 |
|
|
|
352 |
|
|
func (s *scanner) scanClassName() *token { |
353 |
|
|
if sm := rgxClassName.FindStringSubmatch(s.buffer); len(sm) != 0 { |
354 |
|
|
s.consume(len(sm[0])) |
355 |
|
|
return &token{tokClassName, sm[1], map[string]string{"Condition": sm[2]}} |
356 |
|
|
} |
357 |
|
|
|
358 |
|
|
return nil |
359 |
|
|
} |
360 |
|
|
|
361 |
|
|
var rgxAttribute = regexp.MustCompile(`^\[([\w\-:@\.]+)\s*(?:=\s*(\"([^\"\\]*)\"|([^\]]+)))?\](?:\s*\?\s*(.*)$)?`) |
362 |
|
|
|
363 |
|
|
func (s *scanner) scanAttribute() *token { |
364 |
|
|
if sm := rgxAttribute.FindStringSubmatch(s.buffer); len(sm) != 0 { |
365 |
|
|
s.consume(len(sm[0])) |
366 |
|
|
|
367 |
|
|
if len(sm[3]) != 0 || sm[2] == "" { |
368 |
|
|
return &token{tokAttribute, sm[1], map[string]string{"Content": sm[3], "Mode": "raw", "Condition": sm[5]}} |
369 |
|
|
} |
370 |
|
|
|
371 |
|
|
return &token{tokAttribute, sm[1], map[string]string{"Content": sm[4], "Mode": "expression", "Condition": sm[5]}} |
372 |
|
|
} |
373 |
|
|
|
374 |
|
|
return nil |
375 |
|
|
} |
376 |
|
|
|
377 |
|
|
var rgxImport = regexp.MustCompile(`^import\s+([0-9a-zA-Z_\-\. \/]*)$`) |
378 |
|
|
|
379 |
|
|
func (s *scanner) scanImport() *token { |
380 |
|
|
if sm := rgxImport.FindStringSubmatch(s.buffer); len(sm) != 0 { |
381 |
|
|
s.consume(len(sm[0])) |
382 |
|
|
return &token{tokImport, sm[1], nil} |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
return nil |
386 |
|
|
} |
387 |
|
|
|
388 |
|
|
var rgxExtends = regexp.MustCompile(`^extends\s+([0-9a-zA-Z_\-\. \/]*)$`) |
389 |
|
|
|
390 |
|
|
func (s *scanner) scanExtends() *token { |
391 |
|
|
if sm := rgxExtends.FindStringSubmatch(s.buffer); len(sm) != 0 { |
392 |
|
|
s.consume(len(sm[0])) |
393 |
|
|
return &token{tokExtends, sm[1], nil} |
394 |
|
|
} |
395 |
|
|
|
396 |
|
|
return nil |
397 |
|
|
} |
398 |
|
|
|
399 |
|
|
var rgxBlock = regexp.MustCompile(`^block\s+(?:(append|prepend)\s+)?([0-9a-zA-Z_\-\. \/]*)$`) |
400 |
|
|
|
401 |
|
|
func (s *scanner) scanBlock() *token { |
402 |
|
|
if sm := rgxBlock.FindStringSubmatch(s.buffer); len(sm) != 0 { |
403 |
|
|
s.consume(len(sm[0])) |
404 |
|
|
return &token{tokNamedBlock, sm[2], map[string]string{"Modifier": sm[1]}} |
405 |
|
|
} |
406 |
|
|
|
407 |
|
|
return nil |
408 |
|
|
} |
409 |
|
|
|
410 |
|
|
var rgxTag = regexp.MustCompile(`^(\w[-:\w]*)`) |
411 |
|
|
|
412 |
|
|
func (s *scanner) scanTag() *token { |
413 |
|
|
if sm := rgxTag.FindStringSubmatch(s.buffer); len(sm) != 0 { |
414 |
|
|
s.consume(len(sm[0])) |
415 |
|
|
return &token{tokTag, sm[1], nil} |
416 |
|
|
} |
417 |
|
|
|
418 |
|
|
return nil |
419 |
|
|
} |
420 |
|
|
|
421 |
|
|
var rgxMixin = regexp.MustCompile(`^mixin ([a-zA-Z_-]+\w*)(\(((\$\w*(,\s)?)*)\))?$`) |
422 |
|
|
|
423 |
|
|
func (s *scanner) scanMixin() *token { |
424 |
|
|
if sm := rgxMixin.FindStringSubmatch(s.buffer); len(sm) != 0 { |
425 |
|
|
s.consume(len(sm[0])) |
426 |
|
|
return &token{tokMixin, sm[1], map[string]string{"Args": sm[3]}} |
427 |
|
|
} |
428 |
|
|
|
429 |
|
|
return nil |
430 |
|
|
} |
431 |
|
|
|
432 |
|
|
var rgxMixinCall = regexp.MustCompile(`^\+([A-Za-z_-]+\w*)(\((.+(,\s)?)*\))?$`) |
433 |
|
|
|
434 |
|
|
func (s *scanner) scanMixinCall() *token { |
435 |
|
|
if sm := rgxMixinCall.FindStringSubmatch(s.buffer); len(sm) != 0 { |
436 |
|
|
s.consume(len(sm[0])) |
437 |
|
|
return &token{tokMixinCall, sm[1], map[string]string{"Args": sm[3]}} |
438 |
|
|
} |
439 |
|
|
|
440 |
|
|
return nil |
441 |
|
|
} |
442 |
|
|
|
443 |
|
|
var rgxText = regexp.MustCompile(`^(\|)? ?(.*)$`) |
444 |
|
|
|
445 |
|
|
func (s *scanner) scanText() *token { |
446 |
|
|
if sm := rgxText.FindStringSubmatch(s.buffer); len(sm) != 0 { |
447 |
|
|
s.consume(len(sm[0])) |
448 |
|
|
|
449 |
|
|
mode := "inline" |
450 |
|
|
if sm[1] == "|" { |
451 |
|
|
mode = "piped" |
452 |
|
|
} |
453 |
|
|
|
454 |
|
|
return &token{tokText, sm[2], map[string]string{"Mode": mode}} |
455 |
|
|
} |
456 |
|
|
|
457 |
|
|
return nil |
458 |
|
|
} |
459 |
|
|
|
460 |
|
|
// Moves position forward, and removes beginning of s.buffer (len bytes) |
461 |
|
|
func (s *scanner) consume(runes int) { |
462 |
|
|
if len(s.buffer) < runes { |
463 |
|
|
panic(fmt.Sprintf("Unable to consume %d runes from buffer.", runes)) |
464 |
|
|
} |
465 |
|
|
|
466 |
|
|
s.lastTokenLine = s.line |
467 |
|
|
s.lastTokenCol = s.col |
468 |
|
|
s.lastTokenSize = runes |
469 |
|
|
|
470 |
|
|
s.buffer = s.buffer[runes:] |
471 |
|
|
s.col += runes |
472 |
|
|
} |
473 |
|
|
|
474 |
|
|
// Reads string into s.buffer |
475 |
|
|
func (s *scanner) ensureBuffer() { |
476 |
|
|
if len(s.buffer) > 0 { |
477 |
|
|
return |
478 |
|
|
} |
479 |
|
|
|
480 |
|
|
buf, err := s.reader.ReadString('\n') |
481 |
|
|
|
482 |
|
|
if err != nil && err != io.EOF { |
483 |
|
|
panic(err) |
484 |
|
|
} else if err != nil && len(buf) == 0 { |
485 |
|
|
s.state = scnEOF |
486 |
|
|
} else { |
487 |
|
|
// endline "LF only" or "\n" use Unix, Linux, modern MacOS X, FreeBSD, BeOS, RISC OS |
488 |
|
|
if buf[len(buf)-1] == '\n' { |
489 |
|
|
buf = buf[:len(buf)-1] |
490 |
|
|
} |
491 |
|
|
// endline "CR+LF" or "\r\n" use internet protocols, DEC RT-11, Windows, CP/M, MS-DOS, OS/2, Symbian OS |
492 |
|
|
if len(buf) > 0 && buf[len(buf)-1] == '\r' { |
493 |
|
|
buf = buf[:len(buf)-1] |
494 |
|
|
} |
495 |
|
|
|
496 |
|
|
s.state = scnNewLine |
497 |
|
|
s.buffer = buf |
498 |
|
|
s.line += 1 |
499 |
|
|
s.col = 0 |
500 |
|
|
} |
501 |
|
|
} |