@@ -2,6 +2,8 @@ import mustang/[Node, TagParser]
2
2
3
3
import io/[File, FileReader]
4
4
import structs/[Stack, List, LinkedList]
5
+ import text/Regexp
6
+ import text/Buffer
5
7
6
8
7
9
/* *
@@ -19,14 +21,19 @@ import structs/[Stack, List, LinkedList]
19
21
*/
20
22
TemplateParser : class {
21
23
templateText : String
22
- index : Int
24
+
25
+ state : Int
26
+ index , currentLine , currentColumn : Int
27
+ buffer : Buffer
28
+ nextChar : Char
29
+ skipChar : Bool
23
30
24
31
startTag , endTag : String
32
+ tagRegex : Regexp
25
33
parsers : LinkedList <TagParser >
26
34
27
35
firstNode , currentNode : TNode
28
36
nodeStack : Stack <TNode >
29
- running : Bool
30
37
31
38
getParserFromFile : static func (file : File ) -> This {
32
39
size := file size ()
@@ -37,9 +44,10 @@ TemplateParser: class {
37
44
}
38
45
39
46
init : func (= templateText , = startTag , = endTag ) {
40
- index = 0
47
+ buffer = Buffer new ( 1024 )
41
48
parsers = LinkedList< TagParser> new ()
42
49
nodeStack = Stack< TNode> new ()
50
+ tagRegex = Regexp compile ("^%s.+%s$" format (startTag, endTag), RegexpOption DOTALL)
43
51
44
52
// load builtin parsers
45
53
loadBuiltinParsers (parsers)
@@ -68,83 +76,109 @@ TemplateParser: class {
68
76
}
69
77
70
78
parse : func -> TNode {
71
- running = true
79
+ state = ParserState PARSE_TEXT
80
+ index = 0 ; currentLine = 1 ; currentColumn = 0
81
+ buffer size = 0
82
+ currentNode = null
83
+ skipChar = false
72
84
73
- while (true ) {
74
- if (! parseText ()) break
75
- if (! parseTag ()) break
76
- }
85
+ // Parser state machine
86
+ while (state ! = ParserState STOP) {
87
+ nextChar = templateText charAt (index)
77
88
78
- return firstNode
79
- }
89
+ if (state == ParserState PARSE_TEXT) state = parseText ()
90
+ else if (state == ParserState PARSE_TAG) state = parseTag ()
91
+ else Exception new ("Invalid parser state: %d" format (state)) throw ()
80
92
81
- parseText : func -> Bool {
82
- if (index >= templateText length ()) {
83
- // End of template, stop parsing
84
- return false
85
- }
86
-
87
- // Find the start of the next tag
88
- indexOfNextTag := templateText indexOf (startTag, index)
93
+ if (nextChar == '\0 ' ) {
94
+ // End of template, stop parsing!
95
+ break
96
+ }
97
+ else if (nextChar == '\n ' ) {
98
+ // Increment line count and reset column
99
+ currentLine += 1
100
+ currentColumn = 0
101
+ }
102
+ else {
103
+ currentColumn += 1
104
+ }
89
105
90
- if (indexOfNextTag == - 1 ) {
91
- // No more tags to parse, rest of context is plaintext.
92
- text := templateText substring (index)
93
- appendNode (TextNode new (text))
94
- return false
95
- }
96
- else if (indexOfNextTag ! = index) {
97
- text := templateText substring (index, indexOfNextTag)
98
- appendNode (TextNode new (text))
106
+ if (! skipChar) buffer append (nextChar)
107
+ else skipChar = false
108
+ index += 1
99
109
}
100
110
101
- index = indexOfNextTag
102
-
103
- return true
111
+ return firstNode
104
112
}
105
113
106
- parseTag : func -> Bool {
107
- // Read the tag from the template
108
- // TODO: probably best we do this in a regex.
109
- // as of now triple mustaches don't parse right.
110
- index += startTag length () // skip open tag
111
- indexOfEndTag := templateText indexOf (endTag, index) // get index of end tag
112
- tag := templateText substring (index, indexOfEndTag) trim () // substring out the tag content
113
- index = indexOfEndTag + endTag length () // advance past end tag
114
-
115
- // End of tag block
116
- if (tag first () == '/' ) {
117
- endBlock ()
118
- if (templateText charAt (index) == '\n ' ) {
119
- // Skip any newline after the ending block tag
120
- index += 1
114
+ parseText : func -> Int {
115
+ // If at the start of a tag or end of template,
116
+ // create a new TextNode to store the buffered text.
117
+ if (nextChar == '\0 ' || templateText compare (startTag, index)) {
118
+ if (buffer size) {
119
+ appendNode (TextNode new (buffer toString () clone ()))
120
+ buffer size = 0
121
121
}
122
- return true
122
+ return ParserState PARSE_TAG
123
123
}
124
124
125
- // Iterate through parsers and try to find a match
126
- for (p : TagParser in parsers) {
127
- if (p matches (tag)) {
128
- node := p parse (tag)
129
- if (node) {
130
- appendNode (node)
125
+ return ParserState PARSE_TEXT
126
+ }
127
+
128
+ parseTag : func -> Int {
129
+ if (tagRegex matches (buffer toString ())) {
130
+ tag := buffer toString ()
131
+
132
+ // Strip off start and end mustaches plus whitespace
133
+ tag = tag substring (startTag length (), tag length () - endTag length ()) trim ()
134
+ buffer size = 0
135
+
136
+ if (tag first () == '/' ) {
137
+ // Block end tag
138
+ endBlock ()
139
+ if (nextChar == '\n ' ) {
140
+ // Skip any newline after the block tag.
141
+ skipChar = true
131
142
}
132
143
133
- if (p isBlock ()) {
134
- startBlock ()
135
- if (templateText charAt (index) == '\n ' ) {
136
- // Skip any newline that comes after a block start tag
137
- index += 1
144
+ return ParserState PARSE_TEXT
145
+ }
146
+ else {
147
+ // Find a parser for the tag and generate node
148
+ for (p : TagParser in parsers) {
149
+ if (p matches (tag)) {
150
+ node := p parse (tag)
151
+ if (node) {
152
+ appendNode (node)
153
+ }
154
+
155
+ if (p isBlock ()) {
156
+ startBlock ()
157
+ if (nextChar == '\n ' ) {
158
+ // Skip any newlines after a block tag.
159
+ skipChar = true
160
+ }
161
+ }
162
+
163
+ return ParserState PARSE_TEXT
138
164
}
139
165
}
140
166
141
- return true
167
+ Exception new ( "Invald tag: %s" format (tag)) throw ()
142
168
}
143
169
}
144
170
145
- // If control reaches here, no parser could be found for this tag.
146
- // Alert the users of the error.
147
- "ERROR: invalid tag --> {{%s}}" format (tag) println ()
148
- return false
171
+ else if (nextChar == '\0 ' ) {
172
+ Exception new ("Opps, looks like you have an incomplete tag at the end of the file!" ) throw ()
173
+ }
174
+
175
+ return ParserState PARSE_TAG
149
176
}
150
177
}
178
+
179
+ ParserState : class {
180
+ STOP : static Int = 1
181
+ PARSE_TEXT : static Int = 2
182
+ START_PARSE_TAG : static Int = 3
183
+ PARSE_TAG : static Int = 4
184
+ }
0 commit comments