parseReturnType should start from the first non-brace token
[arduino-ctags.git] / jscript.c
blobc4e5b1aec09c9876d17c10db321729b515aed482
1 /*
2 * $Id: jscript.c 666 2008-05-15 17:47:31Z dfishburn $
4 * Copyright (c) 2003, Darren Hiebert
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
9 * This module contains functions for generating tags for JavaScript language
10 * files.
12 * This is a good reference for different forms of the function statement:
13 * http://www.permadi.com/tutorial/jsFunc/
14 * Another good reference:
15 * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide
19 * INCLUDE FILES
21 #include "general.h" /* must always come first */
22 #include <ctype.h> /* to define isalpha () */
23 #include <setjmp.h>
24 #ifdef DEBUG
25 #include <stdio.h>
26 #endif
28 #include "debug.h"
29 #include "entry.h"
30 #include "keyword.h"
31 #include "parse.h"
32 #include "read.h"
33 #include "routines.h"
34 #include "vstring.h"
37 * MACROS
39 #define isType(token,t) (boolean) ((token)->type == (t))
40 #define isKeyword(token,k) (boolean) ((token)->keyword == (k))
43 * DATA DECLARATIONS
46 typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
49 * Tracks class and function names already created
51 static stringList *ClassNames;
52 static stringList *FunctionNames;
54 /* Used to specify type of keyword.
56 typedef enum eKeywordId {
57 KEYWORD_NONE = -1,
58 KEYWORD_function,
59 KEYWORD_capital_function,
60 KEYWORD_object,
61 KEYWORD_capital_object,
62 KEYWORD_prototype,
63 KEYWORD_var,
64 KEYWORD_new,
65 KEYWORD_this,
66 KEYWORD_for,
67 KEYWORD_while,
68 KEYWORD_do,
69 KEYWORD_if,
70 KEYWORD_else,
71 KEYWORD_switch,
72 KEYWORD_try,
73 KEYWORD_catch,
74 KEYWORD_finally
75 } keywordId;
77 /* Used to determine whether keyword is valid for the token language and
78 * what its ID is.
80 typedef struct sKeywordDesc {
81 const char *name;
82 keywordId id;
83 } keywordDesc;
85 typedef enum eTokenType {
86 TOKEN_UNDEFINED,
87 TOKEN_CHARACTER,
88 TOKEN_CLOSE_PAREN,
89 TOKEN_SEMICOLON,
90 TOKEN_COLON,
91 TOKEN_COMMA,
92 TOKEN_KEYWORD,
93 TOKEN_OPEN_PAREN,
94 TOKEN_OPERATOR,
95 TOKEN_IDENTIFIER,
96 TOKEN_STRING,
97 TOKEN_PERIOD,
98 TOKEN_OPEN_CURLY,
99 TOKEN_CLOSE_CURLY,
100 TOKEN_EQUAL_SIGN,
101 TOKEN_FORWARD_SLASH,
102 TOKEN_OPEN_SQUARE,
103 TOKEN_CLOSE_SQUARE
104 } tokenType;
106 typedef struct sTokenInfo {
107 tokenType type;
108 keywordId keyword;
109 vString * string;
110 vString * scope;
111 unsigned long lineNumber;
112 fpos_t filePosition;
113 int nestLevel;
114 boolean ignoreTag;
115 } tokenInfo;
118 * DATA DEFINITIONS
121 static langType Lang_js;
123 static jmp_buf Exception;
125 typedef enum {
126 JSTAG_FUNCTION,
127 JSTAG_CLASS,
128 JSTAG_METHOD,
129 JSTAG_PROPERTY,
130 JSTAG_VARIABLE,
131 JSTAG_COUNT
132 } jsKind;
134 static kindOption JsKinds [] = {
135 { TRUE, 'f', "function", "functions" },
136 { TRUE, 'c', "class", "classes" },
137 { TRUE, 'm', "method", "methods" },
138 { TRUE, 'p', "property", "properties" },
139 { TRUE, 'v', "variable", "global variables" }
142 static const keywordDesc JsKeywordTable [] = {
143 /* keyword keyword ID */
144 { "function", KEYWORD_function },
145 { "Function", KEYWORD_capital_function },
146 { "object", KEYWORD_object },
147 { "Object", KEYWORD_capital_object },
148 { "prototype", KEYWORD_prototype },
149 { "var", KEYWORD_var },
150 { "new", KEYWORD_new },
151 { "this", KEYWORD_this },
152 { "for", KEYWORD_for },
153 { "while", KEYWORD_while },
154 { "do", KEYWORD_do },
155 { "if", KEYWORD_if },
156 { "else", KEYWORD_else },
157 { "switch", KEYWORD_switch },
158 { "try", KEYWORD_try },
159 { "catch", KEYWORD_catch },
160 { "finally", KEYWORD_finally }
164 * FUNCTION DEFINITIONS
167 /* Recursive functions */
168 static void parseFunction (tokenInfo *const token);
169 static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent);
170 static boolean parseLine (tokenInfo *const token, boolean is_inside_class);
172 static boolean isIdentChar (const int c)
174 return (boolean)
175 (isalpha (c) || isdigit (c) || c == '$' ||
176 c == '@' || c == '_' || c == '#');
179 static void buildJsKeywordHash (void)
181 const size_t count = sizeof (JsKeywordTable) /
182 sizeof (JsKeywordTable [0]);
183 size_t i;
184 for (i = 0 ; i < count ; ++i)
186 const keywordDesc* const p = &JsKeywordTable [i];
187 addKeyword (p->name, Lang_js, (int) p->id);
191 static tokenInfo *newToken (void)
193 tokenInfo *const token = xMalloc (1, tokenInfo);
195 token->type = TOKEN_UNDEFINED;
196 token->keyword = KEYWORD_NONE;
197 token->string = vStringNew ();
198 token->scope = vStringNew ();
199 token->nestLevel = 0;
200 token->ignoreTag = FALSE;
201 token->lineNumber = getSourceLineNumber ();
202 token->filePosition = getInputFilePosition ();
204 return token;
207 static void deleteToken (tokenInfo *const token)
209 vStringDelete (token->string);
210 vStringDelete (token->scope);
211 eFree (token);
215 * Tag generation functions
218 static void makeConstTag (tokenInfo *const token, const jsKind kind)
220 if (JsKinds [kind].enabled && ! token->ignoreTag )
222 const char *const name = vStringValue (token->string);
223 tagEntryInfo e;
224 initTagEntry (&e, name);
226 e.lineNumber = token->lineNumber;
227 e.filePosition = token->filePosition;
228 e.kindName = JsKinds [kind].name;
229 e.kind = JsKinds [kind].letter;
231 makeTagEntry (&e);
235 static void makeJsTag (tokenInfo *const token, const jsKind kind)
237 vString * fulltag;
239 if (JsKinds [kind].enabled && ! token->ignoreTag )
242 * If a scope has been added to the token, change the token
243 * string to include the scope when making the tag.
245 if ( vStringLength(token->scope) > 0 )
247 fulltag = vStringNew ();
248 vStringCopy(fulltag, token->scope);
249 vStringCatS (fulltag, ".");
250 vStringCatS (fulltag, vStringValue(token->string));
251 vStringTerminate(fulltag);
252 vStringCopy(token->string, fulltag);
253 vStringDelete (fulltag);
255 makeConstTag (token, kind);
259 static void makeClassTag (tokenInfo *const token)
261 vString * fulltag;
263 if ( ! token->ignoreTag )
265 fulltag = vStringNew ();
266 if (vStringLength (token->scope) > 0)
268 vStringCopy(fulltag, token->scope);
269 vStringCatS (fulltag, ".");
270 vStringCatS (fulltag, vStringValue(token->string));
272 else
274 vStringCopy(fulltag, token->string);
276 vStringTerminate(fulltag);
277 if ( ! stringListHas(ClassNames, vStringValue (fulltag)) )
279 stringListAdd (ClassNames, vStringNewCopy (fulltag));
280 makeJsTag (token, JSTAG_CLASS);
282 vStringDelete (fulltag);
286 static void makeFunctionTag (tokenInfo *const token)
288 vString * fulltag;
290 if ( ! token->ignoreTag )
292 fulltag = vStringNew ();
293 if (vStringLength (token->scope) > 0)
295 vStringCopy(fulltag, token->scope);
296 vStringCatS (fulltag, ".");
297 vStringCatS (fulltag, vStringValue(token->string));
299 else
301 vStringCopy(fulltag, token->string);
303 vStringTerminate(fulltag);
304 if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) )
306 stringListAdd (FunctionNames, vStringNewCopy (fulltag));
307 makeJsTag (token, JSTAG_FUNCTION);
309 vStringDelete (fulltag);
314 * Parsing functions
317 static void parseString (vString *const string, const int delimiter)
319 boolean end = FALSE;
320 while (! end)
322 int c = fileGetc ();
323 if (c == EOF)
324 end = TRUE;
325 else if (c == '\\')
327 c = fileGetc(); /* This maybe a ' or ". */
328 vStringPut(string, c);
330 else if (c == delimiter)
331 end = TRUE;
332 else
333 vStringPut (string, c);
335 vStringTerminate (string);
338 /* Read a C identifier beginning with "firstChar" and places it into
339 * "name".
341 static void parseIdentifier (vString *const string, const int firstChar)
343 int c = firstChar;
344 Assert (isIdentChar (c));
347 vStringPut (string, c);
348 c = fileGetc ();
349 } while (isIdentChar (c));
350 vStringTerminate (string);
351 if (!isspace (c))
352 fileUngetc (c); /* unget non-identifier character */
355 static void readToken (tokenInfo *const token)
357 int c;
359 token->type = TOKEN_UNDEFINED;
360 token->keyword = KEYWORD_NONE;
361 vStringClear (token->string);
363 getNextChar:
366 c = fileGetc ();
367 token->lineNumber = getSourceLineNumber ();
368 token->filePosition = getInputFilePosition ();
370 while (c == '\t' || c == ' ' || c == '\n');
372 switch (c)
374 case EOF: longjmp (Exception, (int)ExceptionEOF); break;
375 case '(': token->type = TOKEN_OPEN_PAREN; break;
376 case ')': token->type = TOKEN_CLOSE_PAREN; break;
377 case ';': token->type = TOKEN_SEMICOLON; break;
378 case ',': token->type = TOKEN_COMMA; break;
379 case '.': token->type = TOKEN_PERIOD; break;
380 case ':': token->type = TOKEN_COLON; break;
381 case '{': token->type = TOKEN_OPEN_CURLY; break;
382 case '}': token->type = TOKEN_CLOSE_CURLY; break;
383 case '=': token->type = TOKEN_EQUAL_SIGN; break;
384 case '[': token->type = TOKEN_OPEN_SQUARE; break;
385 case ']': token->type = TOKEN_CLOSE_SQUARE; break;
387 case '\'':
388 case '"':
389 token->type = TOKEN_STRING;
390 parseString (token->string, c);
391 token->lineNumber = getSourceLineNumber ();
392 token->filePosition = getInputFilePosition ();
393 break;
395 case '\\':
396 c = fileGetc ();
397 if (c != '\\' && c != '"' && !isspace (c))
398 fileUngetc (c);
399 token->type = TOKEN_CHARACTER;
400 token->lineNumber = getSourceLineNumber ();
401 token->filePosition = getInputFilePosition ();
402 break;
404 case '/':
406 int d = fileGetc ();
407 if ( (d != '*') && /* is this the start of a comment? */
408 (d != '/') ) /* is a one line comment? */
410 token->type = TOKEN_FORWARD_SLASH;
411 fileUngetc (d);
413 else
415 if (d == '*')
419 fileSkipToCharacter ('*');
420 c = fileGetc ();
421 if (c == '/')
422 break;
423 else
424 fileUngetc (c);
425 } while (c != EOF && c != '\0');
426 goto getNextChar;
428 else if (d == '/') /* is this the start of a comment? */
430 fileSkipToCharacter ('\n');
431 goto getNextChar;
434 break;
437 default:
438 if (! isIdentChar (c))
439 token->type = TOKEN_UNDEFINED;
440 else
442 parseIdentifier (token->string, c);
443 token->lineNumber = getSourceLineNumber ();
444 token->filePosition = getInputFilePosition ();
445 token->keyword = analyzeToken (token->string, Lang_js);
446 if (isKeyword (token, KEYWORD_NONE))
447 token->type = TOKEN_IDENTIFIER;
448 else
449 token->type = TOKEN_KEYWORD;
451 break;
455 static void copyToken (tokenInfo *const dest, tokenInfo *const src)
457 dest->nestLevel = src->nestLevel;
458 dest->lineNumber = src->lineNumber;
459 dest->filePosition = src->filePosition;
460 dest->type = src->type;
461 dest->keyword = src->keyword;
462 vStringCopy(dest->string, src->string);
463 vStringCopy(dest->scope, src->scope);
467 * Token parsing functions
470 static void skipArgumentList (tokenInfo *const token)
472 int nest_level = 0;
475 * Other databases can have arguments with fully declared
476 * datatypes:
477 * ( name varchar(30), text binary(10) )
478 * So we must check for nested open and closing parantheses
481 if (isType (token, TOKEN_OPEN_PAREN)) /* arguments? */
483 nest_level++;
484 while (! (isType (token, TOKEN_CLOSE_PAREN) && (nest_level == 0)))
486 readToken (token);
487 if (isType (token, TOKEN_OPEN_PAREN))
489 nest_level++;
491 if (isType (token, TOKEN_CLOSE_PAREN))
493 if (nest_level > 0)
495 nest_level--;
499 readToken (token);
503 static void skipArrayList (tokenInfo *const token)
505 int nest_level = 0;
508 * Handle square brackets
509 * var name[1]
510 * So we must check for nested open and closing square brackets
513 if (isType (token, TOKEN_OPEN_SQUARE)) /* arguments? */
515 nest_level++;
516 while (! (isType (token, TOKEN_CLOSE_SQUARE) && (nest_level == 0)))
518 readToken (token);
519 if (isType (token, TOKEN_OPEN_SQUARE))
521 nest_level++;
523 if (isType (token, TOKEN_CLOSE_SQUARE))
525 if (nest_level > 0)
527 nest_level--;
531 readToken (token);
535 static void addContext (tokenInfo* const parent, const tokenInfo* const child)
537 if (vStringLength (parent->string) > 0)
539 vStringCatS (parent->string, ".");
541 vStringCatS (parent->string, vStringValue(child->string));
542 vStringTerminate(parent->string);
545 static void addToScope (tokenInfo* const token, vString* const extra)
547 if (vStringLength (token->scope) > 0)
549 vStringCatS (token->scope, ".");
551 vStringCatS (token->scope, vStringValue(extra));
552 vStringTerminate(token->scope);
556 * Scanning functions
559 static void findCmdTerm (tokenInfo *const token)
562 * Read until we find either a semicolon or closing brace.
563 * Any nested braces will be handled within.
565 while (! ( isType (token, TOKEN_SEMICOLON) ||
566 isType (token, TOKEN_CLOSE_CURLY) ) )
568 /* Handle nested blocks */
569 if ( isType (token, TOKEN_OPEN_CURLY))
571 parseBlock (token, token);
573 else if ( isType (token, TOKEN_OPEN_PAREN) )
575 skipArgumentList(token);
577 else
579 readToken (token);
584 static void parseSwitch (tokenInfo *const token)
587 * switch (expression){
588 * case value1:
589 * statement;
590 * break;
591 * case value2:
592 * statement;
593 * break;
594 * default : statement;
598 readToken (token);
600 if (isType (token, TOKEN_OPEN_PAREN))
603 * Handle nameless functions, these will only
604 * be considered methods.
606 skipArgumentList(token);
609 if (isType (token, TOKEN_OPEN_CURLY))
612 * This will be either a function or a class.
613 * We can only determine this by checking the body
614 * of the function. If we find a "this." we know
615 * it is a class, otherwise it is a function.
617 parseBlock (token, token);
622 static void parseLoop (tokenInfo *const token)
625 * Handles these statements
626 * for (x=0; x<3; x++)
627 * document.write("This text is repeated three times<br>");
629 * for (x=0; x<3; x++)
631 * document.write("This text is repeated three times<br>");
634 * while (number<5){
635 * document.write(number+"<br>");
636 * number++;
639 * do{
640 * document.write(number+"<br>");
641 * number++;
643 * while (number<5);
646 if (isKeyword (token, KEYWORD_for) || isKeyword (token, KEYWORD_while))
648 readToken(token);
650 if (isType (token, TOKEN_OPEN_PAREN))
653 * Handle nameless functions, these will only
654 * be considered methods.
656 skipArgumentList(token);
659 if (isType (token, TOKEN_OPEN_CURLY))
662 * This will be either a function or a class.
663 * We can only determine this by checking the body
664 * of the function. If we find a "this." we know
665 * it is a class, otherwise it is a function.
667 parseBlock (token, token);
669 else
671 parseLine(token, FALSE);
674 else if (isKeyword (token, KEYWORD_do))
676 readToken(token);
678 if (isType (token, TOKEN_OPEN_CURLY))
681 * This will be either a function or a class.
682 * We can only determine this by checking the body
683 * of the function. If we find a "this." we know
684 * it is a class, otherwise it is a function.
686 parseBlock (token, token);
688 else
690 parseLine(token, FALSE);
693 readToken(token);
695 if (isKeyword (token, KEYWORD_while))
697 readToken(token);
699 if (isType (token, TOKEN_OPEN_PAREN))
702 * Handle nameless functions, these will only
703 * be considered methods.
705 skipArgumentList(token);
711 static boolean parseIf (tokenInfo *const token)
713 boolean read_next_token = TRUE;
715 * If statements have two forms
716 * if ( ... )
717 * one line;
719 * if ( ... )
720 * statement;
721 * else
722 * statement
724 * if ( ... ) {
725 * multiple;
726 * statements;
730 * if ( ... ) {
731 * return elem
734 * This example if correctly written, but the
735 * else contains only 1 statement without a terminator
736 * since the function finishes with the closing brace.
738 * function a(flag){
739 * if(flag)
740 * test(1);
741 * else
742 * test(2)
745 * TODO: Deal with statements that can optional end
746 * without a semi-colon. Currently this messes up
747 * the parsing of blocks.
748 * Need to somehow detect this has happened, and either
749 * backup a token, or skip reading the next token if
750 * that is possible from all code locations.
754 readToken (token);
756 if (isKeyword (token, KEYWORD_if))
759 * Check for an "else if" and consume the "if"
761 readToken (token);
764 if (isType (token, TOKEN_OPEN_PAREN))
767 * Handle nameless functions, these will only
768 * be considered methods.
770 skipArgumentList(token);
773 if (isType (token, TOKEN_OPEN_CURLY))
776 * This will be either a function or a class.
777 * We can only determine this by checking the body
778 * of the function. If we find a "this." we know
779 * it is a class, otherwise it is a function.
781 parseBlock (token, token);
783 else
785 findCmdTerm (token);
788 * The IF could be followed by an ELSE statement.
789 * This too could have two formats, a curly braced
790 * multiline section, or another single line.
793 if (isType (token, TOKEN_CLOSE_CURLY))
796 * This statement did not have a line terminator.
798 read_next_token = FALSE;
800 else
802 readToken (token);
804 if (isType (token, TOKEN_CLOSE_CURLY))
807 * This statement did not have a line terminator.
809 read_next_token = FALSE;
811 else
813 if (isKeyword (token, KEYWORD_else))
814 read_next_token = parseIf (token);
818 return read_next_token;
821 static void parseFunction (tokenInfo *const token)
823 tokenInfo *const name = newToken ();
824 boolean is_class = FALSE;
827 * This deals with these formats
828 * function validFunctionTwo(a,b) {}
831 readToken (name);
832 /* Add scope in case this is an INNER function */
833 addToScope(name, token->scope);
835 readToken (token);
836 if (isType (token, TOKEN_PERIOD))
840 readToken (token);
841 if ( isKeyword(token, KEYWORD_NONE) )
843 addContext (name, token);
844 readToken (token);
846 } while (isType (token, TOKEN_PERIOD));
849 if ( isType (token, TOKEN_OPEN_PAREN) )
850 skipArgumentList(token);
852 if ( isType (token, TOKEN_OPEN_CURLY) )
854 is_class = parseBlock (token, name);
855 if ( is_class )
856 makeClassTag (name);
857 else
858 makeFunctionTag (name);
861 findCmdTerm (token);
863 deleteToken (name);
866 static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent)
868 boolean is_class = FALSE;
869 boolean read_next_token = TRUE;
870 vString * saveScope = vStringNew ();
872 token->nestLevel++;
874 * Make this routine a bit more forgiving.
875 * If called on an open_curly advance it
877 if ( isType (token, TOKEN_OPEN_CURLY) &&
878 isKeyword(token, KEYWORD_NONE) )
879 readToken(token);
881 if (! isType (token, TOKEN_CLOSE_CURLY))
884 * Read until we find the closing brace,
885 * any nested braces will be handled within
889 read_next_token = TRUE;
890 if (isKeyword (token, KEYWORD_this))
893 * Means we are inside a class and have found
894 * a class, not a function
896 is_class = TRUE;
897 vStringCopy(saveScope, token->scope);
898 addToScope (token, parent->string);
901 * Ignore the remainder of the line
902 * findCmdTerm(token);
904 parseLine (token, is_class);
906 vStringCopy(token->scope, saveScope);
908 else if (isKeyword (token, KEYWORD_var))
911 * Potentially we have found an inner function.
912 * Set something to indicate the scope
914 vStringCopy(saveScope, token->scope);
915 addToScope (token, parent->string);
916 parseLine (token, is_class);
917 vStringCopy(token->scope, saveScope);
919 else if (isKeyword (token, KEYWORD_function))
921 vStringCopy(saveScope, token->scope);
922 addToScope (token, parent->string);
923 parseFunction (token);
924 vStringCopy(token->scope, saveScope);
926 else if (isType (token, TOKEN_OPEN_CURLY))
928 /* Handle nested blocks */
929 parseBlock (token, parent);
931 else
934 * It is possible for a line to have no terminator
935 * if the following line is a closing brace.
936 * parseLine will detect this case and indicate
937 * whether we should read an additional token.
939 read_next_token = parseLine (token, is_class);
943 * Always read a new token unless we find a statement without
944 * a ending terminator
946 if( read_next_token )
947 readToken(token);
950 * If we find a statement without a terminator consider the
951 * block finished, otherwise the stack will be off by one.
953 } while (! isType (token, TOKEN_CLOSE_CURLY) && read_next_token );
956 vStringDelete(saveScope);
957 token->nestLevel--;
959 return is_class;
962 static void parseMethods (tokenInfo *const token, tokenInfo *const class)
964 tokenInfo *const name = newToken ();
967 * This deals with these formats
968 * validProperty : 2,
969 * validMethod : function(a,b) {}
970 * 'validMethod2' : function(a,b) {}
971 * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false}
976 readToken (token);
977 if (isType (token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE))
979 copyToken(name, token);
981 readToken (token);
982 if ( isType (token, TOKEN_COLON) )
984 readToken (token);
985 if ( isKeyword (token, KEYWORD_function) )
987 readToken (token);
988 if ( isType (token, TOKEN_OPEN_PAREN) )
990 skipArgumentList(token);
993 if (isType (token, TOKEN_OPEN_CURLY))
995 addToScope (name, class->string);
996 makeJsTag (name, JSTAG_METHOD);
997 parseBlock (token, name);
1000 * Read to the closing curly, check next
1001 * token, if a comma, we must loop again
1003 readToken (token);
1006 else
1008 addToScope (name, class->string);
1009 makeJsTag (name, JSTAG_PROPERTY);
1012 * Read the next token, if a comma
1013 * we must loop again
1015 readToken (token);
1019 } while ( isType(token, TOKEN_COMMA) );
1021 findCmdTerm (token);
1023 deleteToken (name);
1026 static boolean parseStatement (tokenInfo *const token, boolean is_inside_class)
1028 tokenInfo *const name = newToken ();
1029 tokenInfo *const secondary_name = newToken ();
1030 vString * saveScope = vStringNew ();
1031 boolean is_class = FALSE;
1032 boolean is_terminated = TRUE;
1033 boolean is_global = FALSE;
1034 boolean is_prototype = FALSE;
1035 vString * fulltag;
1037 vStringClear(saveScope);
1039 * Functions can be named or unnamed.
1040 * This deals with these formats:
1041 * Function
1042 * validFunctionOne = function(a,b) {}
1043 * testlib.validFunctionFive = function(a,b) {}
1044 * var innerThree = function(a,b) {}
1045 * var innerFour = (a,b) {}
1046 * var D2 = secondary_fcn_name(a,b) {}
1047 * var D3 = new Function("a", "b", "return a+b;");
1048 * Class
1049 * testlib.extras.ValidClassOne = function(a,b) {
1050 * this.a = a;
1052 * Class Methods
1053 * testlib.extras.ValidClassOne.prototype = {
1054 * 'validMethodOne' : function(a,b) {},
1055 * 'validMethodTwo' : function(a,b) {}
1057 * ValidClassTwo = function ()
1059 * this.validMethodThree = function() {}
1060 * // unnamed method
1061 * this.validMethodFour = () {}
1063 * Database.prototype.validMethodThree = Database_getTodaysDate;
1066 if ( is_inside_class )
1067 is_class = TRUE;
1069 * var can preceed an inner function
1071 if ( isKeyword(token, KEYWORD_var) )
1074 * Only create variables for global scope
1076 if ( token->nestLevel == 0 )
1078 is_global = TRUE;
1080 readToken(token);
1083 if ( isKeyword(token, KEYWORD_this) )
1085 readToken(token);
1086 if (isType (token, TOKEN_PERIOD))
1088 readToken(token);
1092 copyToken(name, token);
1094 while (! isType (token, TOKEN_CLOSE_CURLY) &&
1095 ! isType (token, TOKEN_SEMICOLON) &&
1096 ! isType (token, TOKEN_EQUAL_SIGN) )
1098 /* Potentially the name of the function */
1099 readToken (token);
1100 if (isType (token, TOKEN_PERIOD))
1103 * Cannot be a global variable is it has dot references in the name
1105 is_global = FALSE;
1108 readToken (token);
1109 if ( isKeyword(token, KEYWORD_NONE) )
1111 if ( is_class )
1113 vStringCopy(saveScope, token->scope);
1114 addToScope(token, name->string);
1116 else
1117 addContext (name, token);
1119 else if ( isKeyword(token, KEYWORD_prototype) )
1122 * When we reach the "prototype" tag, we infer:
1123 * "BindAgent" is a class
1124 * "build" is a method
1126 * function BindAgent( repeatableIdName, newParentIdName ) {
1127 * }
1129 * CASE 1
1130 * Specified function name: "build"
1131 * BindAgent.prototype.build = function( mode ) {
1132 * ignore everything within this function
1135 * CASE 2
1136 * Prototype listing
1137 * ValidClassOne.prototype = {
1138 * 'validMethodOne' : function(a,b) {},
1139 * 'validMethodTwo' : function(a,b) {}
1143 makeClassTag (name);
1144 is_class = TRUE;
1145 is_prototype = TRUE;
1148 * There should a ".function_name" next.
1150 readToken (token);
1151 if (isType (token, TOKEN_PERIOD))
1154 * Handle CASE 1
1156 readToken (token);
1157 if ( isKeyword(token, KEYWORD_NONE) )
1159 vStringCopy(saveScope, token->scope);
1160 addToScope(token, name->string);
1162 makeJsTag (token, JSTAG_METHOD);
1164 * We can read until the end of the block / statement.
1165 * We need to correctly parse any nested blocks, but
1166 * we do NOT want to create any tags based on what is
1167 * within the blocks.
1169 token->ignoreTag = TRUE;
1171 * Find to the end of the statement
1173 findCmdTerm (token);
1174 token->ignoreTag = FALSE;
1175 is_terminated = TRUE;
1176 goto cleanUp;
1179 else if (isType (token, TOKEN_EQUAL_SIGN))
1181 readToken (token);
1182 if (isType (token, TOKEN_OPEN_CURLY))
1185 * Handle CASE 2
1187 * Creates tags for each of these class methods
1188 * ValidClassOne.prototype = {
1189 * 'validMethodOne' : function(a,b) {},
1190 * 'validMethodTwo' : function(a,b) {}
1193 parseMethods(token, name);
1195 * Find to the end of the statement
1197 findCmdTerm (token);
1198 token->ignoreTag = FALSE;
1199 is_terminated = TRUE;
1200 goto cleanUp;
1204 readToken (token);
1205 } while (isType (token, TOKEN_PERIOD));
1208 if ( isType (token, TOKEN_OPEN_PAREN) )
1209 skipArgumentList(token);
1211 if ( isType (token, TOKEN_OPEN_SQUARE) )
1212 skipArrayList(token);
1215 if ( isType (token, TOKEN_OPEN_CURLY) )
1217 is_class = parseBlock (token, name);
1222 if ( isType (token, TOKEN_CLOSE_CURLY) )
1225 * Reaching this section without having
1226 * processed an open curly brace indicates
1227 * the statement is most likely not terminated.
1229 is_terminated = FALSE;
1230 goto cleanUp;
1233 if ( isType (token, TOKEN_SEMICOLON) )
1236 * Only create variables for global scope
1238 if ( token->nestLevel == 0 && is_global )
1241 * Handles this syntax:
1242 * var g_var2;
1244 if (isType (token, TOKEN_SEMICOLON))
1245 makeJsTag (name, JSTAG_VARIABLE);
1248 * Statement has ended.
1249 * This deals with calls to functions, like:
1250 * alert(..);
1252 goto cleanUp;
1255 if ( isType (token, TOKEN_EQUAL_SIGN) )
1257 readToken (token);
1259 if ( isKeyword (token, KEYWORD_function) )
1261 readToken (token);
1263 if ( isKeyword (token, KEYWORD_NONE) &&
1264 ! isType (token, TOKEN_OPEN_PAREN) )
1267 * Functions of this format:
1268 * var D2A = function theAdd(a, b)
1269 * {
1270 * return a+b;
1271 * }
1272 * Are really two separate defined functions and
1273 * can be referenced in two ways:
1274 * alert( D2A(1,2) ); // produces 3
1275 * alert( theAdd(1,2) ); // also produces 3
1276 * So it must have two tags:
1277 * D2A
1278 * theAdd
1279 * Save the reference to the name for later use, once
1280 * we have established this is a valid function we will
1281 * create the secondary reference to it.
1283 copyToken(secondary_name, token);
1284 readToken (token);
1287 if ( isType (token, TOKEN_OPEN_PAREN) )
1288 skipArgumentList(token);
1290 if (isType (token, TOKEN_OPEN_CURLY))
1293 * This will be either a function or a class.
1294 * We can only determine this by checking the body
1295 * of the function. If we find a "this." we know
1296 * it is a class, otherwise it is a function.
1298 if ( is_inside_class )
1300 makeJsTag (name, JSTAG_METHOD);
1301 if ( vStringLength(secondary_name->string) > 0 )
1302 makeFunctionTag (secondary_name);
1303 parseBlock (token, name);
1305 else
1307 is_class = parseBlock (token, name);
1308 if ( is_class )
1309 makeClassTag (name);
1310 else
1311 makeFunctionTag (name);
1313 if ( vStringLength(secondary_name->string) > 0 )
1314 makeFunctionTag (secondary_name);
1317 * Find to the end of the statement
1319 goto cleanUp;
1323 else if (isType (token, TOKEN_OPEN_PAREN))
1326 * Handle nameless functions
1327 * this.method_name = () {}
1329 skipArgumentList(token);
1331 if (isType (token, TOKEN_OPEN_CURLY))
1334 * Nameless functions are only setup as methods.
1336 makeJsTag (name, JSTAG_METHOD);
1337 parseBlock (token, name);
1340 else if (isType (token, TOKEN_OPEN_CURLY))
1343 * Creates tags for each of these class methods
1344 * ValidClassOne.prototype = {
1345 * 'validMethodOne' : function(a,b) {},
1346 * 'validMethodTwo' : function(a,b) {}
1349 parseMethods(token, name);
1350 if (isType (token, TOKEN_CLOSE_CURLY))
1353 * Assume the closing parantheses terminates
1354 * this statements.
1356 is_terminated = TRUE;
1359 else if (isKeyword (token, KEYWORD_new))
1361 readToken (token);
1362 if ( isKeyword (token, KEYWORD_function) ||
1363 isKeyword (token, KEYWORD_capital_function) ||
1364 isKeyword (token, KEYWORD_object) ||
1365 isKeyword (token, KEYWORD_capital_object) )
1367 if ( isKeyword (token, KEYWORD_object) ||
1368 isKeyword (token, KEYWORD_capital_object) )
1369 is_class = TRUE;
1371 readToken (token);
1372 if ( isType (token, TOKEN_OPEN_PAREN) )
1373 skipArgumentList(token);
1375 if (isType (token, TOKEN_SEMICOLON))
1377 if ( token->nestLevel == 0 )
1379 if ( is_class )
1381 makeClassTag (name);
1382 } else {
1383 makeFunctionTag (name);
1389 else if (isKeyword (token, KEYWORD_NONE))
1392 * Only create variables for global scope
1394 if ( token->nestLevel == 0 && is_global )
1397 * A pointer can be created to the function.
1398 * If we recognize the function/class name ignore the variable.
1399 * This format looks identical to a variable definition.
1400 * A variable defined outside of a block is considered
1401 * a global variable:
1402 * var g_var1 = 1;
1403 * var g_var2;
1404 * This is not a global variable:
1405 * var g_var = function;
1406 * This is a global variable:
1407 * var g_var = different_var_name;
1409 fulltag = vStringNew ();
1410 if (vStringLength (token->scope) > 0)
1412 vStringCopy(fulltag, token->scope);
1413 vStringCatS (fulltag, ".");
1414 vStringCatS (fulltag, vStringValue(token->string));
1416 else
1418 vStringCopy(fulltag, token->string);
1420 vStringTerminate(fulltag);
1421 if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) &&
1422 ! stringListHas(ClassNames, vStringValue (fulltag)) )
1424 findCmdTerm (token);
1425 if (isType (token, TOKEN_SEMICOLON))
1426 makeJsTag (name, JSTAG_VARIABLE);
1428 vStringDelete (fulltag);
1432 findCmdTerm (token);
1435 * Statements can be optionally terminated in the case of
1436 * statement prior to a close curly brace as in the
1437 * document.write line below:
1439 * function checkForUpdate() {
1440 * if( 1==1 ) {
1441 * document.write("hello from checkForUpdate<br>")
1443 * return 1;
1446 if ( ! is_terminated && isType (token, TOKEN_CLOSE_CURLY))
1447 is_terminated = FALSE;
1450 cleanUp:
1451 vStringCopy(token->scope, saveScope);
1452 deleteToken (name);
1453 deleteToken (secondary_name);
1454 vStringDelete(saveScope);
1456 return is_terminated;
1459 static boolean parseLine (tokenInfo *const token, boolean is_inside_class)
1461 boolean is_terminated = TRUE;
1463 * Detect the common statements, if, while, for, do, ...
1464 * This is necessary since the last statement within a block "{}"
1465 * can be optionally terminated.
1467 * If the statement is not terminated, we need to tell
1468 * the calling routine to prevent reading an additional token
1469 * looking for the end of the statement.
1472 if (isType(token, TOKEN_KEYWORD))
1474 switch (token->keyword)
1476 case KEYWORD_for:
1477 case KEYWORD_while:
1478 case KEYWORD_do:
1479 parseLoop (token);
1480 break;
1481 case KEYWORD_if:
1482 case KEYWORD_else:
1483 case KEYWORD_try:
1484 case KEYWORD_catch:
1485 case KEYWORD_finally:
1486 /* Common semantics */
1487 is_terminated = parseIf (token);
1488 break;
1489 case KEYWORD_switch:
1490 parseSwitch (token);
1491 break;
1492 default:
1493 parseStatement (token, is_inside_class);
1494 break;
1497 else
1500 * Special case where single line statements may not be
1501 * SEMICOLON terminated. parseBlock needs to know this
1502 * so that it does not read the next token.
1504 is_terminated = parseStatement (token, is_inside_class);
1506 return is_terminated;
1509 static void parseJsFile (tokenInfo *const token)
1513 readToken (token);
1515 if (isType(token, TOKEN_KEYWORD))
1517 switch (token->keyword)
1519 case KEYWORD_function: parseFunction (token); break;
1520 default: parseLine (token, FALSE); break;
1523 else
1525 parseLine (token, FALSE);
1527 } while (TRUE);
1530 static void initialize (const langType language)
1532 Assert (sizeof (JsKinds) / sizeof (JsKinds [0]) == JSTAG_COUNT);
1533 Lang_js = language;
1534 buildJsKeywordHash ();
1537 static void findJsTags (void)
1539 tokenInfo *const token = newToken ();
1540 exception_t exception;
1542 ClassNames = stringListNew ();
1543 FunctionNames = stringListNew ();
1545 exception = (exception_t) (setjmp (Exception));
1546 while (exception == ExceptionNone)
1547 parseJsFile (token);
1549 stringListDelete (ClassNames);
1550 stringListDelete (FunctionNames);
1551 ClassNames = NULL;
1552 FunctionNames = NULL;
1553 deleteToken (token);
1556 /* Create parser definition stucture */
1557 extern parserDefinition* JavaScriptParser (void)
1559 static const char *const extensions [] = { "js", NULL };
1560 parserDefinition *const def = parserNew ("JavaScript");
1561 def->extensions = extensions;
1563 * New definitions for parsing instead of regex
1565 def->kinds = JsKinds;
1566 def->kindCount = KIND_COUNT (JsKinds);
1567 def->parser = findJsTags;
1568 def->initialize = initialize;
1570 return def;
1572 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */