/[aya]/vendor/github.com/eknkc/amber/parser/scanner.go
ViewVC logotype

Annotation of /vendor/github.com/eknkc/amber/parser/scanner.go

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (hide annotations)
Mon Sep 30 00:42:06 2024 UTC (6 weeks, 4 days ago) by yakumo_izuru
Branch: MAIN
CVS Tags: HEAD
Mirrored from https://git.chaotic.ninja/git/yakumo_izuru/aya

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     }

nishi@chaotic.ninja
ViewVC Help
Powered by ViewVC 1.3.0-dev