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
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
19 #include "general.h" /* must always come first */
20 #include <ctype.h> /* to define isalpha () */
36 #define isType(token,t) (boolean) ((token)->type == (t))
37 #define isKeyword(token,k) (boolean) ((token)->keyword == (k))
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
{
56 KEYWORD_capital_function
,
58 KEYWORD_capital_object
,
74 /* Used to determine whether keyword is valid for the token language and
77 typedef struct sKeywordDesc
{
82 typedef enum eTokenType
{
103 typedef struct sTokenInfo
{
108 unsigned long lineNumber
;
118 static langType Lang_js
;
120 static jmp_buf Exception
;
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
)
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]);
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 ();
204 static void deleteToken (tokenInfo
*const token
)
206 vStringDelete (token
->string
);
207 vStringDelete (token
->scope
);
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
);
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
;
232 static void makeJsTag (tokenInfo
*const token
, const jsKind kind
)
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
)
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
));
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
)
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
));
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
);
314 static int skipToCharacter (const int c
)
320 } while (d
!= EOF
&& d
!= c
);
324 static void parseString (vString
*const string
, const int delimiter
)
334 c
= fileGetc(); /* This maybe a ' or ". */
335 vStringPut(string
, c
);
337 else if (c
== delimiter
)
340 vStringPut (string
, c
);
342 vStringTerminate (string
);
345 /* Read a C identifier beginning with "firstChar" and places it into
348 static void parseIdentifier (vString
*const string
, const int firstChar
)
351 Assert (isIdentChar (c
));
354 vStringPut (string
, c
);
356 } while (isIdentChar (c
));
357 vStringTerminate (string
);
359 fileUngetc (c
); /* unget non-identifier character */
362 static keywordId
analyzeToken (vString
*const name
)
364 vString
*keyword
= vStringNew ();
366 vStringCopyToLower (keyword
, name
);
367 result
= (keywordId
) lookupKeyword (vStringValue (keyword
), Lang_js
);
368 vStringDelete (keyword
);
372 static void readToken (tokenInfo
*const token
)
376 token
->type
= TOKEN_UNDEFINED
;
377 token
->keyword
= KEYWORD_NONE
;
378 vStringClear (token
->string
);
384 token
->lineNumber
= getSourceLineNumber ();
385 token
->filePosition
= getInputFilePosition ();
387 while (c
== '\t' || c
== ' ' || c
== '\n');
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;
406 token
->type
= TOKEN_STRING
;
407 parseString (token
->string
, c
);
408 token
->lineNumber
= getSourceLineNumber ();
409 token
->filePosition
= getInputFilePosition ();
414 if (c
!= '\\' && c
!= '"' && !isspace (c
))
416 token
->type
= TOKEN_CHARACTER
;
417 token
->lineNumber
= getSourceLineNumber ();
418 token
->filePosition
= getInputFilePosition ();
424 if ( (d
!= '*') && /* is this the start of a comment? */
425 (d
!= '/') ) /* is a one line comment? */
427 token
->type
= TOKEN_FORWARD_SLASH
;
436 skipToCharacter ('*');
442 } while (c
!= EOF
&& c
!= '\0');
445 else if (d
== '/') /* is this the start of a comment? */
447 skipToCharacter ('\n');
455 if (! isIdentChar (c
))
456 token
->type
= TOKEN_UNDEFINED
;
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
;
466 token
->type
= TOKEN_KEYWORD
;
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
)
492 * Other databases can have arguments with fully declared
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? */
501 while (! (isType (token
, TOKEN_CLOSE_PAREN
) && (nest_level
== 0)))
504 if (isType (token
, TOKEN_OPEN_PAREN
))
508 if (isType (token
, TOKEN_CLOSE_PAREN
))
520 static void skipArrayList (tokenInfo
*const token
)
525 * Handle square brackets
527 * So we must check for nested open and closing square brackets
530 if (isType (token
, TOKEN_OPEN_SQUARE
)) /* arguments? */
533 while (! (isType (token
, TOKEN_CLOSE_SQUARE
) && (nest_level
== 0)))
536 if (isType (token
, TOKEN_OPEN_SQUARE
))
540 if (isType (token
, TOKEN_CLOSE_SQUARE
))
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
);
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
);
601 static void parseSwitch (tokenInfo
*const token
)
604 * switch (expression){
611 * default : statement;
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>");
652 * document.write(number+"<br>");
657 * document.write(number+"<br>");
663 if (isKeyword (token
, KEYWORD_for
) || isKeyword (token
, KEYWORD_while
))
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
);
688 parseLine(token
, FALSE
);
691 else if (isKeyword (token
, KEYWORD_do
))
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
);
707 parseLine(token
, FALSE
);
712 if (isKeyword (token
, KEYWORD_while
))
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
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.
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.
773 if (isKeyword (token
, KEYWORD_if
))
776 * Check for an "else if" and consume the "if"
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
);
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
;
821 if (isType (token
, TOKEN_CLOSE_CURLY
))
824 * This statement did not have a line terminator.
826 read_next_token
= FALSE
;
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) {}
849 /* Add scope in case this is an INNER function */
850 addToScope(name
, token
->scope
);
853 if (isType (token
, TOKEN_PERIOD
))
858 if ( isKeyword(token
, KEYWORD_NONE
) )
860 addContext (name
, 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
);
875 makeFunctionTag (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 ();
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
) )
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
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
);
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
)
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
);
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
987 * validMethod : function(a,b) {}
988 * 'validMethod2' : function(a,b) {}
989 * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false}
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
;
1005 if (isType (token
, TOKEN_STRING
) || isKeyword(token
, KEYWORD_NONE
))
1007 copyToken(name
, token
);
1010 if ( isType (token
, TOKEN_COLON
) )
1013 if ( isKeyword (token
, KEYWORD_function
) )
1016 if ( isType (token
, TOKEN_OPEN_PAREN
) )
1018 skipArgumentList(token
);
1021 if (isType (token
, TOKEN_OPEN_CURLY
))
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
1038 addToScope (name
, class->string
);
1039 makeJsTag (name
, JSTAG_PROPERTY
);
1042 * Read the next token, if a comma
1043 * we must loop again
1049 } while ( isType(token
, TOKEN_COMMA
) );
1051 findCmdTerm (token
);
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
;
1070 vStringClear(saveScope
);
1072 * Functions can be named or unnamed.
1073 * This deals with these formats:
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;");
1082 * testlib.extras.ValidClassOne = function(a,b) {
1086 * testlib.extras.ValidClassOne.prototype = {
1087 * 'validMethodOne' : function(a,b) {},
1088 * 'validMethodTwo' : function(a,b) {}
1090 * ValidClassTwo = function ()
1092 * this.validMethodThree = function() {}
1094 * this.validMethodFour = () {}
1096 * Database.prototype.validMethodThree = Database_getTodaysDate;
1099 if ( is_inside_class
)
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 )
1116 if ( isKeyword(token
, KEYWORD_this
) )
1119 if (isType (token
, TOKEN_PERIOD
))
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 */
1133 if (isType (token
, TOKEN_PERIOD
))
1136 * Cannot be a global variable is it has dot references in the name
1142 if ( isKeyword(token
, KEYWORD_NONE
) )
1146 vStringCopy(saveScope
, token
->scope
);
1147 addToScope(token
, name
->string
);
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 ) {
1163 * Specified function name: "build"
1164 * BindAgent.prototype.build = function( mode ) {
1165 * ignore everything within this function
1170 * ValidClassOne.prototype = {
1171 * 'validMethodOne' : function(a,b) {},
1172 * 'validMethodTwo' : function(a,b) {}
1176 makeClassTag (name
);
1180 * There should a ".function_name" next.
1183 if (isType (token
, TOKEN_PERIOD
))
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
;
1211 else if (isType (token
, TOKEN_EQUAL_SIGN
))
1214 if (isType (token
, TOKEN_OPEN_CURLY
))
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
;
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
;
1265 if ( isType (token
, TOKEN_SEMICOLON
) )
1268 * Only create variables for global scope
1270 if ( token
->nestLevel
== 0 && is_global
)
1273 * Handles this syntax:
1276 if (isType (token
, TOKEN_SEMICOLON
))
1277 makeJsTag (name
, JSTAG_VARIABLE
);
1280 * Statement has ended.
1281 * This deals with calls to functions, like:
1287 if ( isType (token
, TOKEN_EQUAL_SIGN
) )
1291 if ( isKeyword (token
, KEYWORD_function
) )
1295 if ( isKeyword (token
, KEYWORD_NONE
) &&
1296 ! isType (token
, TOKEN_OPEN_PAREN
) )
1299 * Functions of this format:
1300 * var D2A = function theAdd(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:
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
);
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
);
1339 is_class
= parseBlock (token
, name
);
1341 makeClassTag (name
);
1343 makeFunctionTag (name
);
1345 if ( vStringLength(secondary_name
->string
) > 0 )
1346 makeFunctionTag (secondary_name
);
1349 * Find to the end of the statement
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.
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:
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
));
1413 vStringCopy(fulltag
, token
->string
);
1415 vStringTerminate(fulltag
);
1416 if ( ! stringListHas(FunctionNames
, vStringValue (fulltag
)) &&
1417 ! stringListHas(ClassNames
, vStringValue (fulltag
)) )
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
1434 is_terminated
= TRUE
;
1437 else if (isKeyword (token
, KEYWORD_new
))
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
) )
1450 if ( isType (token
, TOKEN_OPEN_PAREN
) )
1451 skipArgumentList(token
);
1453 if (isType (token
, TOKEN_SEMICOLON
))
1455 if ( token
->nestLevel
== 0 )
1459 makeClassTag (name
);
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:
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
));
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() {
1519 * document.write("hello from checkForUpdate<br>")
1524 if ( ! is_terminated
&& isType (token
, TOKEN_CLOSE_CURLY
))
1525 is_terminated
= FALSE
;
1529 vStringCopy(token
->scope
, saveScope
);
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
)
1563 case KEYWORD_finally
:
1564 /* Common semantics */
1565 is_terminated
= parseIf (token
);
1567 case KEYWORD_switch
:
1568 parseSwitch (token
);
1571 parseStatement (token
, is_inside_class
);
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
)
1593 if (isType(token
, TOKEN_KEYWORD
))
1595 switch (token
->keyword
)
1597 case KEYWORD_function
: parseFunction (token
); break;
1598 default: parseLine (token
, FALSE
); break;
1603 parseLine (token
, FALSE
);
1608 static void initialize (const langType language
)
1610 Assert (sizeof (JsKinds
) / sizeof (JsKinds
[0]) == JSTAG_COUNT
);
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
);
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
;
1650 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */