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