scratch – Blame information for rev 66

Subversion Repositories:
Rev:
Rev Author Line No. Line
66 office 1 var helpers = require('./helpers');
2  
3 // --- AST
4  
5 /**
6 * Initialize with _tokens_.
7 */
8  
9 function AST(tokens, str) {
10 this.tokens = tokens;
11  
12 // Windows new line support (CR+LF, \r\n)
13 str = str.replace(/\r\n/g, '\n');
14  
15 // Use a regex to do this magic
16 this.lines = str.split(/\n/g).map(function(i){ return i + '\n'});
17 this.strLength = str.length;
18 }
19  
20 /**
21 * Look-ahead a single token.
22 *
23 * @return {array}
24 * @api public
25 */
26  
27 AST.prototype.peek = function() {
28 return this.tokens[0]
29 }
30  
31 /**
32 * Advance by a single token.
33 *
34 * @return {array}
35 * @api public
36 */
37  
38 AST.prototype.advance = function() {
39 return this.tokens.shift()
40 }
41  
42 /**
43 * Advance and return the token's value.
44 *
45 * @return {mixed}
46 * @api private
47 */
48  
49 AST.prototype.advanceValue = function() {
50 return this.advance()[1][1]
51 }
52  
53 /**
54 * Accept _type_ and advance or do nothing.
55 *
56 * @param {string} type
57 * @return {bool}
58 * @api private
59 */
60  
61 AST.prototype.accept = function(type) {
62 if (this.peekType(type))
63 return this.advance()
64 }
65  
66 /**
67 * Expect _type_ or throw an error _msg_.
68 *
69 * @param {string} type
70 * @param {string} msg
71 * @api private
72 */
73  
74 AST.prototype.expect = function(type, msg) {
75 if (this.accept(type)) return
76 throw new Error(msg + ', ' + helpers.context(this.peek()[1].input))
77 }
78  
79 /**
80 * Return the next token type.
81 *
82 * @return {string}
83 * @api private
84 */
85  
86 AST.prototype.peekType = function(val) {
87 return this.tokens[0] &&
88 this.tokens[0][0] === val
89 }
90  
91 /**
92 * space*
93 */
94  
95 AST.prototype.ignoreSpace = function() {
96 while (this.peekType('space'))
97 this.advance()
98 }
99  
100 /**
101 * (space | indent | dedent)*
102 */
103  
104 AST.prototype.ignoreWhitespace = function() {
105 while (this.peekType('space') ||
106 this.peekType('indent') ||
107 this.peekType('dedent'))
108 this.advance()
109 }
110  
111 // constructor functions
112 function YAMLDoc() {
113 this.node = 'YAMLDoc';
114 }
115 function YAMLHash() {
116 this.node = 'YAMLHash';
117 this.keys = [];
118 }
119 function YAMLHashKey(id) {
120 this.node = 'YAMLHashKey';
121 this.keyName = id[1][0]
122 }
123 function YAMLList() {
124 this.node = 'YAMLList';
125 this.items = [];
126 }
127 function YAMLInt(token){
128 this.node = 'YAMLInt';
129 this.raw = token[1][0];
130 this.value = parseInt(token[1][0]);
131 }
132 function YAMLFloat(token) {
133 this.node = 'YAMLFloat';
134 this.raw = token[1][0];
135 this.value = parseFloat(token[1][0]);
136 }
137 function YAMLString(token) {
138 var raw = token[1][0];
139  
140 this.raw = raw;
141 this.node = 'YAMLString';
142  
143 if (raw[0] === raw[raw.length - 1] && (raw[0] === '"' || raw[0] === '\'')){
144 // Remove quotation marks
145 this.value = raw.substring(1, raw.length - 1);
146 } else {
147 this.value = token[1][0];
148 }
149 }
150 function YAMLTrue(token) {
151 this.node = 'YAMLTrue';
152 this.raw = token[1][0];
153 this.value = true
154 }
155 function YAMLFalse(token) {
156 this.node = 'YAMLFalse';
157 this.raw = token[1][0];
158 this.value = false;
159 }
160 function YAMLNull(token) {
161 this.node = 'YAMLNull';
162 this.raw = token[1][0];
163 this.value = null;
164 }
165 function YAMLDate(token) {
166 this.node = 'YAMLDate';
167 this.raw = token[1][0];
168 this.value = helpers.parseTimestamp(token[1]);
169 }
170  
171 AST.prototype.parse = function() {
172 switch (this.peek()[0]) {
173 case 'doc':
174 return this.parseDoc();
175 case '-':
176 return this.parseList();
177 case '{':
178 return this.parseInlineHash();
179 case '[':
180 return this.parseInlineList();
181 case 'id':
182 return this.parseHash();
183 case 'string':
184 return this.parseValue(YAMLString);
185 case 'timestamp':
186 return this.parseValue(YAMLDate);
187 case 'float':
188 return this.parseValue(YAMLFloat);
189 case 'int':
190 return this.parseValue(YAMLInt);
191 case 'true':
192 return this.parseValue(YAMLTrue);
193 case 'false':
194 return this.parseValue(YAMLFalse);
195 case 'null':
196 return this.parseValue(YAMLNull);
197 }
198 };
199  
200 AST.prototype.parseDoc = function() {
201 this.accept('doc');
202 this.expect('indent', 'expected indent after document');
203 var val = this.parse();
204 this.expect('dedent', 'document not properly dedented');
205 var yamlDoc = new YAMLDoc();
206 yamlDoc.value = val;
207 yamlDoc.start = this.indexToRowCol(0);
208 yamlDoc.end = this.indexToRowCol(this.strLength - 1);
209 return yamlDoc;
210 }
211  
212 AST.prototype.parseHash = function() {
213 var id, hash = new YAMLHash();
214 while (this.peekType('id') && (id = this.advance())) {
215 this.expect(':', 'expected semi-colon after id');
216 this.ignoreSpace();
217 var hashKey = new YAMLHashKey(id);
218 this.assignStartEnd(hashKey, id);
219 if (this.accept('indent')) {
220 hashKey.value = this.parse();
221 if (this.tokens.length){
222 this.expect('dedent', 'hash not properly dedented');
223 }
224 } else {
225 hashKey.value = this.parse();
226 }
227 hash.keys.push(hashKey);
228 this.ignoreSpace();
229 }
230  
231 // Add start and end to the hash based on start of the first key
232 // and end of the last key
233 hash.start = hash.keys[0].start;
234 hash.end = hash.keys[hash.keys.length - 1].value.end;
235  
236 return hash;
237 }
238  
239 AST.prototype.parseInlineHash = function() {
240 var hash = new YAMLHash(), id, i = 0;
241 this.accept('{');
242  
243 while (!this.accept('}')) {
244 this.ignoreSpace();
245  
246 if (i) {
247 this.expect(',', 'expected comma');
248 }
249 this.ignoreWhitespace();
250  
251 if (this.peekType('id') && (id = this.advance())) {
252 var hashKey = new YAMLHashKey(id);
253 this.assignStartEnd(hashKey, id);
254 this.expect(':', 'expected semi-colon after id');
255 this.ignoreSpace();
256 hashKey.value = this.parse();
257 hash.keys.push(hashKey);
258 this.ignoreWhitespace();
259 }
260 ++i;
261 }
262  
263 // Add start and end to the hash based on start of the first key
264 // and end of the last key
265 hash.start = hash.keys[0].start;
266 hash.end = hash.keys[hash.keys.length - 1].value.end;
267  
268 return hash;
269 }
270  
271 AST.prototype.parseList = function() {
272 var list = new YAMLList();
273 var begining, end;
274  
275 begining = this.accept('-');
276 while (true) {
277 this.ignoreSpace();
278  
279 if (this.accept('indent')) {
280 list.items.push(this.parse());
281 this.expect('dedent', 'list item not properly dedented');
282 } else{
283 list.items.push(this.parse());
284 }
285  
286 this.ignoreSpace();
287  
288 end = this.accept('-');
289  
290 if (end){
291 // Keep a copy of last end to use it for list.end
292 endBuffer = end;
293 } else {
294 end = endBuffer;
295 break;
296 }
297 }
298  
299 list.start = begining[2];
300 list.end = end[3];
301  
302 return list;
303 }
304  
305 AST.prototype.parseInlineList = function() {
306 var list = new YAMLList(), i = 0;
307 var begining = this.accept('[');
308 var end = this.accept(']');
309  
310 while (!end) {
311 this.ignoreSpace();
312 if (i) this.expect(',', 'expected comma');
313 this.ignoreSpace();
314 list.items.push(this.parse());
315 this.ignoreSpace();
316 ++i;
317 end = this.accept(']');
318 }
319  
320 list.start = begining[2];
321 list.end = end[3];
322  
323 return list
324 }
325  
326 AST.prototype.parseValue = function(constructorFn) {
327 var token = this.advance();
328 var value = new constructorFn(token);
329 this.assignStartEnd(value, token);
330 return value;
331 }
332  
333  
334 AST.prototype.assignStartEnd = function (node, token) {
335 node.start = this.indexToRowCol(token[2]);
336 node.end = this.indexToRowCol(token[3]);
337 }
338  
339 AST.prototype.indexToRowCol = function (index) {
340 if (!this.lines) return null;
341  
342 for (var l = 0; l < this.lines.length; l++) {
343 if (index >= this.lines[l].length){
344 index -= this.lines[l].length;
345 } else {
346 break;
347 }
348 }
349  
350 return {
351 row: l,
352 column: index
353 };
354 }
355  
356 module.exports = AST;