2 * $Id: jscript.c 666 2008-05-15 17:47:31Z dfishburn $
4 * Copyright (c) 2003, Darren Hiebert
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
9 * This module contains functions for generating tags for JavaScript language
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 () */
39 #define isType(token,t) (boolean) ((token)->type == (t))
40 #define isKeyword(token,k) (boolean) ((token)->keyword == (k))
46 typedef enum eException
{ ExceptionNone
, ExceptionEOF
} exception_t
;
49 * Tracks class and function names already created
51 static stringList
*ClassNames
;
52 static stringList
*FunctionNames
;
54 /* Used to specify type of keyword.
56 typedef enum eKeywordId
{
59 KEYWORD_capital_function
,
61 KEYWORD_capital_object
,
77 /* Used to determine whether keyword is valid for the token language and
80 typedef struct sKeywordDesc
{
85 typedef enum eTokenType
{
106 typedef struct sTokenInfo
{
111 unsigned long lineNumber
;
121 static langType Lang_js
;
123 static jmp_buf Exception
;
134 static kindOption JsKinds
[] = {
135 { TRUE
, 'f', "function", "functions" },
136 { TRUE
, 'c', "class", "classes" },
137 { TRUE
, 'm', "method", "methods" },
138 { TRUE
, 'p', "property", "properties" },
139 { TRUE
, 'v', "variable", "global variables" }
142 static const keywordDesc JsKeywordTable
[] = {
143 /* keyword keyword ID */
144 { "function", KEYWORD_function
},
145 { "Function", KEYWORD_capital_function
},
146 { "object", KEYWORD_object
},
147 { "Object", KEYWORD_capital_object
},
148 { "prototype", KEYWORD_prototype
},
149 { "var", KEYWORD_var
},
150 { "new", KEYWORD_new
},
151 { "this", KEYWORD_this
},
152 { "for", KEYWORD_for
},
153 { "while", KEYWORD_while
},
154 { "do", KEYWORD_do
},
155 { "if", KEYWORD_if
},
156 { "else", KEYWORD_else
},
157 { "switch", KEYWORD_switch
},
158 { "try", KEYWORD_try
},
159 { "catch", KEYWORD_catch
},
160 { "finally", KEYWORD_finally
}
164 * FUNCTION DEFINITIONS
167 /* Recursive functions */
168 static void parseFunction (tokenInfo
*const token
);
169 static boolean
parseBlock (tokenInfo
*const token
, tokenInfo
*const parent
);
170 static boolean
parseLine (tokenInfo
*const token
, boolean is_inside_class
);
172 static boolean
isIdentChar (const int c
)
175 (isalpha (c
) || isdigit (c
) || c
== '$' ||
176 c
== '@' || c
== '_' || c
== '#');
179 static void buildJsKeywordHash (void)
181 const size_t count
= sizeof (JsKeywordTable
) /
182 sizeof (JsKeywordTable
[0]);
184 for (i
= 0 ; i
< count
; ++i
)
186 const keywordDesc
* const p
= &JsKeywordTable
[i
];
187 addKeyword (p
->name
, Lang_js
, (int) p
->id
);
191 static tokenInfo
*newToken (void)
193 tokenInfo
*const token
= xMalloc (1, tokenInfo
);
195 token
->type
= TOKEN_UNDEFINED
;
196 token
->keyword
= KEYWORD_NONE
;
197 token
->string
= vStringNew ();
198 token
->scope
= vStringNew ();
199 token
->nestLevel
= 0;
200 token
->ignoreTag
= FALSE
;
201 token
->lineNumber
= getSourceLineNumber ();
202 token
->filePosition
= getInputFilePosition ();
207 static void deleteToken (tokenInfo
*const token
)
209 vStringDelete (token
->string
);
210 vStringDelete (token
->scope
);
215 * Tag generation functions
218 static void makeConstTag (tokenInfo
*const token
, const jsKind kind
)
220 if (JsKinds
[kind
].enabled
&& ! token
->ignoreTag
)
222 const char *const name
= vStringValue (token
->string
);
224 initTagEntry (&e
, name
);
226 e
.lineNumber
= token
->lineNumber
;
227 e
.filePosition
= token
->filePosition
;
228 e
.kindName
= JsKinds
[kind
].name
;
229 e
.kind
= JsKinds
[kind
].letter
;
235 static void makeJsTag (tokenInfo
*const token
, const jsKind kind
)
239 if (JsKinds
[kind
].enabled
&& ! token
->ignoreTag
)
242 * If a scope has been added to the token, change the token
243 * string to include the scope when making the tag.
245 if ( vStringLength(token
->scope
) > 0 )
247 fulltag
= vStringNew ();
248 vStringCopy(fulltag
, token
->scope
);
249 vStringCatS (fulltag
, ".");
250 vStringCatS (fulltag
, vStringValue(token
->string
));
251 vStringTerminate(fulltag
);
252 vStringCopy(token
->string
, fulltag
);
253 vStringDelete (fulltag
);
255 makeConstTag (token
, kind
);
259 static void makeClassTag (tokenInfo
*const token
)
263 if ( ! token
->ignoreTag
)
265 fulltag
= vStringNew ();
266 if (vStringLength (token
->scope
) > 0)
268 vStringCopy(fulltag
, token
->scope
);
269 vStringCatS (fulltag
, ".");
270 vStringCatS (fulltag
, vStringValue(token
->string
));
274 vStringCopy(fulltag
, token
->string
);
276 vStringTerminate(fulltag
);
277 if ( ! stringListHas(ClassNames
, vStringValue (fulltag
)) )
279 stringListAdd (ClassNames
, vStringNewCopy (fulltag
));
280 makeJsTag (token
, JSTAG_CLASS
);
282 vStringDelete (fulltag
);
286 static void makeFunctionTag (tokenInfo
*const token
)
290 if ( ! token
->ignoreTag
)
292 fulltag
= vStringNew ();
293 if (vStringLength (token
->scope
) > 0)
295 vStringCopy(fulltag
, token
->scope
);
296 vStringCatS (fulltag
, ".");
297 vStringCatS (fulltag
, vStringValue(token
->string
));
301 vStringCopy(fulltag
, token
->string
);
303 vStringTerminate(fulltag
);
304 if ( ! stringListHas(FunctionNames
, vStringValue (fulltag
)) )
306 stringListAdd (FunctionNames
, vStringNewCopy (fulltag
));
307 makeJsTag (token
, JSTAG_FUNCTION
);
309 vStringDelete (fulltag
);
317 static void parseString (vString
*const string
, const int delimiter
)
327 c
= fileGetc(); /* This maybe a ' or ". */
328 vStringPut(string
, c
);
330 else if (c
== delimiter
)
333 vStringPut (string
, c
);
335 vStringTerminate (string
);
338 /* Read a C identifier beginning with "firstChar" and places it into
341 static void parseIdentifier (vString
*const string
, const int firstChar
)
344 Assert (isIdentChar (c
));
347 vStringPut (string
, c
);
349 } while (isIdentChar (c
));
350 vStringTerminate (string
);
352 fileUngetc (c
); /* unget non-identifier character */
355 static void readToken (tokenInfo
*const token
)
359 token
->type
= TOKEN_UNDEFINED
;
360 token
->keyword
= KEYWORD_NONE
;
361 vStringClear (token
->string
);
367 token
->lineNumber
= getSourceLineNumber ();
368 token
->filePosition
= getInputFilePosition ();
370 while (c
== '\t' || c
== ' ' || c
== '\n');
374 case EOF
: longjmp (Exception
, (int)ExceptionEOF
); break;
375 case '(': token
->type
= TOKEN_OPEN_PAREN
; break;
376 case ')': token
->type
= TOKEN_CLOSE_PAREN
; break;
377 case ';': token
->type
= TOKEN_SEMICOLON
; break;
378 case ',': token
->type
= TOKEN_COMMA
; break;
379 case '.': token
->type
= TOKEN_PERIOD
; break;
380 case ':': token
->type
= TOKEN_COLON
; break;
381 case '{': token
->type
= TOKEN_OPEN_CURLY
; break;
382 case '}': token
->type
= TOKEN_CLOSE_CURLY
; break;
383 case '=': token
->type
= TOKEN_EQUAL_SIGN
; break;
384 case '[': token
->type
= TOKEN_OPEN_SQUARE
; break;
385 case ']': token
->type
= TOKEN_CLOSE_SQUARE
; break;
389 token
->type
= TOKEN_STRING
;
390 parseString (token
->string
, c
);
391 token
->lineNumber
= getSourceLineNumber ();
392 token
->filePosition
= getInputFilePosition ();
397 if (c
!= '\\' && c
!= '"' && !isspace (c
))
399 token
->type
= TOKEN_CHARACTER
;
400 token
->lineNumber
= getSourceLineNumber ();
401 token
->filePosition
= getInputFilePosition ();
407 if ( (d
!= '*') && /* is this the start of a comment? */
408 (d
!= '/') ) /* is a one line comment? */
410 token
->type
= TOKEN_FORWARD_SLASH
;
419 fileSkipToCharacter ('*');
425 } while (c
!= EOF
&& c
!= '\0');
428 else if (d
== '/') /* is this the start of a comment? */
430 fileSkipToCharacter ('\n');
438 if (! isIdentChar (c
))
439 token
->type
= TOKEN_UNDEFINED
;
442 parseIdentifier (token
->string
, c
);
443 token
->lineNumber
= getSourceLineNumber ();
444 token
->filePosition
= getInputFilePosition ();
445 token
->keyword
= analyzeToken (token
->string
, Lang_js
);
446 if (isKeyword (token
, KEYWORD_NONE
))
447 token
->type
= TOKEN_IDENTIFIER
;
449 token
->type
= TOKEN_KEYWORD
;
455 static void copyToken (tokenInfo
*const dest
, tokenInfo
*const src
)
457 dest
->nestLevel
= src
->nestLevel
;
458 dest
->lineNumber
= src
->lineNumber
;
459 dest
->filePosition
= src
->filePosition
;
460 dest
->type
= src
->type
;
461 dest
->keyword
= src
->keyword
;
462 vStringCopy(dest
->string
, src
->string
);
463 vStringCopy(dest
->scope
, src
->scope
);
467 * Token parsing functions
470 static void skipArgumentList (tokenInfo
*const token
)
475 * Other databases can have arguments with fully declared
477 * ( name varchar(30), text binary(10) )
478 * So we must check for nested open and closing parantheses
481 if (isType (token
, TOKEN_OPEN_PAREN
)) /* arguments? */
484 while (! (isType (token
, TOKEN_CLOSE_PAREN
) && (nest_level
== 0)))
487 if (isType (token
, TOKEN_OPEN_PAREN
))
491 if (isType (token
, TOKEN_CLOSE_PAREN
))
503 static void skipArrayList (tokenInfo
*const token
)
508 * Handle square brackets
510 * So we must check for nested open and closing square brackets
513 if (isType (token
, TOKEN_OPEN_SQUARE
)) /* arguments? */
516 while (! (isType (token
, TOKEN_CLOSE_SQUARE
) && (nest_level
== 0)))
519 if (isType (token
, TOKEN_OPEN_SQUARE
))
523 if (isType (token
, TOKEN_CLOSE_SQUARE
))
535 static void addContext (tokenInfo
* const parent
, const tokenInfo
* const child
)
537 if (vStringLength (parent
->string
) > 0)
539 vStringCatS (parent
->string
, ".");
541 vStringCatS (parent
->string
, vStringValue(child
->string
));
542 vStringTerminate(parent
->string
);
545 static void addToScope (tokenInfo
* const token
, vString
* const extra
)
547 if (vStringLength (token
->scope
) > 0)
549 vStringCatS (token
->scope
, ".");
551 vStringCatS (token
->scope
, vStringValue(extra
));
552 vStringTerminate(token
->scope
);
559 static void findCmdTerm (tokenInfo
*const token
)
562 * Read until we find either a semicolon or closing brace.
563 * Any nested braces will be handled within.
565 while (! ( isType (token
, TOKEN_SEMICOLON
) ||
566 isType (token
, TOKEN_CLOSE_CURLY
) ) )
568 /* Handle nested blocks */
569 if ( isType (token
, TOKEN_OPEN_CURLY
))
571 parseBlock (token
, token
);
573 else if ( isType (token
, TOKEN_OPEN_PAREN
) )
575 skipArgumentList(token
);
584 static void parseSwitch (tokenInfo
*const token
)
587 * switch (expression){
594 * default : statement;
600 if (isType (token
, TOKEN_OPEN_PAREN
))
603 * Handle nameless functions, these will only
604 * be considered methods.
606 skipArgumentList(token
);
609 if (isType (token
, TOKEN_OPEN_CURLY
))
612 * This will be either a function or a class.
613 * We can only determine this by checking the body
614 * of the function. If we find a "this." we know
615 * it is a class, otherwise it is a function.
617 parseBlock (token
, token
);
622 static void parseLoop (tokenInfo
*const token
)
625 * Handles these statements
626 * for (x=0; x<3; x++)
627 * document.write("This text is repeated three times<br>");
629 * for (x=0; x<3; x++)
631 * document.write("This text is repeated three times<br>");
635 * document.write(number+"<br>");
640 * document.write(number+"<br>");
646 if (isKeyword (token
, KEYWORD_for
) || isKeyword (token
, KEYWORD_while
))
650 if (isType (token
, TOKEN_OPEN_PAREN
))
653 * Handle nameless functions, these will only
654 * be considered methods.
656 skipArgumentList(token
);
659 if (isType (token
, TOKEN_OPEN_CURLY
))
662 * This will be either a function or a class.
663 * We can only determine this by checking the body
664 * of the function. If we find a "this." we know
665 * it is a class, otherwise it is a function.
667 parseBlock (token
, token
);
671 parseLine(token
, FALSE
);
674 else if (isKeyword (token
, KEYWORD_do
))
678 if (isType (token
, TOKEN_OPEN_CURLY
))
681 * This will be either a function or a class.
682 * We can only determine this by checking the body
683 * of the function. If we find a "this." we know
684 * it is a class, otherwise it is a function.
686 parseBlock (token
, token
);
690 parseLine(token
, FALSE
);
695 if (isKeyword (token
, KEYWORD_while
))
699 if (isType (token
, TOKEN_OPEN_PAREN
))
702 * Handle nameless functions, these will only
703 * be considered methods.
705 skipArgumentList(token
);
711 static boolean
parseIf (tokenInfo
*const token
)
713 boolean read_next_token
= TRUE
;
715 * If statements have two forms
734 * This example if correctly written, but the
735 * else contains only 1 statement without a terminator
736 * since the function finishes with the closing brace.
745 * TODO: Deal with statements that can optional end
746 * without a semi-colon. Currently this messes up
747 * the parsing of blocks.
748 * Need to somehow detect this has happened, and either
749 * backup a token, or skip reading the next token if
750 * that is possible from all code locations.
756 if (isKeyword (token
, KEYWORD_if
))
759 * Check for an "else if" and consume the "if"
764 if (isType (token
, TOKEN_OPEN_PAREN
))
767 * Handle nameless functions, these will only
768 * be considered methods.
770 skipArgumentList(token
);
773 if (isType (token
, TOKEN_OPEN_CURLY
))
776 * This will be either a function or a class.
777 * We can only determine this by checking the body
778 * of the function. If we find a "this." we know
779 * it is a class, otherwise it is a function.
781 parseBlock (token
, token
);
788 * The IF could be followed by an ELSE statement.
789 * This too could have two formats, a curly braced
790 * multiline section, or another single line.
793 if (isType (token
, TOKEN_CLOSE_CURLY
))
796 * This statement did not have a line terminator.
798 read_next_token
= FALSE
;
804 if (isType (token
, TOKEN_CLOSE_CURLY
))
807 * This statement did not have a line terminator.
809 read_next_token
= FALSE
;
813 if (isKeyword (token
, KEYWORD_else
))
814 read_next_token
= parseIf (token
);
818 return read_next_token
;
821 static void parseFunction (tokenInfo
*const token
)
823 tokenInfo
*const name
= newToken ();
824 boolean is_class
= FALSE
;
827 * This deals with these formats
828 * function validFunctionTwo(a,b) {}
832 /* Add scope in case this is an INNER function */
833 addToScope(name
, token
->scope
);
836 if (isType (token
, TOKEN_PERIOD
))
841 if ( isKeyword(token
, KEYWORD_NONE
) )
843 addContext (name
, token
);
846 } while (isType (token
, TOKEN_PERIOD
));
849 if ( isType (token
, TOKEN_OPEN_PAREN
) )
850 skipArgumentList(token
);
852 if ( isType (token
, TOKEN_OPEN_CURLY
) )
854 is_class
= parseBlock (token
, name
);
858 makeFunctionTag (name
);
866 static boolean
parseBlock (tokenInfo
*const token
, tokenInfo
*const parent
)
868 boolean is_class
= FALSE
;
869 boolean read_next_token
= TRUE
;
870 vString
* saveScope
= vStringNew ();
874 * Make this routine a bit more forgiving.
875 * If called on an open_curly advance it
877 if ( isType (token
, TOKEN_OPEN_CURLY
) &&
878 isKeyword(token
, KEYWORD_NONE
) )
881 if (! isType (token
, TOKEN_CLOSE_CURLY
))
884 * Read until we find the closing brace,
885 * any nested braces will be handled within
889 read_next_token
= TRUE
;
890 if (isKeyword (token
, KEYWORD_this
))
893 * Means we are inside a class and have found
894 * a class, not a function
897 vStringCopy(saveScope
, token
->scope
);
898 addToScope (token
, parent
->string
);
901 * Ignore the remainder of the line
902 * findCmdTerm(token);
904 parseLine (token
, is_class
);
906 vStringCopy(token
->scope
, saveScope
);
908 else if (isKeyword (token
, KEYWORD_var
))
911 * Potentially we have found an inner function.
912 * Set something to indicate the scope
914 vStringCopy(saveScope
, token
->scope
);
915 addToScope (token
, parent
->string
);
916 parseLine (token
, is_class
);
917 vStringCopy(token
->scope
, saveScope
);
919 else if (isKeyword (token
, KEYWORD_function
))
921 vStringCopy(saveScope
, token
->scope
);
922 addToScope (token
, parent
->string
);
923 parseFunction (token
);
924 vStringCopy(token
->scope
, saveScope
);
926 else if (isType (token
, TOKEN_OPEN_CURLY
))
928 /* Handle nested blocks */
929 parseBlock (token
, parent
);
934 * It is possible for a line to have no terminator
935 * if the following line is a closing brace.
936 * parseLine will detect this case and indicate
937 * whether we should read an additional token.
939 read_next_token
= parseLine (token
, is_class
);
943 * Always read a new token unless we find a statement without
944 * a ending terminator
946 if( read_next_token
)
950 * If we find a statement without a terminator consider the
951 * block finished, otherwise the stack will be off by one.
953 } while (! isType (token
, TOKEN_CLOSE_CURLY
) && read_next_token
);
956 vStringDelete(saveScope
);
962 static void parseMethods (tokenInfo
*const token
, tokenInfo
*const class)
964 tokenInfo
*const name
= newToken ();
967 * This deals with these formats
969 * validMethod : function(a,b) {}
970 * 'validMethod2' : function(a,b) {}
971 * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false}
977 if (isType (token
, TOKEN_STRING
) || isKeyword(token
, KEYWORD_NONE
))
979 copyToken(name
, token
);
982 if ( isType (token
, TOKEN_COLON
) )
985 if ( isKeyword (token
, KEYWORD_function
) )
988 if ( isType (token
, TOKEN_OPEN_PAREN
) )
990 skipArgumentList(token
);
993 if (isType (token
, TOKEN_OPEN_CURLY
))
995 addToScope (name
, class->string
);
996 makeJsTag (name
, JSTAG_METHOD
);
997 parseBlock (token
, name
);
1000 * Read to the closing curly, check next
1001 * token, if a comma, we must loop again
1008 addToScope (name
, class->string
);
1009 makeJsTag (name
, JSTAG_PROPERTY
);
1012 * Read the next token, if a comma
1013 * we must loop again
1019 } while ( isType(token
, TOKEN_COMMA
) );
1021 findCmdTerm (token
);
1026 static boolean
parseStatement (tokenInfo
*const token
, boolean is_inside_class
)
1028 tokenInfo
*const name
= newToken ();
1029 tokenInfo
*const secondary_name
= newToken ();
1030 vString
* saveScope
= vStringNew ();
1031 boolean is_class
= FALSE
;
1032 boolean is_terminated
= TRUE
;
1033 boolean is_global
= FALSE
;
1034 boolean is_prototype
= FALSE
;
1037 vStringClear(saveScope
);
1039 * Functions can be named or unnamed.
1040 * This deals with these formats:
1042 * validFunctionOne = function(a,b) {}
1043 * testlib.validFunctionFive = function(a,b) {}
1044 * var innerThree = function(a,b) {}
1045 * var innerFour = (a,b) {}
1046 * var D2 = secondary_fcn_name(a,b) {}
1047 * var D3 = new Function("a", "b", "return a+b;");
1049 * testlib.extras.ValidClassOne = function(a,b) {
1053 * testlib.extras.ValidClassOne.prototype = {
1054 * 'validMethodOne' : function(a,b) {},
1055 * 'validMethodTwo' : function(a,b) {}
1057 * ValidClassTwo = function ()
1059 * this.validMethodThree = function() {}
1061 * this.validMethodFour = () {}
1063 * Database.prototype.validMethodThree = Database_getTodaysDate;
1066 if ( is_inside_class
)
1069 * var can preceed an inner function
1071 if ( isKeyword(token
, KEYWORD_var
) )
1074 * Only create variables for global scope
1076 if ( token
->nestLevel
== 0 )
1083 if ( isKeyword(token
, KEYWORD_this
) )
1086 if (isType (token
, TOKEN_PERIOD
))
1092 copyToken(name
, token
);
1094 while (! isType (token
, TOKEN_CLOSE_CURLY
) &&
1095 ! isType (token
, TOKEN_SEMICOLON
) &&
1096 ! isType (token
, TOKEN_EQUAL_SIGN
) )
1098 /* Potentially the name of the function */
1100 if (isType (token
, TOKEN_PERIOD
))
1103 * Cannot be a global variable is it has dot references in the name
1109 if ( isKeyword(token
, KEYWORD_NONE
) )
1113 vStringCopy(saveScope
, token
->scope
);
1114 addToScope(token
, name
->string
);
1117 addContext (name
, token
);
1119 else if ( isKeyword(token
, KEYWORD_prototype
) )
1122 * When we reach the "prototype" tag, we infer:
1123 * "BindAgent" is a class
1124 * "build" is a method
1126 * function BindAgent( repeatableIdName, newParentIdName ) {
1130 * Specified function name: "build"
1131 * BindAgent.prototype.build = function( mode ) {
1132 * ignore everything within this function
1137 * ValidClassOne.prototype = {
1138 * 'validMethodOne' : function(a,b) {},
1139 * 'validMethodTwo' : function(a,b) {}
1143 makeClassTag (name
);
1145 is_prototype
= TRUE
;
1148 * There should a ".function_name" next.
1151 if (isType (token
, TOKEN_PERIOD
))
1157 if ( isKeyword(token
, KEYWORD_NONE
) )
1159 vStringCopy(saveScope
, token
->scope
);
1160 addToScope(token
, name
->string
);
1162 makeJsTag (token
, JSTAG_METHOD
);
1164 * We can read until the end of the block / statement.
1165 * We need to correctly parse any nested blocks, but
1166 * we do NOT want to create any tags based on what is
1167 * within the blocks.
1169 token
->ignoreTag
= TRUE
;
1171 * Find to the end of the statement
1173 findCmdTerm (token
);
1174 token
->ignoreTag
= FALSE
;
1175 is_terminated
= TRUE
;
1179 else if (isType (token
, TOKEN_EQUAL_SIGN
))
1182 if (isType (token
, TOKEN_OPEN_CURLY
))
1187 * Creates tags for each of these class methods
1188 * ValidClassOne.prototype = {
1189 * 'validMethodOne' : function(a,b) {},
1190 * 'validMethodTwo' : function(a,b) {}
1193 parseMethods(token
, name
);
1195 * Find to the end of the statement
1197 findCmdTerm (token
);
1198 token
->ignoreTag
= FALSE
;
1199 is_terminated
= TRUE
;
1205 } while (isType (token
, TOKEN_PERIOD
));
1208 if ( isType (token
, TOKEN_OPEN_PAREN
) )
1209 skipArgumentList(token
);
1211 if ( isType (token
, TOKEN_OPEN_SQUARE
) )
1212 skipArrayList(token
);
1215 if ( isType (token, TOKEN_OPEN_CURLY) )
1217 is_class = parseBlock (token, name);
1222 if ( isType (token
, TOKEN_CLOSE_CURLY
) )
1225 * Reaching this section without having
1226 * processed an open curly brace indicates
1227 * the statement is most likely not terminated.
1229 is_terminated
= FALSE
;
1233 if ( isType (token
, TOKEN_SEMICOLON
) )
1236 * Only create variables for global scope
1238 if ( token
->nestLevel
== 0 && is_global
)
1241 * Handles this syntax:
1244 if (isType (token
, TOKEN_SEMICOLON
))
1245 makeJsTag (name
, JSTAG_VARIABLE
);
1248 * Statement has ended.
1249 * This deals with calls to functions, like:
1255 if ( isType (token
, TOKEN_EQUAL_SIGN
) )
1259 if ( isKeyword (token
, KEYWORD_function
) )
1263 if ( isKeyword (token
, KEYWORD_NONE
) &&
1264 ! isType (token
, TOKEN_OPEN_PAREN
) )
1267 * Functions of this format:
1268 * var D2A = function theAdd(a, b)
1272 * Are really two separate defined functions and
1273 * can be referenced in two ways:
1274 * alert( D2A(1,2) ); // produces 3
1275 * alert( theAdd(1,2) ); // also produces 3
1276 * So it must have two tags:
1279 * Save the reference to the name for later use, once
1280 * we have established this is a valid function we will
1281 * create the secondary reference to it.
1283 copyToken(secondary_name
, token
);
1287 if ( isType (token
, TOKEN_OPEN_PAREN
) )
1288 skipArgumentList(token
);
1290 if (isType (token
, TOKEN_OPEN_CURLY
))
1293 * This will be either a function or a class.
1294 * We can only determine this by checking the body
1295 * of the function. If we find a "this." we know
1296 * it is a class, otherwise it is a function.
1298 if ( is_inside_class
)
1300 makeJsTag (name
, JSTAG_METHOD
);
1301 if ( vStringLength(secondary_name
->string
) > 0 )
1302 makeFunctionTag (secondary_name
);
1303 parseBlock (token
, name
);
1307 is_class
= parseBlock (token
, name
);
1309 makeClassTag (name
);
1311 makeFunctionTag (name
);
1313 if ( vStringLength(secondary_name
->string
) > 0 )
1314 makeFunctionTag (secondary_name
);
1317 * Find to the end of the statement
1323 else if (isType (token
, TOKEN_OPEN_PAREN
))
1326 * Handle nameless functions
1327 * this.method_name = () {}
1329 skipArgumentList(token
);
1331 if (isType (token
, TOKEN_OPEN_CURLY
))
1334 * Nameless functions are only setup as methods.
1336 makeJsTag (name
, JSTAG_METHOD
);
1337 parseBlock (token
, name
);
1340 else if (isType (token
, TOKEN_OPEN_CURLY
))
1343 * Creates tags for each of these class methods
1344 * ValidClassOne.prototype = {
1345 * 'validMethodOne' : function(a,b) {},
1346 * 'validMethodTwo' : function(a,b) {}
1349 parseMethods(token
, name
);
1350 if (isType (token
, TOKEN_CLOSE_CURLY
))
1353 * Assume the closing parantheses terminates
1356 is_terminated
= TRUE
;
1359 else if (isKeyword (token
, KEYWORD_new
))
1362 if ( isKeyword (token
, KEYWORD_function
) ||
1363 isKeyword (token
, KEYWORD_capital_function
) ||
1364 isKeyword (token
, KEYWORD_object
) ||
1365 isKeyword (token
, KEYWORD_capital_object
) )
1367 if ( isKeyword (token
, KEYWORD_object
) ||
1368 isKeyword (token
, KEYWORD_capital_object
) )
1372 if ( isType (token
, TOKEN_OPEN_PAREN
) )
1373 skipArgumentList(token
);
1375 if (isType (token
, TOKEN_SEMICOLON
))
1377 if ( token
->nestLevel
== 0 )
1381 makeClassTag (name
);
1383 makeFunctionTag (name
);
1389 else if (isKeyword (token
, KEYWORD_NONE
))
1392 * Only create variables for global scope
1394 if ( token
->nestLevel
== 0 && is_global
)
1397 * A pointer can be created to the function.
1398 * If we recognize the function/class name ignore the variable.
1399 * This format looks identical to a variable definition.
1400 * A variable defined outside of a block is considered
1401 * a global variable:
1404 * This is not a global variable:
1405 * var g_var = function;
1406 * This is a global variable:
1407 * var g_var = different_var_name;
1409 fulltag
= vStringNew ();
1410 if (vStringLength (token
->scope
) > 0)
1412 vStringCopy(fulltag
, token
->scope
);
1413 vStringCatS (fulltag
, ".");
1414 vStringCatS (fulltag
, vStringValue(token
->string
));
1418 vStringCopy(fulltag
, token
->string
);
1420 vStringTerminate(fulltag
);
1421 if ( ! stringListHas(FunctionNames
, vStringValue (fulltag
)) &&
1422 ! stringListHas(ClassNames
, vStringValue (fulltag
)) )
1424 findCmdTerm (token
);
1425 if (isType (token
, TOKEN_SEMICOLON
))
1426 makeJsTag (name
, JSTAG_VARIABLE
);
1428 vStringDelete (fulltag
);
1432 findCmdTerm (token
);
1435 * Statements can be optionally terminated in the case of
1436 * statement prior to a close curly brace as in the
1437 * document.write line below:
1439 * function checkForUpdate() {
1441 * document.write("hello from checkForUpdate<br>")
1446 if ( ! is_terminated
&& isType (token
, TOKEN_CLOSE_CURLY
))
1447 is_terminated
= FALSE
;
1451 vStringCopy(token
->scope
, saveScope
);
1453 deleteToken (secondary_name
);
1454 vStringDelete(saveScope
);
1456 return is_terminated
;
1459 static boolean
parseLine (tokenInfo
*const token
, boolean is_inside_class
)
1461 boolean is_terminated
= TRUE
;
1463 * Detect the common statements, if, while, for, do, ...
1464 * This is necessary since the last statement within a block "{}"
1465 * can be optionally terminated.
1467 * If the statement is not terminated, we need to tell
1468 * the calling routine to prevent reading an additional token
1469 * looking for the end of the statement.
1472 if (isType(token
, TOKEN_KEYWORD
))
1474 switch (token
->keyword
)
1485 case KEYWORD_finally
:
1486 /* Common semantics */
1487 is_terminated
= parseIf (token
);
1489 case KEYWORD_switch
:
1490 parseSwitch (token
);
1493 parseStatement (token
, is_inside_class
);
1500 * Special case where single line statements may not be
1501 * SEMICOLON terminated. parseBlock needs to know this
1502 * so that it does not read the next token.
1504 is_terminated
= parseStatement (token
, is_inside_class
);
1506 return is_terminated
;
1509 static void parseJsFile (tokenInfo
*const token
)
1515 if (isType(token
, TOKEN_KEYWORD
))
1517 switch (token
->keyword
)
1519 case KEYWORD_function
: parseFunction (token
); break;
1520 default: parseLine (token
, FALSE
); break;
1525 parseLine (token
, FALSE
);
1530 static void initialize (const langType language
)
1532 Assert (sizeof (JsKinds
) / sizeof (JsKinds
[0]) == JSTAG_COUNT
);
1534 buildJsKeywordHash ();
1537 static void findJsTags (void)
1539 tokenInfo
*const token
= newToken ();
1540 exception_t exception
;
1542 ClassNames
= stringListNew ();
1543 FunctionNames
= stringListNew ();
1545 exception
= (exception_t
) (setjmp (Exception
));
1546 while (exception
== ExceptionNone
)
1547 parseJsFile (token
);
1549 stringListDelete (ClassNames
);
1550 stringListDelete (FunctionNames
);
1552 FunctionNames
= NULL
;
1553 deleteToken (token
);
1556 /* Create parser definition stucture */
1557 extern parserDefinition
* JavaScriptParser (void)
1559 static const char *const extensions
[] = { "js", NULL
};
1560 parserDefinition
*const def
= parserNew ("JavaScript");
1561 def
->extensions
= extensions
;
1563 * New definitions for parsing instead of regex
1565 def
->kinds
= JsKinds
;
1566 def
->kindCount
= KIND_COUNT (JsKinds
);
1567 def
->parser
= findJsTags
;
1568 def
->initialize
= initialize
;
1572 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */