2 * $RCSfile: JSParser.java,v $
5 * CH-1700 Fribourg, Switzerland
8 *========================================================================
9 * Modifications history
10 *========================================================================
11 * $Log: not supported by cvs2svn $
12 * Revision 1.2 2005/04/06 18:29:29 axelcl
13 * Avoid NullPointerException
15 * Revision 1.1 2004/09/02 18:14:38 jsurfer
16 * intial source from ttp://www.sf.net/projects/wdte
18 * Revision 1.1 2004/02/26 02:25:42 agfitzp
19 * renamed packages to match xml & css
21 * Revision 1.1 2004/02/05 03:10:08 agfitzp
24 * Revision 1.1.2.1 2003/12/12 21:37:24 agfitzp
25 * Experimental work for Classes view
27 * Revision 1.6 2003/12/10 20:19:16 agfitzp
30 * Revision 1.5 2003/06/21 03:48:51 agfitzp
31 * fixed global variables as functions bug
32 * fixed length calculation of instance variables
33 * Automatic outlining is now a preference
35 * Revision 1.4 2003/05/30 20:53:09 agfitzp
36 * 0.0.2 : Outlining is now done as the user types. Some other bug fixes.
38 * Revision 1.3 2003/05/28 20:47:58 agfitzp
39 * Outline the document, not the file.
41 * Revision 1.2 2003/05/28 15:20:00 agfitzp
42 * Trivial change to test CVS commit
44 * Revision 1.1 2003/05/28 15:17:12 agfitzp
45 * net.sourceforge.phpeclipse.js.core 0.0.1 code base
47 *========================================================================
50 package net.sourceforge.phpeclipse.js.core.parser;
52 import java.io.ByteArrayOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.util.HashMap;
56 import java.util.LinkedList;
57 import java.util.List;
59 import net.sourceforge.phpeclipse.js.core.model.JSClassElement;
60 import net.sourceforge.phpeclipse.js.core.model.JSClassMethodElement;
61 import net.sourceforge.phpeclipse.js.core.model.JSClassVariableElement;
62 import net.sourceforge.phpeclipse.js.core.model.JSElement;
63 import net.sourceforge.phpeclipse.js.core.model.JSFunctionElement;
64 import net.sourceforge.phpeclipse.js.core.model.JSGlobalVariableElement;
65 import net.sourceforge.phpeclipse.js.core.model.JSInstanceMethodElement;
66 import net.sourceforge.phpeclipse.js.core.model.JSInstanceVariableElement;
68 import org.eclipse.core.resources.IFile;
69 import org.eclipse.jface.text.BadLocationException;
70 import org.eclipse.jface.text.Document;
71 import org.eclipse.jface.text.IDocument;
72 import org.eclipse.jface.text.rules.IToken;
82 public static final String FUNCTION = "function";
87 public static final String LINE_SEPARATOR = System.getProperty("line.separator");
90 * Array of system types to ignore.
92 private static String[] systemClassNames= {"Array","String"};
95 protected HashMap systemClassMap = new HashMap();
97 protected IFile sourceFile;
98 protected IDocument sourceDocument;
99 protected HashMap functions = new HashMap();
100 protected HashMap classes = new HashMap();
101 protected HashMap globalVariables = new HashMap();
102 protected List elementList = new LinkedList();
103 protected JSSyntaxScanner scanner = new JSSyntaxScanner();
106 * Constructor for JSParser.
114 for(i = 0;i < systemClassNames.length; i++)
116 String aName = systemClassNames[i];
117 systemClassMap.put(aName, aName);
122 * Returns a string containing the contents of the given file. Returns an empty string if there
123 * were any errors reading the file.
128 protected static String getText(IFile file)
132 InputStream in = file.getContents();
133 return streamToString(in);
134 } catch (Exception e)
141 protected static String streamToString(InputStream in) throws IOException
143 ByteArrayOutputStream out = new ByteArrayOutputStream();
144 byte[] buf = new byte[1024];
145 int read = in.read(buf);
149 out.write(buf, 0, read);
153 return out.toString();
157 * Skips ahead and finds next non-whitespace token.
160 public IToken nextNonWhitespaceToken()
162 IToken aToken = scanner.nextToken();
164 while (!aToken.isEOF() && aToken.isWhitespace())
166 aToken = scanner.nextToken();
173 * Parses the input given by the argument.
175 * @param file the element containing the input text
177 * @return an element collection representing the parsed input
179 public List parse(IFile file)
181 this.sourceFile = file;
182 return parse(new Document(getText(file)));
186 * Parses the input given by the argument.
188 * @param aSourceDocument the element containing the input text
190 * @return an element collection representing the parsed input
192 public List parse(IDocument aSourceDocument)
194 sourceDocument = aSourceDocument;
196 scanner.setRange(sourceDocument, 0, sourceDocument.getLength());
197 IToken token = scanner.nextToken();
198 while (!token.isEOF())
200 int offset = scanner.getTokenOffset();
201 int length = scanner.getTokenLength();
202 String expression = getExpression(offset, length);
204 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
206 addFunction(expression, offset, length);
209 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
211 //We need to check if the token is already a function or class
212 if (functions.containsKey(expression) || classes.containsKey(expression))
214 token = nextNonWhitespaceToken();
215 if (token.equals(JSSyntaxScanner.TOKEN_MEMBER))
217 detectInstanceMethod(offset, expression);
220 detectClassMethod(token, offset, expression);
224 if (expression.equals("var"))
226 detectGlobalVariable();
230 token = scanner.nextToken();
235 private void addFunction(String expression, int offset, int length)
237 String functionSignature = getNaked(expression);
238 int braceOffset = functionSignature.indexOf("(");
239 String functionName = functionSignature.substring(0, braceOffset).trim();
241 functionSignature.substring(functionSignature.indexOf("("), functionSignature.indexOf(")") + 1);
243 if (functionName.indexOf(".") >= 0)
245 //If the function signature includes .prototype. then it's a member.
246 if (functionName.indexOf(".prototype.") >= 0)
248 String className = functionName.substring(0, functionName.indexOf("."));
249 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
250 JSInstanceMethodElement aMethod =
251 this.addInstanceMethod(memberName, className, arguments, offset, offset, length);
252 detectInstanceMethodContext(className, aMethod);
255 String className = functionName.substring(0, functionName.indexOf("."));
256 if (functions.containsKey(className) || classes.containsKey(className))
258 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
259 JSFunctionElement aMethod =
260 this.addClassMethod(memberName, className, arguments, offset, offset, length);
265 if(! functions.containsKey(functionName))
267 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, length);
269 elementList.add(aFunction);
270 functions.put(functionName, aFunction);
272 detectFunctionContext(aFunction);
280 private void checkForSpecialGlobalTypes(JSGlobalVariableElement aVariable)
282 IToken token = nextNonWhitespaceToken();
285 if(!checkForDynamicClass(aVariable, token))
287 checkForAnonymousFunction(aVariable, token);
295 private boolean checkForDynamicClass(JSGlobalVariableElement aVariable, IToken rhsToken)
297 if (rhsToken.equals(JSSyntaxScanner.TOKEN_DEFAULT))
299 int offset = scanner.getTokenOffset();
300 int length = scanner.getTokenLength();
302 String expression = getExpression(offset, length);
304 if (expression.equals("new"))
306 IToken token = nextNonWhitespaceToken();
309 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
311 offset = scanner.getTokenOffset();
312 length = scanner.getTokenLength();
313 expression = getExpression(offset, length);
315 if(! isSystemClass(expression))
317 JSClassElement aClass = findOrCreateClass(aVariable.getName());
320 //Tell the class it's dynamically declared: what we will parse as class methods & vars are really instance methods & vars
321 aClass.setPrototype(true);
336 private boolean checkForAnonymousFunction(JSGlobalVariableElement aVariable, IToken rhsToken)
338 if (rhsToken.equals(JSSyntaxScanner.TOKEN_FUNCTION))
340 String functionName = aVariable.getName();
341 int offset = aVariable.getOffset();
342 int length = aVariable.getLength();
344 int functionOffset = scanner.getTokenOffset();
345 int functionLength = scanner.getTokenLength();
346 String functionSignature =
347 getExpression(functionOffset, functionLength);
348 String arguments = getArgumentString(functionSignature);
350 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, functionOffset - offset + functionLength);
352 elementList.add(aFunction);
353 functions.put(functionName, aFunction);
355 elementList.remove(aVariable);
356 globalVariables.remove(functionName);
358 detectFunctionContext(aFunction);
369 private String getExpression(int offset, int length)
373 expression = sourceDocument.get(offset, length);//sourceBuffer.substring(offset, offset + length);
374 } catch(BadLocationException e)
384 private void detectGlobalVariable()
390 token = nextNonWhitespaceToken();
393 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
395 int varOffset = scanner.getTokenOffset();
396 length = scanner.getTokenLength();
397 String variableName = getExpression(varOffset, length);
399 token = nextNonWhitespaceToken();
402 offset = scanner.getTokenOffset();
403 length = scanner.getTokenLength();
404 String expression = getExpression(offset, length);
405 if (expression.equals("="))
407 JSGlobalVariableElement aVariable = addGlobalVariable(variableName, varOffset);
408 if (aVariable!=null) {
409 checkForSpecialGlobalTypes(aVariable);
417 private void detectClassMethod(IToken token, int classOffset, String className)
419 int offset = scanner.getTokenOffset();
420 int length = scanner.getTokenLength();
421 String expression = getExpression(offset, length);
423 if (expression.equals("."))
426 token = nextNonWhitespaceToken();
429 offset = scanner.getTokenOffset();
430 length = scanner.getTokenLength();
431 String memberName = getExpression(offset, length);
433 token = nextNonWhitespaceToken();
436 offset = scanner.getTokenOffset();
437 length = scanner.getTokenLength();
438 expression = getExpression(offset, length);
439 if (expression.equals("="))
442 token = nextNonWhitespaceToken();
443 int tokenOffset = scanner.getTokenOffset();
444 int tokenLength = scanner.getTokenLength();
446 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
448 String functionSignature = getExpression(tokenOffset, tokenLength);
449 String arguments = getArgumentString(functionSignature);
451 JSFunctionElement aMethod =
452 addClassMethod(memberName, className, arguments, classOffset, tokenOffset, tokenLength);
457 addClassVariable(memberName, className, classOffset);
465 private String getArgumentString(String functionSignature)
467 return functionSignature.substring(
468 functionSignature.indexOf("("),
469 functionSignature.indexOf(")") + 1);
472 private void detectInstanceMethod(int classOffset, String className)
479 token = nextNonWhitespaceToken();
482 offset = scanner.getTokenOffset();
483 length = scanner.getTokenLength();
484 expression = getExpression(offset, length);
486 if (expression.equals("."))
489 token = nextNonWhitespaceToken();
492 offset = scanner.getTokenOffset();
493 length = scanner.getTokenLength();
494 String memberName = getExpression(offset, length);
496 token = nextNonWhitespaceToken();
499 offset = scanner.getTokenOffset();
500 length = scanner.getTokenLength();
501 expression = getExpression(offset, length);
502 if (expression.equals("="))
504 token = nextNonWhitespaceToken();
505 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
507 int functionOffset = scanner.getTokenOffset();
508 int functionLength = scanner.getTokenLength();
509 String functionSignature =
510 getExpression(functionOffset, functionLength);
511 String arguments = getArgumentString(functionSignature);
513 JSInstanceMethodElement aMethod =
522 detectInstanceMethodContext(className, aMethod);
526 addInstanceVariable(memberName, className, classOffset, (".prototype.").length());
536 private void parseInstanceMethodContext(String className, JSFunctionElement aMethod)
540 token = nextNonWhitespaceToken();
541 while (!token.isEOF())
543 int offset = scanner.getTokenOffset();
544 int length = scanner.getTokenLength();
545 String expression = getExpression(offset, length);
547 // if (token.equals(JSSyntaxScanner.TOKEN_END_CONTEXT))
548 if (expression.equals("}"))
551 } else if (expression.equals("{"))
553 parseInstanceMethodContext(className, aMethod);
554 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
556 if (expression.equals("this"))
558 handleThisReference(className, offset);
562 token = nextNonWhitespaceToken();
566 private void detectInstanceMethodContext(String className, JSFunctionElement aMethod)
570 token = nextNonWhitespaceToken();
571 while (!token.isEOF())
573 int offset = scanner.getTokenOffset();
574 int length = scanner.getTokenLength();
575 String expression = getExpression(offset, length);
577 // if (token.equals(JSSyntaxScanner.TOKEN_BEGIN_CONTEXT))
578 if (expression.equals("{"))
580 parseInstanceMethodContext(className, aMethod);
584 token = nextNonWhitespaceToken();
588 private void parseClassMethodContext(JSFunctionElement aMethod)
592 token = nextNonWhitespaceToken();
593 while (!token.isEOF())
595 int offset = scanner.getTokenOffset();
596 int length = scanner.getTokenLength();
597 String expression = getExpression(offset, length);
599 if (expression.equals("}"))
602 } else if (expression.equals("{"))
604 parseClassMethodContext(aMethod);
607 token = nextNonWhitespaceToken();
611 private void detectClassMethodContext(JSFunctionElement aMethod)
613 IToken token = nextNonWhitespaceToken();
614 while (!token.isEOF())
616 int offset = scanner.getTokenOffset();
617 int length = scanner.getTokenLength();
618 String expression = getExpression(offset, length);
620 if (expression.equals("{"))
622 parseClassMethodContext(aMethod);
626 token = nextNonWhitespaceToken();
630 private void handleThisReference(String className, int expressionStart)
632 IToken token = nextNonWhitespaceToken();
635 int offset = scanner.getTokenOffset();
636 int length = scanner.getTokenLength();
638 String expression = getExpression(offset, length);
640 if(expression.equals("."))
642 token = nextNonWhitespaceToken();
645 int memberStart = scanner.getTokenOffset();
646 length = scanner.getTokenLength();
648 String memberName = getExpression(memberStart, length);
650 token = nextNonWhitespaceToken();
653 offset = scanner.getTokenOffset();
654 length = scanner.getTokenLength();
655 expression = getExpression(offset, length);
657 if (expression.equals("="))
659 addInstanceVariable(memberName, className, expressionStart, 1 + 4 - className.length());
667 private void parseFunctionContext(JSFunctionElement aFunction)
671 token = nextNonWhitespaceToken();
672 while (!token.isEOF())
674 int offset = scanner.getTokenOffset();
675 int length = scanner.getTokenLength();
676 String expression = getExpression(offset, length);
678 if (expression.equals("}"))
681 } else if (expression.equals("{"))
683 parseFunctionContext(aFunction);
684 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
686 if (expression.equals("this"))
688 handleThisReference(aFunction.getName(), offset);
692 token = nextNonWhitespaceToken();
696 private void detectFunctionContext(JSFunctionElement aFunction)
698 IToken token = nextNonWhitespaceToken();
699 while (!token.isEOF())
701 int offset = scanner.getTokenOffset();
702 int length = scanner.getTokenLength();
703 String expression = getExpression(offset, length);
705 if (expression.equals("{"))
707 parseFunctionContext(aFunction);
711 token = nextNonWhitespaceToken();
715 private JSInstanceMethodElement addInstanceMethod(
723 int signatureLength = functionOffset - classOffset + functionLength;
724 JSInstanceMethodElement aMethod =
725 new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
727 findOrCreateClass(className).addChildElement(aMethod);
732 private JSFunctionElement addClassMethod(
740 JSClassElement aClass = findOrCreateClass(className);
741 int signatureLength = functionOffset - classOffset + functionLength;
742 JSFunctionElement aMethod;
744 if(aClass.isPrototype()) {
745 aMethod = new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
747 aClass.addChildElement(aMethod);
748 detectInstanceMethodContext(className, aMethod);
750 aMethod = new JSClassMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
752 aClass.addChildElement(aMethod);
753 detectClassMethodContext(aMethod);
765 private JSElement addClassVariable(String memberName, String className, int classOffset)
767 //One extra char for "."
769 JSClassElement aClass = findOrCreateClass(className);
771 if(aClass.isPrototype())
773 aVariable = new JSInstanceVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
776 aVariable = new JSClassVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
778 aClass.addChildElement(aVariable);
783 private JSInstanceVariableElement addInstanceVariable(
789 //11 extra chars for ".prototype."
790 JSInstanceVariableElement aVariable =
791 new JSInstanceVariableElement(
795 className.length() + memberName.length() + paddingWidth);
797 findOrCreateClass(className).addChildElement(aVariable);
802 private JSGlobalVariableElement addGlobalVariable(String variableName, int offset)
804 JSGlobalVariableElement aVariable;
805 if (!globalVariables.containsKey(variableName))
807 aVariable = new JSGlobalVariableElement(this.sourceFile, variableName, offset, variableName.length());
809 elementList.add(aVariable);
810 globalVariables.put(variableName, aVariable);
813 aVariable = (JSGlobalVariableElement) classes.get(variableName);
819 private JSClassElement findOrCreateClass(String className)
821 JSClassElement aClass = null;
822 if (!classes.containsKey(className))
824 if(functions.containsKey(className))
826 //if we're creating a class from an existing function we must
827 //migrate the existing function to become a constructor in the class.
828 JSFunctionElement constructor = (JSFunctionElement) functions.get(className);
830 aClass = new JSClassElement(this.sourceFile, className, constructor.getStart(), constructor.getLength());
831 aClass.addChildElement(constructor);
833 elementList.remove(constructor);
834 elementList.add(aClass);
835 classes.put(className, aClass);
836 } else if(globalVariables.containsKey(className))
838 //if we're creating a class from an existing global variable we must
839 //migrate the existing function to become a constructor in the class.
840 JSGlobalVariableElement aVariable = (JSGlobalVariableElement) globalVariables.get(className);
842 aClass = new JSClassElement(this.sourceFile, className, aVariable.getStart(), aVariable.getLength());
844 elementList.remove(aVariable);
845 elementList.add(aClass);
846 classes.put(className, aClass);
847 globalVariables.remove(className);
849 //The final case is if we have no idea where this class came from, but shouldn't be ignored.
850 aClass = new JSClassElement(this.sourceFile, className, 0, 0);
852 elementList.add(aClass);
853 classes.put(className, aClass);
857 aClass = (JSClassElement) classes.get(className);
863 public boolean isSystemClass(String aClassName)
865 return systemClassMap.containsKey(aClassName);
872 private String getNaked(String funcName)
874 if (funcName == null)
879 funcName = funcName.trim().substring(FUNCTION.length()).trim();
880 funcName = replaceInString(funcName.trim(), LINE_SEPARATOR, "");
882 StringBuffer strBuf = new StringBuffer("");
883 int len = funcName.length();
884 boolean wasSpace = false;
885 for (int i = 0; i < len; i++)
887 char ch = funcName.charAt(i);
901 return strBuf.toString();
905 * replace in a string a string sequence with another string sequence
907 public static String replaceInString(String source, String whatBefore, String whatAfter)
909 if (null == source || source.length() == 0)
913 int beforeLen = whatBefore.length();
918 StringBuffer result = new StringBuffer("");
920 int index = source.indexOf(whatBefore, lastIndex);
923 result.append(source.substring(lastIndex, index));
924 result.append(whatAfter);
925 lastIndex = index + beforeLen;
928 index = source.indexOf(whatBefore, lastIndex);
930 result.append(source.substring(lastIndex));
931 return result.toString();
935 * @return Returns the elementList.
937 public List getElementList() {