sync
[gnome.introspection-doc-generator] / JSDOC / TokenReader.js
1 //<script type="text/javascript">
2
3  
4 XObject = imports.XObject.XObject;
5 console = imports.console.console;
6
7
8 Token   = imports.Token.Token;
9 Lang    = imports.Lang.Lang;
10
11 /**
12         @class Search a {@link JSDOC.TextStream} for language tokens.
13 */
14 TokenReader = XObject.define(
15     function(o) {
16         
17         XObject.extend(this, o || {});
18         
19     },
20     Object,
21     {
22         /** @cfg {Boolean} collapseWhite merge multiple whitespace/comments into a single token **/
23         collapseWhite : false, // only reduces white space...
24         /** @cfg {Boolean} keepDocs keep JSDOC comments **/
25         keepDocs : true,
26         /** @cfg {Boolean} keepWhite keep White space **/
27         keepWhite : false,
28         /** @cfg {Boolean} keepComments  keep all comments **/
29         keepComments : false,
30         /** @cfg {Boolean} sepIdents seperate identifiers (eg. a.b.c into ['a', '.', 'b', '.', 'c'] ) **/
31         sepIdents : false,
32         /** @cfg {String} filename name of file being parsed. **/
33         filename : '',
34         
35         /**
36          * tokenize a stream
37          * @return {Array} of tokens
38          * 
39          * ts = new TextStream(File.read(str));
40          * tr = TokenReader({ keepComments : true, keepWhite : true });
41          * tr.tokenize(ts)
42          * 
43          */
44         tokenize : function(/**JSDOC.TextStream*/stream) {
45             this.line =1;
46             var tokens = [];
47             /**@ignore*/ 
48             tokens.last    = function() { return tokens[tokens.length-1]; }
49             /**@ignore*/ 
50             tokens.lastSym = function() {
51                 for (var i = tokens.length-1; i >= 0; i--) {
52                     if (!(tokens[i].is("WHIT") || tokens[i].is("COMM"))) return tokens[i];
53                 }
54             }
55
56             while (!stream.look().eof) {
57                 if (this.read_mlcomment(stream, tokens)) continue;
58                 if (this.read_slcomment(stream, tokens)) continue;
59                 if (this.read_dbquote(stream, tokens))   continue;
60                 if (this.read_snquote(stream, tokens))   continue;
61                 if (this.read_regx(stream, tokens))      continue;
62                 if (this.read_numb(stream, tokens))      continue;
63                 if (this.read_punc(stream, tokens))      continue;
64                 if (this.read_newline(stream, tokens))   continue;
65                 if (this.read_space(stream, tokens))     continue;
66                 if (this.read_word(stream, tokens))      continue;
67                 
68                 // if execution reaches here then an error has happened
69                 tokens.push(new Token(stream.next(), "TOKN", "UNKNOWN_TOKEN", this.line));
70             }
71             
72             
73             
74             return tokens;
75         },
76
77         /**
78          * findPuncToken - find the id of a token (previous to current)
79          * need to back check syntax..
80          * 
81          * @arg {Array} tokens the array of tokens.
82          * @arg {String} token data (eg. '(')
83          * @arg {Number} offset where to start reading from
84          * @return {Number} position of token
85          */
86         findPuncToken : function(tokens, data, n) {
87             n = n || tokens.length -1;
88             var stack = 0;
89             while (n > -1) {
90                 
91                 if (!stack && tokens[n].data == data) {
92                     return n;
93                 }
94                 
95                 if (tokens[n].data  == ')' || tokens[n].data  == '}') {
96                     stack++;
97                     n--;
98                     continue;
99                 }
100                 if (stack && (tokens[n].data  == '{' || tokens[n].data  == '(')) {
101                     stack--;
102                     n--;
103                     continue;
104                 }
105                 
106                 
107                 n--;
108             }
109             return -1;
110         },
111         /**
112          * lastSym - find the last token symbol
113          * need to back check syntax..
114          * 
115          * @arg {Array} tokens the array of tokens.
116          * @arg {Number} offset where to start..
117          * @return {Token} the token
118          */
119         lastSym : function(tokens, n) {
120             for (var i = n-1; i >= 0; i--) {
121                 if (!(tokens[i].is("WHIT") || tokens[i].is("COMM"))) return tokens[i];
122             }
123         },
124         
125          
126         
127         /**
128             @returns {Boolean} Was the token found?
129          */
130         read_word : function(/**JSDOC.TokenStream*/stream, tokens) {
131             var found = "";
132             while (!stream.look().eof && Lang.isWordChar(stream.look())) {
133                 found += stream.next();
134             }
135             
136             if (found === "") {
137                 return false;
138             }
139             
140             var name;
141             if ((name = Lang.keyword(found))) {
142                 if (found == 'return' && tokens.lastSym().data == ')') {
143                     //Seed.print('@' + tokens.length);
144                     var n = this.findPuncToken(tokens, ')');
145                     //Seed.print(')@' + n);
146                     n = this.findPuncToken(tokens, '(', n-1);
147                     //Seed.print('(@' + n);
148                     
149                     var lt = this.lastSym(tokens, n);
150                    Seed.print(JSON.stringify(lt));
151                     if (lt.type != 'KEYW' || ['IF', 'WHILE'].indexOf(lt.name) < -1) {
152                         throw {
153                             name : "ArgumentError", 
154                             message: "\n" + this.filename + ':' + this.line + " Error - return found after )"
155                         }   
156                     }
157                     
158                     
159                     
160                 }
161                 
162                 tokens.push(new Token(found, "KEYW", name, this.line));
163                 return true;
164             }
165             if (!this.sepIdents || found.indexOf('.') < 0 ) {
166                 tokens.push(new Token(found, "NAME", "NAME", this.line));
167                 return true;
168             }
169             var n = found.split('.');
170             var p = false;
171             var _this = this;
172             n.forEach(function(nm) {
173                 if (p) {
174                     tokens.push(new Token('.', "PUNC", "DOT", _this.line));
175                 }
176                 p=true;
177                 tokens.push(new Token(nm, "NAME", "NAME", _this.line));
178             });
179             return true;
180                 
181
182         },
183
184         /**
185             @returns {Boolean} Was the token found?
186          */
187         read_punc : function(/**JSDOC.TokenStream*/stream, tokens) {
188             var found = "";
189             var name;
190             while (!stream.look().eof && Lang.punc(found+stream.look())) {
191                 found += stream.next();
192             }
193             
194             
195             if (found === "") {
196                 return false;
197             }
198             
199             if ((found == '}' || found == ']') && tokens.lastSym().data == ',') {
200                 //print("Error - comma found before " + found);
201                 //print(JSON.stringify(tokens.lastSym(), null,4));
202                 throw {
203                     name : "ArgumentError", 
204                     message: "\n" + this.filename + ':' + this.line + " Error - comma found before " + found
205                 }   
206             }
207             
208             tokens.push(new Token(found, "PUNC", Lang.punc(found), this.line));
209             return true;
210             
211         },
212
213         /**
214             @returns {Boolean} Was the token found?
215          */
216         read_space : function(/**JSDOC.TokenStream*/stream, tokens) {
217             var found = "";
218             
219             while (!stream.look().eof && Lang.isSpace(stream.look()) && !Lang.isNewline(stream.look())) {
220                 found += stream.next();
221             }
222             
223             if (found === "") {
224                 return false;
225             }
226             //print("WHITE = " + JSON.stringify(found)); 
227             if (this.collapseWhite) found = " ";
228             if (this.keepWhite) tokens.push(new Token(found, "WHIT", "SPACE", this.line));
229             return true;
230         
231         },
232
233         /**
234             @returns {Boolean} Was the token found?
235          */
236         read_newline : function(/**JSDOC.TokenStream*/stream, tokens) {
237             var found = "";
238             var line = this.line;
239             while (!stream.look().eof && Lang.isNewline(stream.look())) {
240                 this.line++;
241                 found += stream.next();
242             }
243             
244             if (found === "") {
245                 return false;
246             }
247             //this.line++;
248             if (this.collapseWhite) {
249                 found = "\n";
250             }
251             if (this.keepWhite) {
252                 var last = tokens.pop();
253                 if (last && last.name != "WHIT") {
254                     tokens.push(last);
255                 }
256                 
257                 tokens.push(new Token(found, "WHIT", "NEWLINE", line));
258             }
259             return true;
260         },
261
262         /**
263             @returns {Boolean} Was the token found?
264          */
265         read_mlcomment : function(/**JSDOC.TokenStream*/stream, tokens) {
266             if (stream.look() == "/" && stream.look(1) == "*") {
267                 var found = stream.next(2);
268                 var c = '';
269                 var line = this.line;
270                 while (!stream.look().eof && !(stream.look(-1) == "/" && stream.look(-2) == "*")) {
271                     c = stream.next();
272                     if (c == "\n") this.line++;
273                     found += c;
274                 }
275                 
276                 // to start doclet we allow /** or /*** but not /**/ or /****
277                 if (/^\/\*\*([^\/]|\*[^*])/.test(found) && this.keepDocs) tokens.push(new Token(found, "COMM", "JSDOC", this.line));
278                 else if (this.keepComments) tokens.push(new Token(found, "COMM", "MULTI_LINE_COMM", line));
279                 return true;
280             }
281             return false;
282         },
283
284         /**
285             @returns {Boolean} Was the token found?
286          */
287         read_slcomment : function(/**JSDOC.TokenStream*/stream, tokens) {
288             var found;
289             if (
290                 (stream.look() == "/" && stream.look(1) == "/" && (found=stream.next(2)))
291                 || 
292                 (stream.look() == "<" && stream.look(1) == "!" && stream.look(2) == "-" && stream.look(3) == "-" && (found=stream.next(4)))
293             ) {
294                 var line = this.line;
295                 while (!stream.look().eof && !Lang.isNewline(stream.look())) {
296                     found += stream.next();
297                 }
298                 if (!stream.look().eof) {
299                     found += stream.next();
300                 }
301                 if (this.keepComments) {
302                     tokens.push(new Token(found, "COMM", "SINGLE_LINE_COMM", line));
303                 }
304                 this.line++;
305                 return true;
306             }
307             return false;
308         },
309
310         /**
311             @returns {Boolean} Was the token found?
312          */
313         read_dbquote : function(/**JSDOC.TokenStream*/stream, tokens) {
314             if (stream.look() == "\"") {
315                 // find terminator
316                 var string = stream.next();
317                 
318                 while (!stream.look().eof) {
319                     if (stream.look() == "\\") {
320                         if (Lang.isNewline(stream.look(1))) {
321                             do {
322                                 stream.next();
323                             } while (!stream.look().eof && Lang.isNewline(stream.look()));
324                             string += "\\\n";
325                         }
326                         else {
327                             string += stream.next(2);
328                         }
329                     }
330                     else if (stream.look() == "\"") {
331                         string += stream.next();
332                         tokens.push(new Token(string, "STRN", "DOUBLE_QUOTE", this.line));
333                         return true;
334                     }
335                     else {
336                         string += stream.next();
337                     }
338                 }
339             }
340             return false; // error! unterminated string
341         },
342
343         /**
344             @returns {Boolean} Was the token found?
345          */
346         read_snquote : function(/**JSDOC.TokenStream*/stream, tokens) {
347             if (stream.look() == "'") {
348                 // find terminator
349                 var string = stream.next();
350                 
351                 while (!stream.look().eof) {
352                     if (stream.look() == "\\") { // escape sequence
353                         string += stream.next(2);
354                     }
355                     else if (stream.look() == "'") {
356                         string += stream.next();
357                         tokens.push(new Token(string, "STRN", "SINGLE_QUOTE", this.line));
358                         return true;
359                     }
360                     else {
361                         string += stream.next();
362                     }
363                 }
364             }
365             return false; // error! unterminated string
366         },
367
368         /**
369             @returns {Boolean} Was the token found?
370          */
371         read_numb : function(/**JSDOC.TokenStream*/stream, tokens) {
372             if (stream.look() === "0" && stream.look(1) == "x") {
373                 return this.read_hex(stream, tokens);
374             }
375             
376             var found = "";
377             
378             while (!stream.look().eof && Lang.isNumber(found+stream.look())){
379                 found += stream.next();
380             }
381             
382             if (found === "") {
383                 return false;
384             }
385             else {
386                 if (/^0[0-7]/.test(found)) tokens.push(new Token(found, "NUMB", "OCTAL", this.line));
387                 else tokens.push(new Token(found, "NUMB", "DECIMAL", this.line));
388                 return true;
389             }
390         },
391         /*t:
392             requires("../lib/JSDOC/TextStream.js");
393             requires("../lib/JSDOC/Token.js");
394             requires("../lib/JSDOC/Lang.js");
395             
396             plan(3, "testing read_numb");
397             
398             //// setup
399             var src = "function foo(num){while (num+8.0 >= 0x20 && num < 0777){}}";
400             var tr = new TokenReader();
401             var tokens = tr.tokenize(new TextStream(src));
402             
403             var hexToken, octToken, decToken;
404             for (var i = 0; i < tokens.length; i++) {
405                 if (tokens[i].name == "HEX_DEC") hexToken = tokens[i];
406                 if (tokens[i].name == "OCTAL") octToken = tokens[i];
407                 if (tokens[i].name == "DECIMAL") decToken = tokens[i];
408             }
409             ////
410             
411             is(decToken.data, "8.0", "decimal number is found in source.");
412             is(hexToken.data, "0x20", "hexdec number is found in source (issue #99).");
413             is(octToken.data, "0777", "octal number is found in source.");
414         */
415
416         /**
417             @returns {Boolean} Was the token found?
418          */
419         read_hex : function(/**JSDOC.TokenStream*/stream, tokens) {
420             var found = stream.next(2);
421             
422             while (!stream.look().eof) {
423                 if (Lang.isHexDec(found) && !Lang.isHexDec(found+stream.look())) { // done
424                     tokens.push(new Token(found, "NUMB", "HEX_DEC", this.line));
425                     return true;
426                 }
427                 else {
428                     found += stream.next();
429                 }
430             }
431             return false;
432         },
433
434         /**
435             @returns {Boolean} Was the token found?
436          */
437         read_regx : function(/**JSDOC.TokenStream*/stream, tokens) {
438             var last;
439             if (
440                 stream.look() == "/"
441                 && 
442                 (
443                     
444                     (
445                         !(last = tokens.lastSym()) // there is no last, the regex is the first symbol
446                         || 
447                         (
448                                !last.is("NUMB")
449                             && !last.is("NAME")
450                             && !last.is("RIGHT_PAREN")
451                             && !last.is("RIGHT_BRACKET")
452                         )
453                     )
454                 )
455             ) {
456                 var regex = stream.next();
457                 
458                 while (!stream.look().eof) {
459                     if (stream.look() == "\\") { // escape sequence
460                         regex += stream.next(2);
461                     }
462                     else if (stream.look() == "/") {
463                         regex += stream.next();
464                         
465                         while (/[gmi]/.test(stream.look())) {
466                             regex += stream.next();
467                         }
468                         
469                         tokens.push(new Token(regex, "REGX", "REGX", this.line));
470                         return true;
471                     }
472                     else {
473                         regex += stream.next();
474                     }
475                 }
476                 // error: unterminated regex
477             }
478             return false;
479         }
480 });