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
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
21 #include "general.h" /* must always come first */
22 #include <ctype.h> /* to define isalpha () */
37 #define isType(token,t) (boolean) ((token)->type == (t))
38 #define isKeyword(token,k) (boolean) ((token)->keyword == (k))
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
{
57 KEYWORD_capital_function
,
59 KEYWORD_capital_object
,
75 /* Used to determine whether keyword is valid for the token language and
78 typedef struct sKeywordDesc
{
83 typedef enum eTokenType
{
104 typedef struct sTokenInfo
{
109 unsigned long lineNumber
;
113 int bufferPosition
; /* buffer position of line containing name */
120 static langType Lang_js
;
122 static jmp_buf Exception
;
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
)
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]);
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 ();
202 token
->filePosition
= getInputFilePosition ();
204 token
->bufferPosition
= getInputBufferPosition ();
209 static void deleteToken (tokenInfo
*const token
)
211 vStringDelete (token
->string
);
212 vStringDelete (token
->scope
);
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
);
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
;
237 static void makeJsTag (tokenInfo
*const token
, const jsKind kind
)
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
);
289 static int skipToCharacter (const int c
)
295 } while (d
!= EOF
&& d
!= c
);
299 static void parseString (vString
*const string
, const int delimiter
)
309 c
= fileGetc(); /* This maybe a ' or ". */
310 vStringPut(string
, c
);
312 else if (c
== delimiter
)
315 vStringPut (string
, c
);
317 vStringTerminate (string
);
320 /* Read a C identifier beginning with "firstChar" and places it into
323 static void parseIdentifier (vString
*const string
, const int firstChar
)
326 Assert (isIdentChar (c
));
329 vStringPut (string
, c
);
331 } while (isIdentChar (c
));
332 vStringTerminate (string
);
334 fileUngetc (c
); /* unget non-identifier character */
337 static keywordId
analyzeToken (vString
*const name
)
339 vString
*keyword
= vStringNew ();
341 vStringCopyToLower (keyword
, name
);
342 result
= (keywordId
) lookupKeyword (vStringValue (keyword
), Lang_js
);
343 vStringDelete (keyword
);
347 static void readToken (tokenInfo
*const token
)
351 token
->type
= TOKEN_UNDEFINED
;
352 token
->keyword
= KEYWORD_NONE
;
353 vStringClear (token
->string
);
359 token
->lineNumber
= getSourceLineNumber ();
361 token
->filePosition
= getInputFilePosition ();
363 token
->bufferPosition
= getInputBufferPosition ();
365 while (c
== '\t' || c
== ' ' || c
== '\n');
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;
384 token
->type
= TOKEN_STRING
;
385 parseString (token
->string
, c
);
386 token
->lineNumber
= getSourceLineNumber ();
388 token
->filePosition
= getInputFilePosition ();
390 token
->bufferPosition
= getInputBufferPosition ();
395 if (c
!= '\\' && c
!= '"' && !isspace (c
))
397 token
->type
= TOKEN_CHARACTER
;
398 token
->lineNumber
= getSourceLineNumber ();
400 token
->filePosition
= getInputFilePosition ();
402 token
->bufferPosition
= getInputBufferPosition ();
408 if ( (d
!= '*') && /* is this the start of a comment? */
409 (d
!= '/') ) /* is a one line comment? */
411 token
->type
= TOKEN_FORWARD_SLASH
;
420 skipToCharacter ('*');
426 } while (c
!= EOF
&& c
!= '\0');
429 else if (d
== '/') /* is this the start of a comment? */
431 skipToCharacter ('\n');
439 if (! isIdentChar (c
))
440 token
->type
= TOKEN_UNDEFINED
;
443 parseIdentifier (token
->string
, c
);
444 token
->lineNumber
= getSourceLineNumber ();
446 token
->filePosition
= getInputFilePosition ();
448 token
->bufferPosition
= getInputBufferPosition ();
449 token
->keyword
= analyzeToken (token
->string
);
450 if (isKeyword (token
, KEYWORD_NONE
))
451 token
->type
= TOKEN_IDENTIFIER
;
453 token
->type
= TOKEN_KEYWORD
;
459 static void copyToken (tokenInfo
*const dest
, tokenInfo
*const src
)
461 dest
->nestLevel
= src
->nestLevel
;
462 dest
->lineNumber
= src
->lineNumber
;
464 dest
->filePosition
= src
->filePosition
;
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
)
482 * Other databases can have arguments with fully declared
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? */
491 while (! (isType (token
, TOKEN_CLOSE_PAREN
) && (nest_level
== 0)))
494 if (isType (token
, TOKEN_OPEN_PAREN
))
498 if (isType (token
, TOKEN_CLOSE_PAREN
))
510 static void skipArrayList (tokenInfo
*const token
)
515 * Handle square brackets
517 * So we must check for nested open and closing square brackets
520 if (isType (token
, TOKEN_OPEN_SQUARE
)) /* arguments? */
523 while (! (isType (token
, TOKEN_CLOSE_SQUARE
) && (nest_level
== 0)))
526 if (isType (token
, TOKEN_OPEN_SQUARE
))
530 if (isType (token
, TOKEN_CLOSE_SQUARE
))
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
);
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
);
591 static void parseSwitch (tokenInfo
*const token
)
594 * switch (expression){
601 * default : statement;
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>");
642 * document.write(number+"<br>");
647 * document.write(number+"<br>");
653 if (isKeyword (token
, KEYWORD_for
) || isKeyword (token
, KEYWORD_while
))
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
);
678 parseLine(token
, FALSE
);
681 else if (isKeyword (token
, KEYWORD_do
))
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
);
697 parseLine(token
, FALSE
);
702 if (isKeyword (token
, KEYWORD_while
))
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
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.
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.
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
);
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
;
803 if (isType (token
, TOKEN_CLOSE_CURLY
))
806 * This statement did not have a line terminator.
808 read_next_token
= FALSE
;
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) {}
831 /* Add scope in case this is an INNER function */
832 addToScope(name
, token
->scope
);
835 if (isType (token
, TOKEN_PERIOD
))
840 if ( isKeyword(token
, KEYWORD_NONE
) )
842 addContext (name
, 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
);
857 makeFunctionTag (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 ();
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
) )
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
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
);
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
)
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
);
961 static void parseMethods (tokenInfo
*const token
, tokenInfo
*const class)
963 tokenInfo
*const name
= newToken ();
966 * This deals with these formats
968 * validMethod : function(a,b) {}
969 * 'validMethod2' : function(a,b) {}
975 if (isType (token
, TOKEN_STRING
) || isKeyword(token
, KEYWORD_NONE
))
977 copyToken(name
, token
);
980 if ( isType (token
, TOKEN_COLON
) )
983 if ( isKeyword (token
, KEYWORD_function
) )
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
1006 addToScope (name
, class->string
);
1007 makeJsTag (name
, JSTAG_PROPERTY
);
1010 * Read the next token, if a comma
1011 * we must loop again
1017 } while ( isType(token
, TOKEN_COMMA
) );
1019 findCmdTerm (token
);
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:
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;");
1046 * testlib.extras.ValidClassOne = function(a,b) {
1050 * testlib.extras.ValidClassOne.prototype = {
1051 * 'validMethodOne' : function(a,b) {},
1052 * 'validMethodTwo' : function(a,b) {}
1054 * ValidClassTwo = function ()
1056 * this.validMethodThree = function() {}
1058 * this.validMethodFour = () {}
1060 * Database.prototype.validMethodThree = Database_getTodaysDate;
1063 if ( is_inside_class
)
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 )
1080 if ( isKeyword(token
, KEYWORD_this
) )
1083 if (isType (token
, TOKEN_PERIOD
))
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 */
1097 if (isType (token
, TOKEN_PERIOD
))
1100 * Cannot be a global variable is it has dot references in the name
1106 if ( isKeyword(token
, KEYWORD_NONE
) )
1110 vStringCopy(saveScope
, token
->scope
);
1111 addToScope(token
, name
->string
);
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 ) {
1127 * Specified function name: "build"
1128 * BindAgent.prototype.build = function( mode ) {
1129 * ignore everything within this function
1134 * ValidClassOne.prototype = {
1135 * 'validMethodOne' : function(a,b) {},
1136 * 'validMethodTwo' : function(a,b) {}
1140 makeClassTag (name
);
1142 is_prototype
= TRUE
;
1145 * There should a ".function_name" next.
1148 if (isType (token
, TOKEN_PERIOD
))
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
;
1176 else if (isType (token
, TOKEN_EQUAL_SIGN
))
1179 if (isType (token
, TOKEN_OPEN_CURLY
))
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
;
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
;
1230 if ( isType (token
, TOKEN_SEMICOLON
) )
1233 * Only create variables for global scope
1235 if ( token
->nestLevel
== 0 && is_global
)
1238 * Handles this syntax:
1241 if (isType (token
, TOKEN_SEMICOLON
))
1242 makeJsTag (name
, JSTAG_VARIABLE
);
1245 * Statement has ended.
1246 * This deals with calls to functions, like:
1252 if ( isType (token
, TOKEN_EQUAL_SIGN
) )
1256 if ( isKeyword (token
, KEYWORD_function
) )
1260 if ( isKeyword (token
, KEYWORD_NONE
) &&
1261 ! isType (token
, TOKEN_OPEN_PAREN
) )
1264 * Functions of this format:
1265 * var D2A = function theAdd(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:
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
);
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
);
1304 is_class
= parseBlock (token
, name
);
1306 makeClassTag (name
);
1308 makeFunctionTag (name
);
1310 if ( vStringLength(secondary_name
->string
) > 0 )
1311 makeFunctionTag (secondary_name
);
1314 * Find to the end of the statement
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
))
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
) )
1361 if ( isType (token
, TOKEN_OPEN_PAREN
) )
1362 skipArgumentList(token
);
1364 if (isType (token
, TOKEN_SEMICOLON
))
1368 makeClassTag (name
);
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:
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() {
1414 * document.write("hello from checkForUpdate<br>")
1419 if (isType (token
, TOKEN_CLOSE_CURLY
))
1420 is_terminated
= FALSE
;
1424 vStringCopy(token
->scope
, saveScope
);
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
)
1458 case KEYWORD_finally
:
1459 /* Common semantics */
1460 is_terminated
= parseIf (token
);
1462 case KEYWORD_switch
:
1463 parseSwitch (token
);
1466 parseStatement (token
, is_inside_class
);
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
)
1488 if (isType(token
, TOKEN_KEYWORD
))
1490 switch (token
->keyword
)
1492 case KEYWORD_function
: parseFunction (token
); break;
1493 default: parseLine (token
, FALSE
); break;
1498 parseLine (token
, FALSE
);
1503 static void initialize (const langType language
)
1505 Assert (sizeof (JsKinds
) / sizeof (JsKinds
[0]) == JSTAG_COUNT
);
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
);
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
;
1545 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */