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