Remove lexer argument from STYLESET_FROM_MAPPING()
[geany-mirror.git] / tagmanager / js.c
blob347a1dcf7d1e4609400290cc6343e7470960a575
1 /*
2 * Copyright (c) 2003, Darren Hiebert
4 * This source code is released for free distribution under the terms of the
5 * GNU General Public License.
7 * This module contains functions for generating tags for JavaScript language
8 * files.
10 * This is a good reference for different forms of the function statement:
11 * http://www.permadi.com/tutorial/jsFunc/
12 * Another good reference:
13 * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide
17 * INCLUDE FILES
19 #include "general.h" /* must always come first */
20 #include <ctype.h> /* to define isalpha () */
21 #include <setjmp.h>
22 #include <mio/mio.h>
23 #ifdef DEBUG
24 #include <stdio.h>
25 #endif
27 #include "keyword.h"
28 #include "parse.h"
29 #include "read.h"
30 #include "main.h"
31 #include "vstring.h"
34 * MACROS
36 #define isType(token,t) (boolean) ((token)->type == (t))
37 #define isKeyword(token,k) (boolean) ((token)->keyword == (k))
40 * DATA DECLARATIONS
43 typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
46 * Tracks class and function names already created
48 static stringList *ClassNames;
49 static stringList *FunctionNames;
51 /* Used to specify type of keyword.
53 typedef enum eKeywordId {
54 KEYWORD_NONE = -1,
55 KEYWORD_function,
56 KEYWORD_capital_function,
57 KEYWORD_object,
58 KEYWORD_capital_object,
59 KEYWORD_prototype,
60 KEYWORD_var,
61 KEYWORD_new,
62 KEYWORD_this,
63 KEYWORD_for,
64 KEYWORD_while,
65 KEYWORD_do,
66 KEYWORD_if,
67 KEYWORD_else,
68 KEYWORD_switch,
69 KEYWORD_try,
70 KEYWORD_catch,
71 KEYWORD_finally
72 } keywordId;
74 /* Used to determine whether keyword is valid for the token language and
75 * what its ID is.
77 typedef struct sKeywordDesc {
78 const char *name;
79 keywordId id;
80 } keywordDesc;
82 typedef enum eTokenType {
83 TOKEN_UNDEFINED,
84 TOKEN_CHARACTER,
85 TOKEN_CLOSE_PAREN,
86 TOKEN_SEMICOLON,
87 TOKEN_COLON,
88 TOKEN_COMMA,
89 TOKEN_KEYWORD,
90 TOKEN_OPEN_PAREN,
91 TOKEN_OPERATOR,
92 TOKEN_IDENTIFIER,
93 TOKEN_STRING,
94 TOKEN_PERIOD,
95 TOKEN_OPEN_CURLY,
96 TOKEN_CLOSE_CURLY,
97 TOKEN_EQUAL_SIGN,
98 TOKEN_FORWARD_SLASH,
99 TOKEN_OPEN_SQUARE,
100 TOKEN_CLOSE_SQUARE
101 } tokenType;
103 typedef struct sTokenInfo {
104 tokenType type;
105 keywordId keyword;
106 vString * string;
107 vString * scope;
108 unsigned long lineNumber;
109 MIOPos filePosition;
110 int nestLevel;
111 boolean ignoreTag;
112 } tokenInfo;
115 * DATA DEFINITIONS
118 static langType Lang_js;
120 static jmp_buf Exception;
122 typedef enum {
123 JSTAG_FUNCTION,
124 JSTAG_CLASS,
125 JSTAG_METHOD,
126 JSTAG_PROPERTY,
127 JSTAG_VARIABLE,
128 JSTAG_COUNT
129 } jsKind;
131 static kindOption JsKinds [] = {
132 { TRUE, 'f', "function", "functions" },
133 { TRUE, 'c', "class", "classes" },
134 { TRUE, 'm', "method", "methods" },
135 { TRUE, 'p', "member", "properties" },
136 { TRUE, 'v', "variable", "global variables" }
139 static const keywordDesc JsKeywordTable [] = {
140 /* keyword keyword ID */
141 { "function", KEYWORD_function },
142 { "Function", KEYWORD_capital_function },
143 { "object", KEYWORD_object },
144 { "Object", KEYWORD_capital_object },
145 { "prototype", KEYWORD_prototype },
146 { "var", KEYWORD_var },
147 { "new", KEYWORD_new },
148 { "this", KEYWORD_this },
149 { "for", KEYWORD_for },
150 { "while", KEYWORD_while },
151 { "do", KEYWORD_do },
152 { "if", KEYWORD_if },
153 { "else", KEYWORD_else },
154 { "switch", KEYWORD_switch },
155 { "try", KEYWORD_try },
156 { "catch", KEYWORD_catch },
157 { "finally", KEYWORD_finally }
161 * FUNCTION DEFINITIONS
164 /* Recursive functions */
165 static void parseFunction (tokenInfo *const token);
166 static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent);
167 static boolean parseLine (tokenInfo *const token, boolean is_inside_class);
169 static boolean isIdentChar (const int c)
171 return (boolean)
172 (isalpha (c) || isdigit (c) || c == '$' ||
173 c == '@' || c == '_' || c == '#');
176 static void buildJsKeywordHash (void)
178 const size_t count = sizeof (JsKeywordTable) /
179 sizeof (JsKeywordTable [0]);
180 size_t i;
181 for (i = 0 ; i < count ; ++i)
183 const keywordDesc* const p = &JsKeywordTable [i];
184 addKeyword (p->name, Lang_js, (int) p->id);
188 static tokenInfo *newToken (void)
190 tokenInfo *const token = xMalloc (1, tokenInfo);
192 token->type = TOKEN_UNDEFINED;
193 token->keyword = KEYWORD_NONE;
194 token->string = vStringNew ();
195 token->scope = vStringNew ();
196 token->nestLevel = 0;
197 token->ignoreTag = FALSE;
198 token->lineNumber = getSourceLineNumber ();
199 token->filePosition = getInputFilePosition ();
201 return token;
204 static void deleteToken (tokenInfo *const token)
206 vStringDelete (token->string);
207 vStringDelete (token->scope);
208 eFree (token);
212 * Tag generation functions
215 static void makeConstTag (tokenInfo *const token, const jsKind kind)
217 if (JsKinds [kind].enabled && ! token->ignoreTag )
219 const char *const name = vStringValue (token->string);
220 tagEntryInfo e;
221 initTagEntry (&e, name);
223 e.lineNumber = token->lineNumber;
224 e.filePosition = token->filePosition;
225 e.kindName = JsKinds [kind].name;
226 e.kind = JsKinds [kind].letter;
228 makeTagEntry (&e);
232 static void makeJsTag (tokenInfo *const token, const jsKind kind)
234 vString * fulltag;
236 if (JsKinds [kind].enabled && ! token->ignoreTag )
239 * If a scope has been added to the token, change the token
240 * string to include the scope when making the tag.
242 if ( vStringLength(token->scope) > 0 )
244 fulltag = vStringNew ();
245 vStringCopy(fulltag, token->scope);
246 vStringCatS (fulltag, ".");
247 vStringCatS (fulltag, vStringValue(token->string));
248 vStringTerminate(fulltag);
249 vStringCopy(token->string, fulltag);
250 vStringDelete (fulltag);
252 makeConstTag (token, kind);
256 static void makeClassTag (tokenInfo *const token)
258 vString * fulltag;
260 if ( ! token->ignoreTag )
262 fulltag = vStringNew ();
263 if (vStringLength (token->scope) > 0)
265 vStringCopy(fulltag, token->scope);
266 vStringCatS (fulltag, ".");
267 vStringCatS (fulltag, vStringValue(token->string));
269 else
271 vStringCopy(fulltag, token->string);
273 vStringTerminate(fulltag);
274 if ( ! stringListHas(ClassNames, vStringValue (fulltag)) )
276 stringListAdd (ClassNames, vStringNewCopy (fulltag));
277 makeJsTag (token, JSTAG_CLASS);
279 vStringDelete (fulltag);
283 static void makeFunctionTag (tokenInfo *const token)
285 vString * fulltag;
287 if ( ! token->ignoreTag )
289 fulltag = vStringNew ();
290 if (vStringLength (token->scope) > 0)
292 vStringCopy(fulltag, token->scope);
293 vStringCatS (fulltag, ".");
294 vStringCatS (fulltag, vStringValue(token->string));
296 else
298 vStringCopy(fulltag, token->string);
300 vStringTerminate(fulltag);
301 if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) )
303 stringListAdd (FunctionNames, vStringNewCopy (fulltag));
304 makeJsTag (token, JSTAG_FUNCTION);
306 vStringDelete (fulltag);
311 * Parsing functions
314 static int skipToCharacter (const int c)
316 int d;
319 d = fileGetc ();
320 } while (d != EOF && d != c);
321 return d;
324 static void parseString (vString *const string, const int delimiter)
326 boolean end = FALSE;
327 while (! end)
329 int c = fileGetc ();
330 if (c == EOF)
331 end = TRUE;
332 else if (c == '\\')
334 c = fileGetc(); /* This maybe a ' or ". */
335 vStringPut(string, c);
337 else if (c == delimiter)
338 end = TRUE;
339 else
340 vStringPut (string, c);
342 vStringTerminate (string);
345 /* Read a C identifier beginning with "firstChar" and places it into
346 * "name".
348 static void parseIdentifier (vString *const string, const int firstChar)
350 int c = firstChar;
351 Assert (isIdentChar (c));
354 vStringPut (string, c);
355 c = fileGetc ();
356 } while (isIdentChar (c));
357 vStringTerminate (string);
358 if (!isspace (c))
359 fileUngetc (c); /* unget non-identifier character */
362 static keywordId analyzeToken (vString *const name)
364 vString *keyword = vStringNew ();
365 keywordId result;
366 vStringCopyToLower (keyword, name);
367 result = (keywordId) lookupKeyword (vStringValue (keyword), Lang_js);
368 vStringDelete (keyword);
369 return result;
372 static void readToken (tokenInfo *const token)
374 int c;
376 token->type = TOKEN_UNDEFINED;
377 token->keyword = KEYWORD_NONE;
378 vStringClear (token->string);
380 getNextChar:
383 c = fileGetc ();
384 token->lineNumber = getSourceLineNumber ();
385 token->filePosition = getInputFilePosition ();
387 while (c == '\t' || c == ' ' || c == '\n');
389 switch (c)
391 case EOF: longjmp (Exception, (int)ExceptionEOF); break;
392 case '(': token->type = TOKEN_OPEN_PAREN; break;
393 case ')': token->type = TOKEN_CLOSE_PAREN; break;
394 case ';': token->type = TOKEN_SEMICOLON; break;
395 case ',': token->type = TOKEN_COMMA; break;
396 case '.': token->type = TOKEN_PERIOD; break;
397 case ':': token->type = TOKEN_COLON; break;
398 case '{': token->type = TOKEN_OPEN_CURLY; break;
399 case '}': token->type = TOKEN_CLOSE_CURLY; break;
400 case '=': token->type = TOKEN_EQUAL_SIGN; break;
401 case '[': token->type = TOKEN_OPEN_SQUARE; break;
402 case ']': token->type = TOKEN_CLOSE_SQUARE; break;
404 case '\'':
405 case '"':
406 token->type = TOKEN_STRING;
407 parseString (token->string, c);
408 token->lineNumber = getSourceLineNumber ();
409 token->filePosition = getInputFilePosition ();
410 break;
412 case '\\':
413 c = fileGetc ();
414 if (c != '\\' && c != '"' && !isspace (c))
415 fileUngetc (c);
416 token->type = TOKEN_CHARACTER;
417 token->lineNumber = getSourceLineNumber ();
418 token->filePosition = getInputFilePosition ();
419 break;
421 case '/':
423 int d = fileGetc ();
424 if ( (d != '*') && /* is this the start of a comment? */
425 (d != '/') ) /* is a one line comment? */
427 token->type = TOKEN_FORWARD_SLASH;
428 fileUngetc (d);
430 else
432 if (d == '*')
436 skipToCharacter ('*');
437 c = fileGetc ();
438 if (c == '/')
439 break;
440 else
441 fileUngetc (c);
442 } while (c != EOF && c != '\0');
443 goto getNextChar;
445 else if (d == '/') /* is this the start of a comment? */
447 skipToCharacter ('\n');
448 goto getNextChar;
451 break;
454 default:
455 if (! isIdentChar (c))
456 token->type = TOKEN_UNDEFINED;
457 else
459 parseIdentifier (token->string, c);
460 token->lineNumber = getSourceLineNumber ();
461 token->filePosition = getInputFilePosition ();
462 token->keyword = analyzeToken (token->string);
463 if (isKeyword (token, KEYWORD_NONE))
464 token->type = TOKEN_IDENTIFIER;
465 else
466 token->type = TOKEN_KEYWORD;
468 break;
472 static void copyToken (tokenInfo *const dest, tokenInfo *const src)
474 dest->nestLevel = src->nestLevel;
475 dest->lineNumber = src->lineNumber;
476 dest->filePosition = src->filePosition;
477 dest->type = src->type;
478 dest->keyword = src->keyword;
479 vStringCopy(dest->string, src->string);
480 vStringCopy(dest->scope, src->scope);
484 * Token parsing functions
487 static void skipArgumentList (tokenInfo *const token)
489 int nest_level = 0;
492 * Other databases can have arguments with fully declared
493 * datatypes:
494 * ( name varchar(30), text binary(10) )
495 * So we must check for nested open and closing parantheses
498 if (isType (token, TOKEN_OPEN_PAREN)) /* arguments? */
500 nest_level++;
501 while (! (isType (token, TOKEN_CLOSE_PAREN) && (nest_level == 0)))
503 readToken (token);
504 if (isType (token, TOKEN_OPEN_PAREN))
506 nest_level++;
508 if (isType (token, TOKEN_CLOSE_PAREN))
510 if (nest_level > 0)
512 nest_level--;
516 readToken (token);
520 static void skipArrayList (tokenInfo *const token)
522 int nest_level = 0;
525 * Handle square brackets
526 * var name[1]
527 * So we must check for nested open and closing square brackets
530 if (isType (token, TOKEN_OPEN_SQUARE)) /* arguments? */
532 nest_level++;
533 while (! (isType (token, TOKEN_CLOSE_SQUARE) && (nest_level == 0)))
535 readToken (token);
536 if (isType (token, TOKEN_OPEN_SQUARE))
538 nest_level++;
540 if (isType (token, TOKEN_CLOSE_SQUARE))
542 if (nest_level > 0)
544 nest_level--;
548 readToken (token);
552 static void addContext (tokenInfo* const parent, const tokenInfo* const child)
554 if (vStringLength (parent->string) > 0)
556 vStringCatS (parent->string, ".");
558 vStringCatS (parent->string, vStringValue(child->string));
559 vStringTerminate(parent->string);
562 static void addToScope (tokenInfo* const token, vString* const extra)
564 if (vStringLength (token->scope) > 0)
566 vStringCatS (token->scope, ".");
568 vStringCatS (token->scope, vStringValue(extra));
569 vStringTerminate(token->scope);
573 * Scanning functions
576 static void findCmdTerm (tokenInfo *const token)
579 * Read until we find either a semicolon or closing brace.
580 * Any nested braces will be handled within.
582 while (! ( isType (token, TOKEN_SEMICOLON) ||
583 isType (token, TOKEN_CLOSE_CURLY) ) )
585 /* Handle nested blocks */
586 if ( isType (token, TOKEN_OPEN_CURLY))
588 parseBlock (token, token);
590 else if ( isType (token, TOKEN_OPEN_PAREN) )
592 skipArgumentList(token);
594 else
596 readToken (token);
601 static void parseSwitch (tokenInfo *const token)
604 * switch (expression){
605 * case value1:
606 * statement;
607 * break;
608 * case value2:
609 * statement;
610 * break;
611 * default : statement;
615 readToken (token);
617 if (isType (token, TOKEN_OPEN_PAREN))
620 * Handle nameless functions, these will only
621 * be considered methods.
623 skipArgumentList(token);
626 if (isType (token, TOKEN_OPEN_CURLY))
629 * This will be either a function or a class.
630 * We can only determine this by checking the body
631 * of the function. If we find a "this." we know
632 * it is a class, otherwise it is a function.
634 parseBlock (token, token);
639 static void parseLoop (tokenInfo *const token)
642 * Handles these statements
643 * for (x=0; x<3; x++)
644 * document.write("This text is repeated three times<br>");
646 * for (x=0; x<3; x++)
648 * document.write("This text is repeated three times<br>");
651 * while (number<5){
652 * document.write(number+"<br>");
653 * number++;
656 * do{
657 * document.write(number+"<br>");
658 * number++;
660 * while (number<5);
663 if (isKeyword (token, KEYWORD_for) || isKeyword (token, KEYWORD_while))
665 readToken(token);
667 if (isType (token, TOKEN_OPEN_PAREN))
670 * Handle nameless functions, these will only
671 * be considered methods.
673 skipArgumentList(token);
676 if (isType (token, TOKEN_OPEN_CURLY))
679 * This will be either a function or a class.
680 * We can only determine this by checking the body
681 * of the function. If we find a "this." we know
682 * it is a class, otherwise it is a function.
684 parseBlock (token, token);
686 else
688 parseLine(token, FALSE);
691 else if (isKeyword (token, KEYWORD_do))
693 readToken(token);
695 if (isType (token, TOKEN_OPEN_CURLY))
698 * This will be either a function or a class.
699 * We can only determine this by checking the body
700 * of the function. If we find a "this." we know
701 * it is a class, otherwise it is a function.
703 parseBlock (token, token);
705 else
707 parseLine(token, FALSE);
710 readToken(token);
712 if (isKeyword (token, KEYWORD_while))
714 readToken(token);
716 if (isType (token, TOKEN_OPEN_PAREN))
719 * Handle nameless functions, these will only
720 * be considered methods.
722 skipArgumentList(token);
728 static boolean parseIf (tokenInfo *const token)
730 boolean read_next_token = TRUE;
732 * If statements have two forms
733 * if ( ... )
734 * one line;
736 * if ( ... )
737 * statement;
738 * else
739 * statement
741 * if ( ... ) {
742 * multiple;
743 * statements;
747 * if ( ... ) {
748 * return elem
751 * This example if correctly written, but the
752 * else contains only 1 statement without a terminator
753 * since the function finishes with the closing brace.
755 * function a(flag){
756 * if(flag)
757 * test(1);
758 * else
759 * test(2)
762 * TODO: Deal with statements that can optional end
763 * without a semi-colon. Currently this messes up
764 * the parsing of blocks.
765 * Need to somehow detect this has happened, and either
766 * backup a token, or skip reading the next token if
767 * that is possible from all code locations.
771 readToken (token);
773 if (isKeyword (token, KEYWORD_if))
776 * Check for an "else if" and consume the "if"
778 readToken (token);
781 if (isType (token, TOKEN_OPEN_PAREN))
784 * Handle nameless functions, these will only
785 * be considered methods.
787 skipArgumentList(token);
790 if (isType (token, TOKEN_OPEN_CURLY))
793 * This will be either a function or a class.
794 * We can only determine this by checking the body
795 * of the function. If we find a "this." we know
796 * it is a class, otherwise it is a function.
798 parseBlock (token, token);
800 else
802 findCmdTerm (token);
805 * The IF could be followed by an ELSE statement.
806 * This too could have two formats, a curly braced
807 * multiline section, or another single line.
810 if (isType (token, TOKEN_CLOSE_CURLY))
813 * This statement did not have a line terminator.
815 read_next_token = FALSE;
817 else
819 readToken (token);
821 if (isType (token, TOKEN_CLOSE_CURLY))
824 * This statement did not have a line terminator.
826 read_next_token = FALSE;
828 else
830 if (isKeyword (token, KEYWORD_else))
831 read_next_token = parseIf (token);
835 return read_next_token;
838 static void parseFunction (tokenInfo *const token)
840 tokenInfo *const name = newToken ();
841 boolean is_class = FALSE;
844 * This deals with these formats
845 * function validFunctionTwo(a,b) {}
848 readToken (name);
849 /* Add scope in case this is an INNER function */
850 addToScope(name, token->scope);
852 readToken (token);
853 if (isType (token, TOKEN_PERIOD))
857 readToken (token);
858 if ( isKeyword(token, KEYWORD_NONE) )
860 addContext (name, token);
861 readToken (token);
863 } while (isType (token, TOKEN_PERIOD));
866 if ( isType (token, TOKEN_OPEN_PAREN) )
867 skipArgumentList(token);
869 if ( isType (token, TOKEN_OPEN_CURLY) )
871 is_class = parseBlock (token, name);
872 if ( is_class )
873 makeClassTag (name);
874 else
875 makeFunctionTag (name);
878 findCmdTerm (token);
880 deleteToken (name);
883 static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent)
885 boolean is_class = FALSE;
886 boolean read_next_token = TRUE;
887 vString * saveScope = vStringNew ();
889 token->nestLevel++;
891 * Make this routine a bit more forgiving.
892 * If called on an open_curly advance it
894 if ( isType (token, TOKEN_OPEN_CURLY) &&
895 isKeyword(token, KEYWORD_NONE) )
896 readToken(token);
898 if (! isType (token, TOKEN_CLOSE_CURLY))
901 * Read until we find the closing brace,
902 * any nested braces will be handled within
906 read_next_token = TRUE;
907 if (isKeyword (token, KEYWORD_this))
910 * Means we are inside a class and have found
911 * a class, not a function
913 is_class = TRUE;
914 vStringCopy(saveScope, token->scope);
915 addToScope (token, parent->string);
918 * Ignore the remainder of the line
919 * findCmdTerm(token);
921 parseLine (token, is_class);
923 vStringCopy(token->scope, saveScope);
925 else if (isKeyword (token, KEYWORD_var))
928 * Potentially we have found an inner function.
929 * Set something to indicate the scope
931 vStringCopy(saveScope, token->scope);
932 addToScope (token, parent->string);
933 parseLine (token, is_class);
934 vStringCopy(token->scope, saveScope);
936 else if (isKeyword (token, KEYWORD_function))
938 vStringCopy(saveScope, token->scope);
939 addToScope (token, parent->string);
940 parseFunction (token);
941 vStringCopy(token->scope, saveScope);
943 else if (isType (token, TOKEN_OPEN_CURLY))
945 /* Handle nested blocks */
946 parseBlock (token, parent);
948 else
951 * It is possible for a line to have no terminator
952 * if the following line is a closing brace.
953 * parseLine will detect this case and indicate
954 * whether we should read an additional token.
956 read_next_token = parseLine (token, is_class);
960 * Always read a new token unless we find a statement without
961 * a ending terminator
963 if( read_next_token )
964 readToken(token);
967 * If we find a statement without a terminator consider the
968 * block finished, otherwise the stack will be off by one.
970 } while (! isType (token, TOKEN_CLOSE_CURLY) && read_next_token );
973 vStringDelete(saveScope);
974 token->nestLevel--;
976 return is_class;
979 static boolean parseMethods (tokenInfo *const token, tokenInfo *const class)
981 tokenInfo *const name = newToken ();
982 boolean has_methods = FALSE;
985 * This deals with these formats
986 * validProperty : 2,
987 * validMethod : function(a,b) {}
988 * 'validMethod2' : function(a,b) {}
989 * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false}
994 readToken (token);
995 if (isType (token, TOKEN_CLOSE_CURLY))
998 * This was most likely a variable declaration of a hash table.
999 * indicate there were no methods and return.
1001 has_methods = FALSE;
1002 goto cleanUp;
1005 if (isType (token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE))
1007 copyToken(name, token);
1009 readToken (token);
1010 if ( isType (token, TOKEN_COLON) )
1012 readToken (token);
1013 if ( isKeyword (token, KEYWORD_function) )
1015 readToken (token);
1016 if ( isType (token, TOKEN_OPEN_PAREN) )
1018 skipArgumentList(token);
1021 if (isType (token, TOKEN_OPEN_CURLY))
1023 has_methods = TRUE;
1024 addToScope (name, class->string);
1025 makeJsTag (name, JSTAG_METHOD);
1026 parseBlock (token, name);
1029 * Read to the closing curly, check next
1030 * token, if a comma, we must loop again
1032 readToken (token);
1035 else
1037 has_methods = TRUE;
1038 addToScope (name, class->string);
1039 makeJsTag (name, JSTAG_PROPERTY);
1042 * Read the next token, if a comma
1043 * we must loop again
1045 readToken (token);
1049 } while ( isType(token, TOKEN_COMMA) );
1051 findCmdTerm (token);
1053 cleanUp:
1054 deleteToken (name);
1056 return has_methods;
1059 static boolean parseStatement (tokenInfo *const token, boolean is_inside_class)
1061 tokenInfo *const name = newToken ();
1062 tokenInfo *const secondary_name = newToken ();
1063 vString * saveScope = vStringNew ();
1064 boolean is_class = FALSE;
1065 boolean is_terminated = TRUE;
1066 boolean is_global = FALSE;
1067 boolean has_methods = FALSE;
1068 vString * fulltag;
1070 vStringClear(saveScope);
1072 * Functions can be named or unnamed.
1073 * This deals with these formats:
1074 * Function
1075 * validFunctionOne = function(a,b) {}
1076 * testlib.validFunctionFive = function(a,b) {}
1077 * var innerThree = function(a,b) {}
1078 * var innerFour = (a,b) {}
1079 * var D2 = secondary_fcn_name(a,b) {}
1080 * var D3 = new Function("a", "b", "return a+b;");
1081 * Class
1082 * testlib.extras.ValidClassOne = function(a,b) {
1083 * this.a = a;
1085 * Class Methods
1086 * testlib.extras.ValidClassOne.prototype = {
1087 * 'validMethodOne' : function(a,b) {},
1088 * 'validMethodTwo' : function(a,b) {}
1090 * ValidClassTwo = function ()
1092 * this.validMethodThree = function() {}
1093 * // unnamed method
1094 * this.validMethodFour = () {}
1096 * Database.prototype.validMethodThree = Database_getTodaysDate;
1099 if ( is_inside_class )
1100 is_class = TRUE;
1102 * var can preceed an inner function
1104 if ( isKeyword(token, KEYWORD_var) )
1107 * Only create variables for global scope
1109 if ( token->nestLevel == 0 )
1111 is_global = TRUE;
1113 readToken(token);
1116 if ( isKeyword(token, KEYWORD_this) )
1118 readToken(token);
1119 if (isType (token, TOKEN_PERIOD))
1121 readToken(token);
1125 copyToken(name, token);
1127 while (! isType (token, TOKEN_CLOSE_CURLY) &&
1128 ! isType (token, TOKEN_SEMICOLON) &&
1129 ! isType (token, TOKEN_EQUAL_SIGN) )
1131 /* Potentially the name of the function */
1132 readToken (token);
1133 if (isType (token, TOKEN_PERIOD))
1136 * Cannot be a global variable is it has dot references in the name
1138 is_global = FALSE;
1141 readToken (token);
1142 if ( isKeyword(token, KEYWORD_NONE) )
1144 if ( is_class )
1146 vStringCopy(saveScope, token->scope);
1147 addToScope(token, name->string);
1149 else
1150 addContext (name, token);
1152 else if ( isKeyword(token, KEYWORD_prototype) )
1155 * When we reach the "prototype" tag, we infer:
1156 * "BindAgent" is a class
1157 * "build" is a method
1159 * function BindAgent( repeatableIdName, newParentIdName ) {
1162 * CASE 1
1163 * Specified function name: "build"
1164 * BindAgent.prototype.build = function( mode ) {
1165 * ignore everything within this function
1168 * CASE 2
1169 * Prototype listing
1170 * ValidClassOne.prototype = {
1171 * 'validMethodOne' : function(a,b) {},
1172 * 'validMethodTwo' : function(a,b) {}
1176 makeClassTag (name);
1177 is_class = TRUE;
1180 * There should a ".function_name" next.
1182 readToken (token);
1183 if (isType (token, TOKEN_PERIOD))
1186 * Handle CASE 1
1188 readToken (token);
1189 if ( isKeyword(token, KEYWORD_NONE) )
1191 vStringCopy(saveScope, token->scope);
1192 addToScope(token, name->string);
1194 makeJsTag (token, JSTAG_METHOD);
1196 * We can read until the end of the block / statement.
1197 * We need to correctly parse any nested blocks, but
1198 * we do NOT want to create any tags based on what is
1199 * within the blocks.
1201 token->ignoreTag = TRUE;
1203 * Find to the end of the statement
1205 findCmdTerm (token);
1206 token->ignoreTag = FALSE;
1207 is_terminated = TRUE;
1208 goto cleanUp;
1211 else if (isType (token, TOKEN_EQUAL_SIGN))
1213 readToken (token);
1214 if (isType (token, TOKEN_OPEN_CURLY))
1217 * Handle CASE 2
1219 * Creates tags for each of these class methods
1220 * ValidClassOne.prototype = {
1221 * 'validMethodOne' : function(a,b) {},
1222 * 'validMethodTwo' : function(a,b) {}
1225 parseMethods(token, name);
1227 * Find to the end of the statement
1229 findCmdTerm (token);
1230 token->ignoreTag = FALSE;
1231 is_terminated = TRUE;
1232 goto cleanUp;
1236 readToken (token);
1237 } while (isType (token, TOKEN_PERIOD));
1240 if ( isType (token, TOKEN_OPEN_PAREN) )
1241 skipArgumentList(token);
1243 if ( isType (token, TOKEN_OPEN_SQUARE) )
1244 skipArrayList(token);
1247 if ( isType (token, TOKEN_OPEN_CURLY) )
1249 is_class = parseBlock (token, name);
1254 if ( isType (token, TOKEN_CLOSE_CURLY) )
1257 * Reaching this section without having
1258 * processed an open curly brace indicates
1259 * the statement is most likely not terminated.
1261 is_terminated = FALSE;
1262 goto cleanUp;
1265 if ( isType (token, TOKEN_SEMICOLON) )
1268 * Only create variables for global scope
1270 if ( token->nestLevel == 0 && is_global )
1273 * Handles this syntax:
1274 * var g_var2;
1276 if (isType (token, TOKEN_SEMICOLON))
1277 makeJsTag (name, JSTAG_VARIABLE);
1280 * Statement has ended.
1281 * This deals with calls to functions, like:
1282 * alert(..);
1284 goto cleanUp;
1287 if ( isType (token, TOKEN_EQUAL_SIGN) )
1289 readToken (token);
1291 if ( isKeyword (token, KEYWORD_function) )
1293 readToken (token);
1295 if ( isKeyword (token, KEYWORD_NONE) &&
1296 ! isType (token, TOKEN_OPEN_PAREN) )
1299 * Functions of this format:
1300 * var D2A = function theAdd(a, b)
1302 * return a+b;
1304 * Are really two separate defined functions and
1305 * can be referenced in two ways:
1306 * alert( D2A(1,2) ); // produces 3
1307 * alert( theAdd(1,2) ); // also produces 3
1308 * So it must have two tags:
1309 * D2A
1310 * theAdd
1311 * Save the reference to the name for later use, once
1312 * we have established this is a valid function we will
1313 * create the secondary reference to it.
1315 copyToken(secondary_name, token);
1316 readToken (token);
1319 if ( isType (token, TOKEN_OPEN_PAREN) )
1320 skipArgumentList(token);
1322 if (isType (token, TOKEN_OPEN_CURLY))
1325 * This will be either a function or a class.
1326 * We can only determine this by checking the body
1327 * of the function. If we find a "this." we know
1328 * it is a class, otherwise it is a function.
1330 if ( is_inside_class )
1332 makeJsTag (name, JSTAG_METHOD);
1333 if ( vStringLength(secondary_name->string) > 0 )
1334 makeFunctionTag (secondary_name);
1335 parseBlock (token, name);
1337 else
1339 is_class = parseBlock (token, name);
1340 if ( is_class )
1341 makeClassTag (name);
1342 else
1343 makeFunctionTag (name);
1345 if ( vStringLength(secondary_name->string) > 0 )
1346 makeFunctionTag (secondary_name);
1349 * Find to the end of the statement
1351 goto cleanUp;
1355 else if (isType (token, TOKEN_OPEN_PAREN))
1358 * Handle nameless functions
1359 * this.method_name = () {}
1361 skipArgumentList(token);
1363 if (isType (token, TOKEN_OPEN_CURLY))
1366 * Nameless functions are only setup as methods.
1368 makeJsTag (name, JSTAG_METHOD);
1369 parseBlock (token, name);
1372 else if (isType (token, TOKEN_OPEN_CURLY))
1375 * Creates tags for each of these class methods
1376 * ValidClassOne.prototype = {
1377 * 'validMethodOne' : function(a,b) {},
1378 * 'validMethodTwo' : function(a,b) {}
1380 * Or checks if this is a hash variable.
1381 * var z = {};
1383 has_methods = parseMethods(token, name);
1384 if ( ! has_methods )
1387 * Only create variables for global scope
1389 if ( token->nestLevel == 0 && is_global )
1392 * A pointer can be created to the function.
1393 * If we recognize the function/class name ignore the variable.
1394 * This format looks identical to a variable definition.
1395 * A variable defined outside of a block is considered
1396 * a global variable:
1397 * var g_var1 = 1;
1398 * var g_var2;
1399 * This is not a global variable:
1400 * var g_var = function;
1401 * This is a global variable:
1402 * var g_var = different_var_name;
1404 fulltag = vStringNew ();
1405 if (vStringLength (token->scope) > 0)
1407 vStringCopy(fulltag, token->scope);
1408 vStringCatS (fulltag, ".");
1409 vStringCatS (fulltag, vStringValue(token->string));
1411 else
1413 vStringCopy(fulltag, token->string);
1415 vStringTerminate(fulltag);
1416 if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) &&
1417 ! stringListHas(ClassNames, vStringValue (fulltag)) )
1419 readToken (token);
1420 if ( ! isType (token, TOKEN_SEMICOLON))
1421 findCmdTerm (token);
1422 if (isType (token, TOKEN_SEMICOLON))
1423 makeJsTag (name, JSTAG_VARIABLE);
1425 vStringDelete (fulltag);
1428 if (isType (token, TOKEN_CLOSE_CURLY))
1431 * Assume the closing parantheses terminates
1432 * this statements.
1434 is_terminated = TRUE;
1437 else if (isKeyword (token, KEYWORD_new))
1439 readToken (token);
1440 if ( isKeyword (token, KEYWORD_function) ||
1441 isKeyword (token, KEYWORD_capital_function) ||
1442 isKeyword (token, KEYWORD_object) ||
1443 isKeyword (token, KEYWORD_capital_object) )
1445 if ( isKeyword (token, KEYWORD_object) ||
1446 isKeyword (token, KEYWORD_capital_object) )
1447 is_class = TRUE;
1449 readToken (token);
1450 if ( isType (token, TOKEN_OPEN_PAREN) )
1451 skipArgumentList(token);
1453 if (isType (token, TOKEN_SEMICOLON))
1455 if ( token->nestLevel == 0 )
1457 if ( is_class )
1459 makeClassTag (name);
1460 } else {
1461 makeFunctionTag (name);
1467 else if (isKeyword (token, KEYWORD_NONE))
1470 * Only create variables for global scope
1472 if ( token->nestLevel == 0 && is_global )
1475 * A pointer can be created to the function.
1476 * If we recognize the function/class name ignore the variable.
1477 * This format looks identical to a variable definition.
1478 * A variable defined outside of a block is considered
1479 * a global variable:
1480 * var g_var1 = 1;
1481 * var g_var2;
1482 * This is not a global variable:
1483 * var g_var = function;
1484 * This is a global variable:
1485 * var g_var = different_var_name;
1487 fulltag = vStringNew ();
1488 if (vStringLength (token->scope) > 0)
1490 vStringCopy(fulltag, token->scope);
1491 vStringCatS (fulltag, ".");
1492 vStringCatS (fulltag, vStringValue(token->string));
1494 else
1496 vStringCopy(fulltag, token->string);
1498 vStringTerminate(fulltag);
1499 if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) &&
1500 ! stringListHas(ClassNames, vStringValue (fulltag)) )
1502 findCmdTerm (token);
1503 if (isType (token, TOKEN_SEMICOLON))
1504 makeJsTag (name, JSTAG_VARIABLE);
1506 vStringDelete (fulltag);
1510 findCmdTerm (token);
1513 * Statements can be optionally terminated in the case of
1514 * statement prior to a close curly brace as in the
1515 * document.write line below:
1517 * function checkForUpdate() {
1518 * if( 1==1 ) {
1519 * document.write("hello from checkForUpdate<br>")
1521 * return 1;
1524 if ( ! is_terminated && isType (token, TOKEN_CLOSE_CURLY))
1525 is_terminated = FALSE;
1528 cleanUp:
1529 vStringCopy(token->scope, saveScope);
1530 deleteToken (name);
1531 deleteToken (secondary_name);
1532 vStringDelete(saveScope);
1534 return is_terminated;
1537 static boolean parseLine (tokenInfo *const token, boolean is_inside_class)
1539 boolean is_terminated = TRUE;
1541 * Detect the common statements, if, while, for, do, ...
1542 * This is necessary since the last statement within a block "{}"
1543 * can be optionally terminated.
1545 * If the statement is not terminated, we need to tell
1546 * the calling routine to prevent reading an additional token
1547 * looking for the end of the statement.
1550 if (isType(token, TOKEN_KEYWORD))
1552 switch (token->keyword)
1554 case KEYWORD_for:
1555 case KEYWORD_while:
1556 case KEYWORD_do:
1557 parseLoop (token);
1558 break;
1559 case KEYWORD_if:
1560 case KEYWORD_else:
1561 case KEYWORD_try:
1562 case KEYWORD_catch:
1563 case KEYWORD_finally:
1564 /* Common semantics */
1565 is_terminated = parseIf (token);
1566 break;
1567 case KEYWORD_switch:
1568 parseSwitch (token);
1569 break;
1570 default:
1571 parseStatement (token, is_inside_class);
1572 break;
1575 else
1578 * Special case where single line statements may not be
1579 * SEMICOLON terminated. parseBlock needs to know this
1580 * so that it does not read the next token.
1582 is_terminated = parseStatement (token, is_inside_class);
1584 return is_terminated;
1587 static void parseJsFile (tokenInfo *const token)
1591 readToken (token);
1593 if (isType(token, TOKEN_KEYWORD))
1595 switch (token->keyword)
1597 case KEYWORD_function: parseFunction (token); break;
1598 default: parseLine (token, FALSE); break;
1601 else
1603 parseLine (token, FALSE);
1605 } while (TRUE);
1608 static void initialize (const langType language)
1610 Assert (sizeof (JsKinds) / sizeof (JsKinds [0]) == JSTAG_COUNT);
1611 Lang_js = language;
1612 buildJsKeywordHash ();
1615 static void findJsTags (void)
1617 tokenInfo *const token = newToken ();
1618 exception_t exception;
1620 ClassNames = stringListNew ();
1621 FunctionNames = stringListNew ();
1623 exception = (exception_t) (setjmp (Exception));
1624 while (exception == ExceptionNone)
1625 parseJsFile (token);
1627 stringListDelete (ClassNames);
1628 stringListDelete (FunctionNames);
1629 ClassNames = NULL;
1630 FunctionNames = NULL;
1631 deleteToken (token);
1634 /* Create parser definition stucture */
1635 extern parserDefinition* JavaScriptParser (void)
1637 static const char *const extensions [] = { "js", NULL };
1638 parserDefinition *const def = parserNew ("JavaScript");
1639 def->extensions = extensions;
1641 * New definitions for parsing instead of regex
1643 def->kinds = JsKinds;
1644 def->kindCount = KIND_COUNT (JsKinds);
1645 def->parser = findJsTags;
1646 def->initialize = initialize;
1648 return def;
1650 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */