Version bump.
[geany-mirror.git] / tagmanager / js.c
blob7c1844d74d1e173e3024799e44849432e754ab17
1 /*
2 * $Id$
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 "keyword.h"
29 #include "parse.h"
30 #include "read.h"
31 #include "main.h"
32 #include "vstring.h"
35 * MACROS
37 #define isType(token,t) (boolean) ((token)->type == (t))
38 #define isKeyword(token,k) (boolean) ((token)->keyword == (k))
41 * DATA DECLARATIONS
44 typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
47 * Tracks class and function names already created
49 static stringList *ClassNames;
50 static stringList *FunctionNames;
52 /* Used to specify type of keyword.
54 typedef enum eKeywordId {
55 KEYWORD_NONE = -1,
56 KEYWORD_function,
57 KEYWORD_capital_function,
58 KEYWORD_object,
59 KEYWORD_capital_object,
60 KEYWORD_prototype,
61 KEYWORD_var,
62 KEYWORD_new,
63 KEYWORD_this,
64 KEYWORD_for,
65 KEYWORD_while,
66 KEYWORD_do,
67 KEYWORD_if,
68 KEYWORD_else,
69 KEYWORD_switch,
70 KEYWORD_try,
71 KEYWORD_catch,
72 KEYWORD_finally
73 } keywordId;
75 /* Used to determine whether keyword is valid for the token language and
76 * what its ID is.
78 typedef struct sKeywordDesc {
79 const char *name;
80 keywordId id;
81 } keywordDesc;
83 typedef enum eTokenType {
84 TOKEN_UNDEFINED,
85 TOKEN_CHARACTER,
86 TOKEN_CLOSE_PAREN,
87 TOKEN_SEMICOLON,
88 TOKEN_COLON,
89 TOKEN_COMMA,
90 TOKEN_KEYWORD,
91 TOKEN_OPEN_PAREN,
92 TOKEN_OPERATOR,
93 TOKEN_IDENTIFIER,
94 TOKEN_STRING,
95 TOKEN_PERIOD,
96 TOKEN_OPEN_CURLY,
97 TOKEN_CLOSE_CURLY,
98 TOKEN_EQUAL_SIGN,
99 TOKEN_FORWARD_SLASH,
100 TOKEN_OPEN_SQUARE,
101 TOKEN_CLOSE_SQUARE
102 } tokenType;
104 typedef struct sTokenInfo {
105 tokenType type;
106 keywordId keyword;
107 vString * string;
108 vString * scope;
109 unsigned long lineNumber;
110 fpos_t filePosition;
111 int nestLevel;
112 boolean ignoreTag;
113 int bufferPosition; /* buffer position of line containing name */
114 } tokenInfo;
117 * DATA DEFINITIONS
120 static langType Lang_js;
122 static jmp_buf Exception;
124 typedef enum {
125 JSTAG_FUNCTION,
126 JSTAG_CLASS,
127 JSTAG_METHOD,
128 JSTAG_PROPERTY,
129 JSTAG_VARIABLE,
130 JSTAG_COUNT
131 } jsKind;
133 static kindOption JsKinds [] = {
134 { TRUE, 'f', "function", "functions" },
135 { TRUE, 'c', "class", "classes" },
136 { TRUE, 'm', "method", "methods" },
137 { TRUE, 'p', "member", "properties" },
138 { TRUE, 'v', "variable", "global variables" }
141 static const keywordDesc JsKeywordTable [] = {
142 /* keyword keyword ID */
143 { "function", KEYWORD_function },
144 { "Function", KEYWORD_capital_function },
145 { "object", KEYWORD_object },
146 { "Object", KEYWORD_capital_object },
147 { "prototype", KEYWORD_prototype },
148 { "var", KEYWORD_var },
149 { "new", KEYWORD_new },
150 { "this", KEYWORD_this },
151 { "for", KEYWORD_for },
152 { "while", KEYWORD_while },
153 { "do", KEYWORD_do },
154 { "if", KEYWORD_if },
155 { "else", KEYWORD_else },
156 { "switch", KEYWORD_switch },
157 { "try", KEYWORD_try },
158 { "catch", KEYWORD_catch },
159 { "finally", KEYWORD_finally }
163 * FUNCTION DEFINITIONS
166 /* Recursive functions */
167 static void parseFunction (tokenInfo *const token);
168 static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent);
169 static boolean parseLine (tokenInfo *const token, boolean is_inside_class);
171 static boolean isIdentChar (const int c)
173 return (boolean)
174 (isalpha (c) || isdigit (c) || c == '$' ||
175 c == '@' || c == '_' || c == '#');
178 static void buildJsKeywordHash (void)
180 const size_t count = sizeof (JsKeywordTable) /
181 sizeof (JsKeywordTable [0]);
182 size_t i;
183 for (i = 0 ; i < count ; ++i)
185 const keywordDesc* const p = &JsKeywordTable [i];
186 addKeyword (p->name, Lang_js, (int) p->id);
190 static tokenInfo *newToken (void)
192 tokenInfo *const token = xMalloc (1, tokenInfo);
194 token->type = TOKEN_UNDEFINED;
195 token->keyword = KEYWORD_NONE;
196 token->string = vStringNew ();
197 token->scope = vStringNew ();
198 token->nestLevel = 0;
199 token->ignoreTag = FALSE;
200 token->lineNumber = getSourceLineNumber ();
201 if (useFile())
202 token->filePosition = getInputFilePosition ();
203 else
204 token->bufferPosition = getInputBufferPosition ();
206 return token;
209 static void deleteToken (tokenInfo *const token)
211 vStringDelete (token->string);
212 vStringDelete (token->scope);
213 eFree (token);
217 * Tag generation functions
220 static void makeConstTag (tokenInfo *const token, const jsKind kind)
222 if (JsKinds [kind].enabled && ! token->ignoreTag )
224 const char *const name = vStringValue (token->string);
225 tagEntryInfo e;
226 initTagEntry (&e, name);
228 e.lineNumber = token->lineNumber;
229 e.filePosition = token->filePosition;
230 e.kindName = JsKinds [kind].name;
231 e.kind = JsKinds [kind].letter;
233 makeTagEntry (&e);
237 static void makeJsTag (tokenInfo *const token, const jsKind kind)
239 vString * fulltag;
241 if (JsKinds [kind].enabled && ! token->ignoreTag )
244 * If a scope has been added to the token, change the token
245 * string to include the scope when making the tag.
247 if ( vStringLength(token->scope) > 0 )
249 fulltag = vStringNew ();
250 vStringCopy(fulltag, token->scope);
251 vStringCatS (fulltag, ".");
252 vStringCatS (fulltag, vStringValue(token->string));
253 vStringTerminate(fulltag);
254 vStringCopy(token->string, fulltag);
255 vStringDelete (fulltag);
257 makeConstTag (token, kind);
261 static void makeClassTag (tokenInfo *const token)
263 if ( ! token->ignoreTag )
265 if ( ! stringListHas(ClassNames, vStringValue (token->string)) )
267 stringListAdd (ClassNames, vStringNewCopy (token->string));
268 makeJsTag (token, JSTAG_CLASS);
273 static void makeFunctionTag (tokenInfo *const token)
275 if ( ! token->ignoreTag )
277 if ( ! stringListHas(FunctionNames, vStringValue (token->string)) )
279 stringListAdd (FunctionNames, vStringNewCopy (token->string));
280 makeJsTag (token, JSTAG_FUNCTION);
286 * Parsing functions
289 static int skipToCharacter (const int c)
291 int d;
294 d = fileGetc ();
295 } while (d != EOF && d != c);
296 return d;
299 static void parseString (vString *const string, const int delimiter)
301 boolean end = FALSE;
302 while (! end)
304 int c = fileGetc ();
305 if (c == EOF)
306 end = TRUE;
307 else if (c == '\\')
309 c = fileGetc(); /* This maybe a ' or ". */
310 vStringPut(string, c);
312 else if (c == delimiter)
313 end = TRUE;
314 else
315 vStringPut (string, c);
317 vStringTerminate (string);
320 /* Read a C identifier beginning with "firstChar" and places it into
321 * "name".
323 static void parseIdentifier (vString *const string, const int firstChar)
325 int c = firstChar;
326 Assert (isIdentChar (c));
329 vStringPut (string, c);
330 c = fileGetc ();
331 } while (isIdentChar (c));
332 vStringTerminate (string);
333 if (!isspace (c))
334 fileUngetc (c); /* unget non-identifier character */
337 static keywordId analyzeToken (vString *const name)
339 vString *keyword = vStringNew ();
340 keywordId result;
341 vStringCopyToLower (keyword, name);
342 result = (keywordId) lookupKeyword (vStringValue (keyword), Lang_js);
343 vStringDelete (keyword);
344 return result;
347 static void readToken (tokenInfo *const token)
349 int c;
351 token->type = TOKEN_UNDEFINED;
352 token->keyword = KEYWORD_NONE;
353 vStringClear (token->string);
355 getNextChar:
358 c = fileGetc ();
359 token->lineNumber = getSourceLineNumber ();
360 if (useFile())
361 token->filePosition = getInputFilePosition ();
362 else
363 token->bufferPosition = getInputBufferPosition ();
365 while (c == '\t' || c == ' ' || c == '\n');
367 switch (c)
369 case EOF: longjmp (Exception, (int)ExceptionEOF); break;
370 case '(': token->type = TOKEN_OPEN_PAREN; break;
371 case ')': token->type = TOKEN_CLOSE_PAREN; break;
372 case ';': token->type = TOKEN_SEMICOLON; break;
373 case ',': token->type = TOKEN_COMMA; break;
374 case '.': token->type = TOKEN_PERIOD; break;
375 case ':': token->type = TOKEN_COLON; break;
376 case '{': token->type = TOKEN_OPEN_CURLY; break;
377 case '}': token->type = TOKEN_CLOSE_CURLY; break;
378 case '=': token->type = TOKEN_EQUAL_SIGN; break;
379 case '[': token->type = TOKEN_OPEN_SQUARE; break;
380 case ']': token->type = TOKEN_CLOSE_SQUARE; break;
382 case '\'':
383 case '"':
384 token->type = TOKEN_STRING;
385 parseString (token->string, c);
386 token->lineNumber = getSourceLineNumber ();
387 if (useFile())
388 token->filePosition = getInputFilePosition ();
389 else
390 token->bufferPosition = getInputBufferPosition ();
391 break;
393 case '\\':
394 c = fileGetc ();
395 if (c != '\\' && c != '"' && !isspace (c))
396 fileUngetc (c);
397 token->type = TOKEN_CHARACTER;
398 token->lineNumber = getSourceLineNumber ();
399 if (useFile())
400 token->filePosition = getInputFilePosition ();
401 else
402 token->bufferPosition = getInputBufferPosition ();
403 break;
405 case '/':
407 int d = fileGetc ();
408 if ( (d != '*') && /* is this the start of a comment? */
409 (d != '/') ) /* is a one line comment? */
411 token->type = TOKEN_FORWARD_SLASH;
412 fileUngetc (d);
414 else
416 if (d == '*')
420 skipToCharacter ('*');
421 c = fileGetc ();
422 if (c == '/')
423 break;
424 else
425 fileUngetc (c);
426 } while (c != EOF && c != '\0');
427 goto getNextChar;
429 else if (d == '/') /* is this the start of a comment? */
431 skipToCharacter ('\n');
432 goto getNextChar;
435 break;
438 default:
439 if (! isIdentChar (c))
440 token->type = TOKEN_UNDEFINED;
441 else
443 parseIdentifier (token->string, c);
444 token->lineNumber = getSourceLineNumber ();
445 if (useFile())
446 token->filePosition = getInputFilePosition ();
447 else
448 token->bufferPosition = getInputBufferPosition ();
449 token->keyword = analyzeToken (token->string);
450 if (isKeyword (token, KEYWORD_NONE))
451 token->type = TOKEN_IDENTIFIER;
452 else
453 token->type = TOKEN_KEYWORD;
455 break;
459 static void copyToken (tokenInfo *const dest, tokenInfo *const src)
461 dest->nestLevel = src->nestLevel;
462 dest->lineNumber = src->lineNumber;
463 if (useFile())
464 dest->filePosition = src->filePosition;
465 else
466 dest->bufferPosition = src->bufferPosition;
467 dest->type = src->type;
468 dest->keyword = src->keyword;
469 vStringCopy(dest->string, src->string);
470 vStringCopy(dest->scope, src->scope);
474 * Token parsing functions
477 static void skipArgumentList (tokenInfo *const token)
479 int nest_level = 0;
482 * Other databases can have arguments with fully declared
483 * datatypes:
484 * ( name varchar(30), text binary(10) )
485 * So we must check for nested open and closing parantheses
488 if (isType (token, TOKEN_OPEN_PAREN)) /* arguments? */
490 nest_level++;
491 while (! (isType (token, TOKEN_CLOSE_PAREN) && (nest_level == 0)))
493 readToken (token);
494 if (isType (token, TOKEN_OPEN_PAREN))
496 nest_level++;
498 if (isType (token, TOKEN_CLOSE_PAREN))
500 if (nest_level > 0)
502 nest_level--;
506 readToken (token);
510 static void skipArrayList (tokenInfo *const token)
512 int nest_level = 0;
515 * Handle square brackets
516 * var name[1]
517 * So we must check for nested open and closing square brackets
520 if (isType (token, TOKEN_OPEN_SQUARE)) /* arguments? */
522 nest_level++;
523 while (! (isType (token, TOKEN_CLOSE_SQUARE) && (nest_level == 0)))
525 readToken (token);
526 if (isType (token, TOKEN_OPEN_SQUARE))
528 nest_level++;
530 if (isType (token, TOKEN_CLOSE_SQUARE))
532 if (nest_level > 0)
534 nest_level--;
538 readToken (token);
542 static void addContext (tokenInfo* const parent, const tokenInfo* const child)
544 if (vStringLength (parent->string) > 0)
546 vStringCatS (parent->string, ".");
548 vStringCatS (parent->string, vStringValue(child->string));
549 vStringTerminate(parent->string);
552 static void addToScope (tokenInfo* const token, vString* const extra)
554 if (vStringLength (token->scope) > 0)
556 vStringCatS (token->scope, ".");
558 vStringCatS (token->scope, vStringValue(extra));
559 vStringTerminate(token->scope);
563 * Scanning functions
566 static void findCmdTerm (tokenInfo *const token)
569 * Read until we find either a semicolon or closing brace.
570 * Any nested braces will be handled within.
572 while (! ( isType (token, TOKEN_SEMICOLON) ||
573 isType (token, TOKEN_CLOSE_CURLY) ) )
575 /* Handle nested blocks */
576 if ( isType (token, TOKEN_OPEN_CURLY))
578 parseBlock (token, token);
580 else if ( isType (token, TOKEN_OPEN_PAREN) )
582 skipArgumentList(token);
584 else
586 readToken (token);
591 static void parseSwitch (tokenInfo *const token)
594 * switch (expression){
595 * case value1:
596 * statement;
597 * break;
598 * case value2:
599 * statement;
600 * break;
601 * default : statement;
605 readToken (token);
607 if (isType (token, TOKEN_OPEN_PAREN))
610 * Handle nameless functions, these will only
611 * be considered methods.
613 skipArgumentList(token);
616 if (isType (token, TOKEN_OPEN_CURLY))
619 * This will be either a function or a class.
620 * We can only determine this by checking the body
621 * of the function. If we find a "this." we know
622 * it is a class, otherwise it is a function.
624 parseBlock (token, token);
629 static void parseLoop (tokenInfo *const token)
632 * Handles these statements
633 * for (x=0; x<3; x++)
634 * document.write("This text is repeated three times<br>");
636 * for (x=0; x<3; x++)
638 * document.write("This text is repeated three times<br>");
641 * while (number<5){
642 * document.write(number+"<br>");
643 * number++;
646 * do{
647 * document.write(number+"<br>");
648 * number++;
650 * while (number<5);
653 if (isKeyword (token, KEYWORD_for) || isKeyword (token, KEYWORD_while))
655 readToken(token);
657 if (isType (token, TOKEN_OPEN_PAREN))
660 * Handle nameless functions, these will only
661 * be considered methods.
663 skipArgumentList(token);
666 if (isType (token, TOKEN_OPEN_CURLY))
669 * This will be either a function or a class.
670 * We can only determine this by checking the body
671 * of the function. If we find a "this." we know
672 * it is a class, otherwise it is a function.
674 parseBlock (token, token);
676 else
678 parseLine(token, FALSE);
681 else if (isKeyword (token, KEYWORD_do))
683 readToken(token);
685 if (isType (token, TOKEN_OPEN_CURLY))
688 * This will be either a function or a class.
689 * We can only determine this by checking the body
690 * of the function. If we find a "this." we know
691 * it is a class, otherwise it is a function.
693 parseBlock (token, token);
695 else
697 parseLine(token, FALSE);
700 readToken(token);
702 if (isKeyword (token, KEYWORD_while))
704 readToken(token);
706 if (isType (token, TOKEN_OPEN_PAREN))
709 * Handle nameless functions, these will only
710 * be considered methods.
712 skipArgumentList(token);
718 static boolean parseIf (tokenInfo *const token)
720 boolean read_next_token = TRUE;
722 * If statements have two forms
723 * if ( ... )
724 * one line;
726 * if ( ... )
727 * statement;
728 * else
729 * statement
731 * if ( ... ) {
732 * multiple;
733 * statements;
737 * if ( ... ) {
738 * return elem
741 * This example if correctly written, but the
742 * else contains only 1 statement without a terminator
743 * since the function finishes with the closing brace.
745 * function a(flag){
746 * if(flag)
747 * test(1);
748 * else
749 * test(2)
752 * TODO: Deal with statements that can optional end
753 * without a semi-colon. Currently this messes up
754 * the parsing of blocks.
755 * Need to somehow detect this has happened, and either
756 * backup a token, or skip reading the next token if
757 * that is possible from all code locations.
761 readToken (token);
763 if (isType (token, TOKEN_OPEN_PAREN))
766 * Handle nameless functions, these will only
767 * be considered methods.
769 skipArgumentList(token);
772 if (isType (token, TOKEN_OPEN_CURLY))
775 * This will be either a function or a class.
776 * We can only determine this by checking the body
777 * of the function. If we find a "this." we know
778 * it is a class, otherwise it is a function.
780 parseBlock (token, token);
782 else
784 findCmdTerm (token);
787 * The IF could be followed by an ELSE statement.
788 * This too could have two formats, a curly braced
789 * multiline section, or another single line.
792 if (isType (token, TOKEN_CLOSE_CURLY))
795 * This statement did not have a line terminator.
797 read_next_token = FALSE;
799 else
801 readToken (token);
803 if (isType (token, TOKEN_CLOSE_CURLY))
806 * This statement did not have a line terminator.
808 read_next_token = FALSE;
810 else
812 if (isKeyword (token, KEYWORD_else))
813 read_next_token = parseIf (token);
817 return read_next_token;
820 static void parseFunction (tokenInfo *const token)
822 tokenInfo *const name = newToken ();
823 boolean is_class = FALSE;
826 * This deals with these formats
827 * function validFunctionTwo(a,b) {}
830 readToken (name);
831 /* Add scope in case this is an INNER function */
832 addToScope(name, token->scope);
834 readToken (token);
835 if (isType (token, TOKEN_PERIOD))
839 readToken (token);
840 if ( isKeyword(token, KEYWORD_NONE) )
842 addContext (name, token);
843 readToken (token);
845 } while (isType (token, TOKEN_PERIOD));
848 if ( isType (token, TOKEN_OPEN_PAREN) )
849 skipArgumentList(token);
851 if ( isType (token, TOKEN_OPEN_CURLY) )
853 is_class = parseBlock (token, name);
854 if ( is_class )
855 makeClassTag (name);
856 else
857 makeFunctionTag (name);
860 findCmdTerm (token);
862 deleteToken (name);
865 static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent)
867 boolean is_class = FALSE;
868 boolean read_next_token = TRUE;
869 vString * saveScope = vStringNew ();
871 token->nestLevel++;
873 * Make this routine a bit more forgiving.
874 * If called on an open_curly advance it
876 if ( isType (token, TOKEN_OPEN_CURLY) &&
877 isKeyword(token, KEYWORD_NONE) )
878 readToken(token);
880 if (! isType (token, TOKEN_CLOSE_CURLY))
883 * Read until we find the closing brace,
884 * any nested braces will be handled within
888 read_next_token = TRUE;
889 if (isKeyword (token, KEYWORD_this))
892 * Means we are inside a class and have found
893 * a class, not a function
895 is_class = TRUE;
896 vStringCopy(saveScope, token->scope);
897 addToScope (token, parent->string);
900 * Ignore the remainder of the line
901 * findCmdTerm(token);
903 parseLine (token, is_class);
905 vStringCopy(token->scope, saveScope);
907 else if (isKeyword (token, KEYWORD_var))
910 * Potentially we have found an inner function.
911 * Set something to indicate the scope
913 vStringCopy(saveScope, token->scope);
914 addToScope (token, parent->string);
915 parseLine (token, is_class);
916 vStringCopy(token->scope, saveScope);
918 else if (isKeyword (token, KEYWORD_function))
920 vStringCopy(saveScope, token->scope);
921 addToScope (token, parent->string);
922 parseFunction (token);
923 vStringCopy(token->scope, saveScope);
925 else if (isType (token, TOKEN_OPEN_CURLY))
927 /* Handle nested blocks */
928 parseBlock (token, parent);
930 else
933 * It is possible for a line to have no terminator
934 * if the following line is a closing brace.
935 * parseLine will detect this case and indicate
936 * whether we should read an additional token.
938 read_next_token = parseLine (token, is_class);
942 * Always read a new token unless we find a statement without
943 * a ending terminator
945 if( read_next_token )
946 readToken(token);
949 * If we find a statement without a terminator consider the
950 * block finished, otherwise the stack will be off by one.
952 } while (! isType (token, TOKEN_CLOSE_CURLY) && read_next_token );
955 vStringDelete(saveScope);
956 token->nestLevel--;
958 return is_class;
961 static void parseMethods (tokenInfo *const token, tokenInfo *const class)
963 tokenInfo *const name = newToken ();
966 * This deals with these formats
967 * validProperty : 2,
968 * validMethod : function(a,b) {}
969 * 'validMethod2' : function(a,b) {}
974 readToken (token);
975 if (isType (token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE))
977 copyToken(name, token);
979 readToken (token);
980 if ( isType (token, TOKEN_COLON) )
982 readToken (token);
983 if ( isKeyword (token, KEYWORD_function) )
985 readToken (token);
986 if ( isType (token, TOKEN_OPEN_PAREN) )
988 skipArgumentList(token);
991 if (isType (token, TOKEN_OPEN_CURLY))
993 addToScope (name, class->string);
994 makeJsTag (name, JSTAG_METHOD);
995 parseBlock (token, name);
998 * Read to the closing curly, check next
999 * token, if a comma, we must loop again
1001 readToken (token);
1004 else
1006 addToScope (name, class->string);
1007 makeJsTag (name, JSTAG_PROPERTY);
1010 * Read the next token, if a comma
1011 * we must loop again
1013 readToken (token);
1017 } while ( isType(token, TOKEN_COMMA) );
1019 findCmdTerm (token);
1021 deleteToken (name);
1024 static boolean parseStatement (tokenInfo *const token, boolean is_inside_class)
1026 tokenInfo *const name = newToken ();
1027 tokenInfo *const secondary_name = newToken ();
1028 vString * saveScope = vStringNew ();
1029 boolean is_class = FALSE;
1030 boolean is_terminated = TRUE;
1031 boolean is_global = FALSE;
1032 boolean is_prototype = FALSE;
1034 vStringClear(saveScope);
1036 * Functions can be named or unnamed.
1037 * This deals with these formats:
1038 * Function
1039 * validFunctionOne = function(a,b) {}
1040 * testlib.validFunctionFive = function(a,b) {}
1041 * var innerThree = function(a,b) {}
1042 * var innerFour = (a,b) {}
1043 * var D2 = secondary_fcn_name(a,b) {}
1044 * var D3 = new Function("a", "b", "return a+b;");
1045 * Class
1046 * testlib.extras.ValidClassOne = function(a,b) {
1047 * this.a = a;
1049 * Class Methods
1050 * testlib.extras.ValidClassOne.prototype = {
1051 * 'validMethodOne' : function(a,b) {},
1052 * 'validMethodTwo' : function(a,b) {}
1054 * ValidClassTwo = function ()
1056 * this.validMethodThree = function() {}
1057 * // unnamed method
1058 * this.validMethodFour = () {}
1060 * Database.prototype.validMethodThree = Database_getTodaysDate;
1063 if ( is_inside_class )
1064 is_class = TRUE;
1066 * var can preceed an inner function
1068 if ( isKeyword(token, KEYWORD_var) )
1071 * Only create variables for global scope
1073 if ( token->nestLevel == 0 )
1075 is_global = TRUE;
1077 readToken(token);
1080 if ( isKeyword(token, KEYWORD_this) )
1082 readToken(token);
1083 if (isType (token, TOKEN_PERIOD))
1085 readToken(token);
1089 copyToken(name, token);
1091 while (! isType (token, TOKEN_CLOSE_CURLY) &&
1092 ! isType (token, TOKEN_SEMICOLON) &&
1093 ! isType (token, TOKEN_EQUAL_SIGN) )
1095 /* Potentially the name of the function */
1096 readToken (token);
1097 if (isType (token, TOKEN_PERIOD))
1100 * Cannot be a global variable is it has dot references in the name
1102 is_global = FALSE;
1105 readToken (token);
1106 if ( isKeyword(token, KEYWORD_NONE) )
1108 if ( is_class )
1110 vStringCopy(saveScope, token->scope);
1111 addToScope(token, name->string);
1113 else
1114 addContext (name, token);
1116 else if ( isKeyword(token, KEYWORD_prototype) )
1119 * When we reach the "prototype" tag, we infer:
1120 * "BindAgent" is a class
1121 * "build" is a method
1123 * function BindAgent( repeatableIdName, newParentIdName ) {
1126 * CASE 1
1127 * Specified function name: "build"
1128 * BindAgent.prototype.build = function( mode ) {
1129 * ignore everything within this function
1132 * CASE 2
1133 * Prototype listing
1134 * ValidClassOne.prototype = {
1135 * 'validMethodOne' : function(a,b) {},
1136 * 'validMethodTwo' : function(a,b) {}
1140 makeClassTag (name);
1141 is_class = TRUE;
1142 is_prototype = TRUE;
1145 * There should a ".function_name" next.
1147 readToken (token);
1148 if (isType (token, TOKEN_PERIOD))
1151 * Handle CASE 1
1153 readToken (token);
1154 if ( isKeyword(token, KEYWORD_NONE) )
1156 vStringCopy(saveScope, token->scope);
1157 addToScope(token, name->string);
1159 makeJsTag (token, JSTAG_METHOD);
1161 * We can read until the end of the block / statement.
1162 * We need to correctly parse any nested blocks, but
1163 * we do NOT want to create any tags based on what is
1164 * within the blocks.
1166 token->ignoreTag = TRUE;
1168 * Find to the end of the statement
1170 findCmdTerm (token);
1171 token->ignoreTag = FALSE;
1172 is_terminated = TRUE;
1173 goto cleanUp;
1176 else if (isType (token, TOKEN_EQUAL_SIGN))
1178 readToken (token);
1179 if (isType (token, TOKEN_OPEN_CURLY))
1182 * Handle CASE 2
1184 * Creates tags for each of these class methods
1185 * ValidClassOne.prototype = {
1186 * 'validMethodOne' : function(a,b) {},
1187 * 'validMethodTwo' : function(a,b) {}
1190 parseMethods(token, name);
1192 * Find to the end of the statement
1194 findCmdTerm (token);
1195 token->ignoreTag = FALSE;
1196 is_terminated = TRUE;
1197 goto cleanUp;
1201 readToken (token);
1202 } while (isType (token, TOKEN_PERIOD));
1205 if ( isType (token, TOKEN_OPEN_PAREN) )
1206 skipArgumentList(token);
1208 if ( isType (token, TOKEN_OPEN_SQUARE) )
1209 skipArrayList(token);
1212 if ( isType (token, TOKEN_OPEN_CURLY) )
1214 is_class = parseBlock (token, name);
1219 if ( isType (token, TOKEN_CLOSE_CURLY) )
1222 * Reaching this section without having
1223 * processed an open curly brace indicates
1224 * the statement is most likely not terminated.
1226 is_terminated = FALSE;
1227 goto cleanUp;
1230 if ( isType (token, TOKEN_SEMICOLON) )
1233 * Only create variables for global scope
1235 if ( token->nestLevel == 0 && is_global )
1238 * Handles this syntax:
1239 * var g_var2;
1241 if (isType (token, TOKEN_SEMICOLON))
1242 makeJsTag (name, JSTAG_VARIABLE);
1245 * Statement has ended.
1246 * This deals with calls to functions, like:
1247 * alert(..);
1249 goto cleanUp;
1252 if ( isType (token, TOKEN_EQUAL_SIGN) )
1254 readToken (token);
1256 if ( isKeyword (token, KEYWORD_function) )
1258 readToken (token);
1260 if ( isKeyword (token, KEYWORD_NONE) &&
1261 ! isType (token, TOKEN_OPEN_PAREN) )
1264 * Functions of this format:
1265 * var D2A = function theAdd(a, b)
1267 * return a+b;
1269 * Are really two separate defined functions and
1270 * can be referenced in two ways:
1271 * alert( D2A(1,2) ); // produces 3
1272 * alert( theAdd(1,2) ); // also produces 3
1273 * So it must have two tags:
1274 * D2A
1275 * theAdd
1276 * Save the reference to the name for later use, once
1277 * we have established this is a valid function we will
1278 * create the secondary reference to it.
1280 copyToken(secondary_name, token);
1281 readToken (token);
1284 if ( isType (token, TOKEN_OPEN_PAREN) )
1285 skipArgumentList(token);
1287 if (isType (token, TOKEN_OPEN_CURLY))
1290 * This will be either a function or a class.
1291 * We can only determine this by checking the body
1292 * of the function. If we find a "this." we know
1293 * it is a class, otherwise it is a function.
1295 if ( is_inside_class )
1297 makeJsTag (name, JSTAG_METHOD);
1298 if ( vStringLength(secondary_name->string) > 0 )
1299 makeFunctionTag (secondary_name);
1300 parseBlock (token, name);
1302 else
1304 is_class = parseBlock (token, name);
1305 if ( is_class )
1306 makeClassTag (name);
1307 else
1308 makeFunctionTag (name);
1310 if ( vStringLength(secondary_name->string) > 0 )
1311 makeFunctionTag (secondary_name);
1314 * Find to the end of the statement
1316 goto cleanUp;
1320 else if (isType (token, TOKEN_OPEN_PAREN))
1323 * Handle nameless functions
1324 * this.method_name = () {}
1326 skipArgumentList(token);
1328 if (isType (token, TOKEN_OPEN_CURLY))
1331 * Nameless functions are only setup as methods.
1333 makeJsTag (name, JSTAG_METHOD);
1334 parseBlock (token, name);
1337 else if (isType (token, TOKEN_OPEN_CURLY))
1340 * Creates tags for each of these class methods
1341 * ValidClassOne.prototype = {
1342 * 'validMethodOne' : function(a,b) {},
1343 * 'validMethodTwo' : function(a,b) {}
1346 parseMethods(token, name);
1348 else if (isKeyword (token, KEYWORD_new))
1350 readToken (token);
1351 if ( isKeyword (token, KEYWORD_function) ||
1352 isKeyword (token, KEYWORD_capital_function) ||
1353 isKeyword (token, KEYWORD_object) ||
1354 isKeyword (token, KEYWORD_capital_object) )
1356 if ( isKeyword (token, KEYWORD_object) ||
1357 isKeyword (token, KEYWORD_capital_object) )
1358 is_class = TRUE;
1360 readToken (token);
1361 if ( isType (token, TOKEN_OPEN_PAREN) )
1362 skipArgumentList(token);
1364 if (isType (token, TOKEN_SEMICOLON))
1366 if ( is_class )
1368 makeClassTag (name);
1369 } else {
1370 makeFunctionTag (name);
1375 else if (isKeyword (token, KEYWORD_NONE))
1378 * Only create variables for global scope
1380 if ( token->nestLevel == 0 && is_global )
1383 * A pointer can be created to the function.
1384 * If we recognize the function/class name ignore the variable.
1385 * This format looks identical to a variable definition.
1386 * A variable defined outside of a block is considered
1387 * a global variable:
1388 * var g_var1 = 1;
1389 * var g_var2;
1390 * This is not a global variable:
1391 * var g_var = function;
1392 * This is a global variable:
1393 * var g_var = different_var_name;
1395 if ( ! stringListHas(FunctionNames, vStringValue (token->string)) &&
1396 ! stringListHas(ClassNames, vStringValue (token->string)) )
1398 findCmdTerm (token);
1399 if (isType (token, TOKEN_SEMICOLON))
1400 makeJsTag (name, JSTAG_VARIABLE);
1405 findCmdTerm (token);
1408 * Statements can be optionally terminated in the case of
1409 * statement prior to a close curly brace as in the
1410 * document.write line below:
1412 * function checkForUpdate() {
1413 * if( 1==1 ) {
1414 * document.write("hello from checkForUpdate<br>")
1416 * return 1;
1419 if (isType (token, TOKEN_CLOSE_CURLY))
1420 is_terminated = FALSE;
1423 cleanUp:
1424 vStringCopy(token->scope, saveScope);
1425 deleteToken (name);
1426 deleteToken (secondary_name);
1427 vStringDelete(saveScope);
1429 return is_terminated;
1432 static boolean parseLine (tokenInfo *const token, boolean is_inside_class)
1434 boolean is_terminated = TRUE;
1436 * Detect the common statements, if, while, for, do, ...
1437 * This is necessary since the last statement within a block "{}"
1438 * can be optionally terminated.
1440 * If the statement is not terminated, we need to tell
1441 * the calling routine to prevent reading an additional token
1442 * looking for the end of the statement.
1445 if (isType(token, TOKEN_KEYWORD))
1447 switch (token->keyword)
1449 case KEYWORD_for:
1450 case KEYWORD_while:
1451 case KEYWORD_do:
1452 parseLoop (token);
1453 break;
1454 case KEYWORD_if:
1455 case KEYWORD_else:
1456 case KEYWORD_try:
1457 case KEYWORD_catch:
1458 case KEYWORD_finally:
1459 /* Common semantics */
1460 is_terminated = parseIf (token);
1461 break;
1462 case KEYWORD_switch:
1463 parseSwitch (token);
1464 break;
1465 default:
1466 parseStatement (token, is_inside_class);
1467 break;
1470 else
1473 * Special case where single line statements may not be
1474 * SEMICOLON terminated. parseBlock needs to know this
1475 * so that it does not read the next token.
1477 is_terminated = parseStatement (token, is_inside_class);
1479 return is_terminated;
1482 static void parseJsFile (tokenInfo *const token)
1486 readToken (token);
1488 if (isType(token, TOKEN_KEYWORD))
1490 switch (token->keyword)
1492 case KEYWORD_function: parseFunction (token); break;
1493 default: parseLine (token, FALSE); break;
1496 else
1498 parseLine (token, FALSE);
1500 } while (TRUE);
1503 static void initialize (const langType language)
1505 Assert (sizeof (JsKinds) / sizeof (JsKinds [0]) == JSTAG_COUNT);
1506 Lang_js = language;
1507 buildJsKeywordHash ();
1510 static void findJsTags (void)
1512 tokenInfo *const token = newToken ();
1513 exception_t exception;
1515 ClassNames = stringListNew ();
1516 FunctionNames = stringListNew ();
1518 exception = (exception_t) (setjmp (Exception));
1519 while (exception == ExceptionNone)
1520 parseJsFile (token);
1522 stringListDelete (ClassNames);
1523 stringListDelete (FunctionNames);
1524 ClassNames = NULL;
1525 FunctionNames = NULL;
1526 deleteToken (token);
1529 /* Create parser definition stucture */
1530 extern parserDefinition* JavaScriptParser (void)
1532 static const char *const extensions [] = { "js", NULL };
1533 parserDefinition *const def = parserNew ("JavaScript");
1534 def->extensions = extensions;
1536 * New definitions for parsing instead of regex
1538 def->kinds = JsKinds;
1539 def->kindCount = KIND_COUNT (JsKinds);
1540 def->parser = findJsTags;
1541 def->initialize = initialize;
1543 return def;
1545 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */