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                 if (this.ignoreBadGrammer) {
207                     print("\n" + this.filename + ':' + this.line + " Error - comma found before " + found);
208                 } else {
209                     
210                     throw {
211                         name : "ArgumentError", 
212                         message: "\n" + this.filename + ':' + this.line + " Error - comma found before " + found
213                     }
214                 }
215             }
216             
217             tokens.push(new Token(found, "PUNC", Lang.punc(found), this.line));
218             return true;
219             
220         },
221
222         /**
223             @returns {Boolean} Was the token found?
224          */
225         read_space : function(/**JSDOC.TokenStream*/stream, tokens) {
226             var found = "";
227             
228             while (!stream.look().eof && Lang.isSpace(stream.look()) && !Lang.isNewline(stream.look())) {
229                 found += stream.next();
230             }
231             
232             if (found === "") {
233                 return false;
234             }
235             //print("WHITE = " + JSON.stringify(found)); 
236             if (this.collapseWhite) found = " ";
237             if (this.keepWhite) tokens.push(new Token(found, "WHIT", "SPACE", this.line));
238             return true;
239         
240         },
241
242         /**
243             @returns {Boolean} Was the token found?
244          */
245         read_newline : function(/**JSDOC.TokenStream*/stream, tokens) {
246             var found = "";
247             var line = this.line;
248             while (!stream.look().eof && Lang.isNewline(stream.look())) {
249                 this.line++;
250                 found += stream.next();
251             }
252             
253             if (found === "") {
254                 return false;
255             }
256             //this.line++;
257             if (this.collapseWhite) {
258                 found = "\n";
259             }
260             if (this.keepWhite) {
261                 var last = tokens.pop();
262                 if (last && last.name != "WHIT") {
263                     tokens.push(last);
264                 }
265                 
266                 tokens.push(new Token(found, "WHIT", "NEWLINE", line));
267             }
268             return true;
269         },
270
271         /**
272             @returns {Boolean} Was the token found?
273          */
274         read_mlcomment : function(/**JSDOC.TokenStream*/stream, tokens) {
275             if (stream.look() == "/" && stream.look(1) == "*") {
276                 var found = stream.next(2);
277                 var c = '';
278                 var line = this.line;
279                 while (!stream.look().eof && !(stream.look(-1) == "/" && stream.look(-2) == "*")) {
280                     c = stream.next();
281                     if (c == "\n") this.line++;
282                     found += c;
283                 }
284                 
285                 // to start doclet we allow /** or /*** but not /**/ or /****
286                 if (/^\/\*\*([^\/]|\*[^*])/.test(found) && this.keepDocs) tokens.push(new Token(found, "COMM", "JSDOC", this.line));
287                 else if (this.keepComments) tokens.push(new Token(found, "COMM", "MULTI_LINE_COMM", line));
288                 return true;
289             }
290             return false;
291         },
292
293         /**
294             @returns {Boolean} Was the token found?
295          */
296         read_slcomment : function(/**JSDOC.TokenStream*/stream, tokens) {
297             var found;
298             if (
299                 (stream.look() == "/" && stream.look(1) == "/" && (found=stream.next(2)))
300                 || 
301                 (stream.look() == "<" && stream.look(1) == "!" && stream.look(2) == "-" && stream.look(3) == "-" && (found=stream.next(4)))
302             ) {
303                 var line = this.line;
304                 while (!stream.look().eof && !Lang.isNewline(stream.look())) {
305                     found += stream.next();
306                 }
307                 if (!stream.look().eof) {
308                     found += stream.next();
309                 }
310                 if (this.keepComments) {
311                     tokens.push(new Token(found, "COMM", "SINGLE_LINE_COMM", line));
312                 }
313                 this.line++;
314                 return true;
315             }
316             return false;
317         },
318
319         /**
320             @returns {Boolean} Was the token found?
321          */
322         read_dbquote : function(/**JSDOC.TokenStream*/stream, tokens) {
323             if (stream.look() == "\"") {
324                 // find terminator
325                 var string = stream.next();
326                 
327                 while (!stream.look().eof) {
328                     if (stream.look() == "\\") {
329                         if (Lang.isNewline(stream.look(1))) {
330                             do {
331                                 stream.next();
332                             } while (!stream.look().eof && Lang.isNewline(stream.look()));
333                             string += "\\\n";
334                         }
335                         else {
336                             string += stream.next(2);
337                         }
338                     }
339                     else if (stream.look() == "\"") {
340                         string += stream.next();
341                         tokens.push(new Token(string, "STRN", "DOUBLE_QUOTE", this.line));
342                         return true;
343                     }
344                     else {
345                         string += stream.next();
346                     }
347                 }
348             }
349             return false; // error! unterminated string
350         },
351
352         /**
353             @returns {Boolean} Was the token found?
354          */
355         read_snquote : function(/**JSDOC.TokenStream*/stream, tokens) {
356             if (stream.look() == "'") {
357                 // find terminator
358                 var string = stream.next();
359                 
360                 while (!stream.look().eof) {
361                     if (stream.look() == "\\") { // escape sequence
362                         string += stream.next(2);
363                     }
364                     else if (stream.look() == "'") {
365                         string += stream.next();
366                         tokens.push(new Token(string, "STRN", "SINGLE_QUOTE", this.line));
367                         return true;
368                     }
369                     else {
370                         string += stream.next();
371                     }
372                 }
373             }
374             return false; // error! unterminated string
375         },
376
377         /**
378             @returns {Boolean} Was the token found?
379          */
380         read_numb : function(/**JSDOC.TokenStream*/stream, tokens) {
381             if (stream.look() === "0" && stream.look(1) == "x") {
382                 return this.read_hex(stream, tokens);
383             }
384             
385             var found = "";
386             
387             while (!stream.look().eof && Lang.isNumber(found+stream.look())){
388                 found += stream.next();
389             }
390             
391             if (found === "") {
392                 return false;
393             }
394             else {
395                 if (/^0[0-7]/.test(found)) tokens.push(new Token(found, "NUMB", "OCTAL", this.line));
396                 else tokens.push(new Token(found, "NUMB", "DECIMAL", this.line));
397                 return true;
398             }
399         },
400         /*t:
401             requires("../lib/JSDOC/TextStream.js");
402             requires("../lib/JSDOC/Token.js");
403             requires("../lib/JSDOC/Lang.js");
404             
405             plan(3, "testing read_numb");
406             
407             //// setup
408             var src = "function foo(num){while (num+8.0 >= 0x20 && num < 0777){}}";
409             var tr = new TokenReader();
410             var tokens = tr.tokenize(new TextStream(src));
411             
412             var hexToken, octToken, decToken;
413             for (var i = 0; i < tokens.length; i++) {
414                 if (tokens[i].name == "HEX_DEC") hexToken = tokens[i];
415                 if (tokens[i].name == "OCTAL") octToken = tokens[i];
416                 if (tokens[i].name == "DECIMAL") decToken = tokens[i];
417             }
418             ////
419             
420             is(decToken.data, "8.0", "decimal number is found in source.");
421             is(hexToken.data, "0x20", "hexdec number is found in source (issue #99).");
422             is(octToken.data, "0777", "octal number is found in source.");
423         */
424
425         /**
426             @returns {Boolean} Was the token found?
427          */
428         read_hex : function(/**JSDOC.TokenStream*/stream, tokens) {
429             var found = stream.next(2);
430             
431             while (!stream.look().eof) {
432                 if (Lang.isHexDec(found) && !Lang.isHexDec(found+stream.look())) { // done
433                     tokens.push(new Token(found, "NUMB", "HEX_DEC", this.line));
434                     return true;
435                 }
436                 else {
437                     found += stream.next();
438                 }
439             }
440             return false;
441         },
442
443         /**
444             @returns {Boolean} Was the token found?
445          */
446         read_regx : function(/**JSDOC.TokenStream*/stream, tokens) {
447             var last;
448             if (
449                 stream.look() == "/"
450                 && 
451                 (
452                     
453                     (
454                         !(last = tokens.lastSym()) // there is no last, the regex is the first symbol
455                         || 
456                         (
457                                !last.is("NUMB")
458                             && !last.is("NAME")
459                             && !last.is("RIGHT_PAREN")
460                             && !last.is("RIGHT_BRACKET")
461                         )
462                     )
463                 )
464             ) {
465                 var regex = stream.next();
466                 
467                 while (!stream.look().eof) {
468                     if (stream.look() == "\\") { // escape sequence
469                         regex += stream.next(2);
470                     }
471                     else if (stream.look() == "/") {
472                         regex += stream.next();
473                         
474                         while (/[gmi]/.test(stream.look())) {
475                             regex += stream.next();
476                         }
477                         
478                         tokens.push(new Token(regex, "REGX", "REGX", this.line));
479                         return true;
480                     }
481                     else {
482                         regex += stream.next();
483                     }
484                 }
485                 // error: unterminated regex
486             }
487             return false;
488         }
489 });