2 * $RCSfile: JSParser.java,v $
5 * CH-1700 Fribourg, Switzerland
8 *========================================================================
9 * Modifications history
10 *========================================================================
11 * $Log: not supported by cvs2svn $
12 * Revision 1.1 2004/02/26 02:25:42 agfitzp
13 * renamed packages to match xml & css
15 * Revision 1.1 2004/02/05 03:10:08 agfitzp
18 * Revision 1.1.2.1 2003/12/12 21:37:24 agfitzp
19 * Experimental work for Classes view
21 * Revision 1.6 2003/12/10 20:19:16 agfitzp
24 * Revision 1.5 2003/06/21 03:48:51 agfitzp
25 * fixed global variables as functions bug
26 * fixed length calculation of instance variables
27 * Automatic outlining is now a preference
29 * Revision 1.4 2003/05/30 20:53:09 agfitzp
30 * 0.0.2 : Outlining is now done as the user types. Some other bug fixes.
32 * Revision 1.3 2003/05/28 20:47:58 agfitzp
33 * Outline the document, not the file.
35 * Revision 1.2 2003/05/28 15:20:00 agfitzp
36 * Trivial change to test CVS commit
38 * Revision 1.1 2003/05/28 15:17:12 agfitzp
39 * net.sourceforge.phpeclipse.js.core 0.0.1 code base
41 *========================================================================
44 package net.sourceforge.phpeclipse.js.core.parser;
46 import java.io.ByteArrayOutputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.util.HashMap;
50 import java.util.LinkedList;
51 import java.util.List;
53 import net.sourceforge.phpeclipse.js.core.model.*;
55 import org.eclipse.core.resources.IFile;
56 import org.eclipse.jface.text.BadLocationException;
57 import org.eclipse.jface.text.Document;
58 import org.eclipse.jface.text.IDocument;
59 import org.eclipse.jface.text.rules.IToken;
69 public static final String FUNCTION = "function";
74 public static final String LINE_SEPARATOR = System.getProperty("line.separator");
77 * Array of system types to ignore.
79 private static String[] systemClassNames= {"Array","String"};
82 protected HashMap systemClassMap = new HashMap();
84 protected IFile sourceFile;
85 protected IDocument sourceDocument;
86 protected HashMap functions = new HashMap();
87 protected HashMap classes = new HashMap();
88 protected HashMap globalVariables = new HashMap();
89 protected List elementList = new LinkedList();
90 protected JSSyntaxScanner scanner = new JSSyntaxScanner();
93 * Constructor for JSParser.
101 for(i = 0;i < systemClassNames.length; i++)
103 String aName = systemClassNames[i];
104 systemClassMap.put(aName, aName);
109 * Returns a string containing the contents of the given file. Returns an empty string if there
110 * were any errors reading the file.
115 protected static String getText(IFile file)
119 InputStream in = file.getContents();
120 return streamToString(in);
121 } catch (Exception e)
128 protected static String streamToString(InputStream in) throws IOException
130 ByteArrayOutputStream out = new ByteArrayOutputStream();
131 byte[] buf = new byte[1024];
132 int read = in.read(buf);
136 out.write(buf, 0, read);
140 return out.toString();
144 * Skips ahead and finds next non-whitespace token.
147 public IToken nextNonWhitespaceToken()
149 IToken aToken = scanner.nextToken();
151 while (!aToken.isEOF() && aToken.isWhitespace())
153 aToken = scanner.nextToken();
160 * Parses the input given by the argument.
162 * @param file the element containing the input text
164 * @return an element collection representing the parsed input
166 public List parse(IFile file)
168 this.sourceFile = file;
169 return parse(new Document(getText(file)));
173 * Parses the input given by the argument.
175 * @param aSourceDocument the element containing the input text
177 * @return an element collection representing the parsed input
179 public List parse(IDocument aSourceDocument)
181 sourceDocument = aSourceDocument;
183 scanner.setRange(sourceDocument, 0, sourceDocument.getLength());
184 IToken token = scanner.nextToken();
185 while (!token.isEOF())
187 int offset = scanner.getTokenOffset();
188 int length = scanner.getTokenLength();
189 String expression = getExpression(offset, length);
191 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
193 addFunction(expression, offset, length);
196 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
198 //We need to check if the token is already a function or class
199 if (functions.containsKey(expression) || classes.containsKey(expression))
201 token = nextNonWhitespaceToken();
202 if (token.equals(JSSyntaxScanner.TOKEN_MEMBER))
204 detectInstanceMethod(offset, expression);
207 detectClassMethod(token, offset, expression);
211 if (expression.equals("var"))
213 detectGlobalVariable();
217 token = scanner.nextToken();
222 private void addFunction(String expression, int offset, int length)
224 String functionSignature = getNaked(expression);
225 int braceOffset = functionSignature.indexOf("(");
226 String functionName = functionSignature.substring(0, braceOffset).trim();
228 functionSignature.substring(functionSignature.indexOf("("), functionSignature.indexOf(")") + 1);
230 if (functionName.indexOf(".") >= 0)
232 //If the function signature includes .prototype. then it's a member.
233 if (functionName.indexOf(".prototype.") >= 0)
235 String className = functionName.substring(0, functionName.indexOf("."));
236 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
237 JSInstanceMethodElement aMethod =
238 this.addInstanceMethod(memberName, className, arguments, offset, offset, length);
239 detectInstanceMethodContext(className, aMethod);
242 String className = functionName.substring(0, functionName.indexOf("."));
243 if (functions.containsKey(className) || classes.containsKey(className))
245 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
246 JSFunctionElement aMethod =
247 this.addClassMethod(memberName, className, arguments, offset, offset, length);
252 if(! functions.containsKey(functionName))
254 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, length);
256 elementList.add(aFunction);
257 functions.put(functionName, aFunction);
259 detectFunctionContext(aFunction);
267 private void checkForSpecialGlobalTypes(JSGlobalVariableElement aVariable)
269 IToken token = nextNonWhitespaceToken();
272 if(!checkForDynamicClass(aVariable, token))
274 checkForAnonymousFunction(aVariable, token);
282 private boolean checkForDynamicClass(JSGlobalVariableElement aVariable, IToken rhsToken)
284 if (rhsToken.equals(JSSyntaxScanner.TOKEN_DEFAULT))
286 int offset = scanner.getTokenOffset();
287 int length = scanner.getTokenLength();
289 String expression = getExpression(offset, length);
291 if (expression.equals("new"))
293 IToken token = nextNonWhitespaceToken();
296 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
298 offset = scanner.getTokenOffset();
299 length = scanner.getTokenLength();
300 expression = getExpression(offset, length);
302 if(! isSystemClass(expression))
304 JSClassElement aClass = findOrCreateClass(aVariable.getName());
307 //Tell the class it's dynamically declared: what we will parse as class methods & vars are really instance methods & vars
308 aClass.setPrototype(true);
323 private boolean checkForAnonymousFunction(JSGlobalVariableElement aVariable, IToken rhsToken)
325 if (rhsToken.equals(JSSyntaxScanner.TOKEN_FUNCTION))
327 String functionName = aVariable.getName();
328 int offset = aVariable.getOffset();
329 int length = aVariable.getLength();
331 int functionOffset = scanner.getTokenOffset();
332 int functionLength = scanner.getTokenLength();
333 String functionSignature =
334 getExpression(functionOffset, functionLength);
335 String arguments = getArgumentString(functionSignature);
337 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, functionOffset - offset + functionLength);
339 elementList.add(aFunction);
340 functions.put(functionName, aFunction);
342 elementList.remove(aVariable);
343 globalVariables.remove(functionName);
345 detectFunctionContext(aFunction);
356 private String getExpression(int offset, int length)
360 expression = sourceDocument.get(offset, length);//sourceBuffer.substring(offset, offset + length);
361 } catch(BadLocationException e)
371 private void detectGlobalVariable()
377 token = nextNonWhitespaceToken();
380 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
382 int varOffset = scanner.getTokenOffset();
383 length = scanner.getTokenLength();
384 String variableName = getExpression(varOffset, length);
386 token = nextNonWhitespaceToken();
389 offset = scanner.getTokenOffset();
390 length = scanner.getTokenLength();
391 String expression = getExpression(offset, length);
392 if (expression.equals("="))
394 JSGlobalVariableElement aVariable = addGlobalVariable(variableName, varOffset);
395 checkForSpecialGlobalTypes(aVariable);
402 private void detectClassMethod(IToken token, int classOffset, String className)
404 int offset = scanner.getTokenOffset();
405 int length = scanner.getTokenLength();
406 String expression = getExpression(offset, length);
408 if (expression.equals("."))
411 token = nextNonWhitespaceToken();
414 offset = scanner.getTokenOffset();
415 length = scanner.getTokenLength();
416 String memberName = getExpression(offset, length);
418 token = nextNonWhitespaceToken();
421 offset = scanner.getTokenOffset();
422 length = scanner.getTokenLength();
423 expression = getExpression(offset, length);
424 if (expression.equals("="))
427 token = nextNonWhitespaceToken();
428 int tokenOffset = scanner.getTokenOffset();
429 int tokenLength = scanner.getTokenLength();
431 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
433 String functionSignature = getExpression(tokenOffset, tokenLength);
434 String arguments = getArgumentString(functionSignature);
436 JSFunctionElement aMethod =
437 addClassMethod(memberName, className, arguments, classOffset, tokenOffset, tokenLength);
442 addClassVariable(memberName, className, classOffset);
450 private String getArgumentString(String functionSignature)
452 return functionSignature.substring(
453 functionSignature.indexOf("("),
454 functionSignature.indexOf(")") + 1);
457 private void detectInstanceMethod(int classOffset, String className)
464 token = nextNonWhitespaceToken();
467 offset = scanner.getTokenOffset();
468 length = scanner.getTokenLength();
469 expression = getExpression(offset, length);
471 if (expression.equals("."))
474 token = nextNonWhitespaceToken();
477 offset = scanner.getTokenOffset();
478 length = scanner.getTokenLength();
479 String memberName = getExpression(offset, length);
481 token = nextNonWhitespaceToken();
484 offset = scanner.getTokenOffset();
485 length = scanner.getTokenLength();
486 expression = getExpression(offset, length);
487 if (expression.equals("="))
489 token = nextNonWhitespaceToken();
490 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
492 int functionOffset = scanner.getTokenOffset();
493 int functionLength = scanner.getTokenLength();
494 String functionSignature =
495 getExpression(functionOffset, functionLength);
496 String arguments = getArgumentString(functionSignature);
498 JSInstanceMethodElement aMethod =
507 detectInstanceMethodContext(className, aMethod);
511 addInstanceVariable(memberName, className, classOffset, (".prototype.").length());
521 private void parseInstanceMethodContext(String className, JSFunctionElement aMethod)
525 token = nextNonWhitespaceToken();
526 while (!token.isEOF())
528 int offset = scanner.getTokenOffset();
529 int length = scanner.getTokenLength();
530 String expression = getExpression(offset, length);
532 // if (token.equals(JSSyntaxScanner.TOKEN_END_CONTEXT))
533 if (expression.equals("}"))
536 } else if (expression.equals("{"))
538 parseInstanceMethodContext(className, aMethod);
539 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
541 if (expression.equals("this"))
543 handleThisReference(className, offset);
547 token = nextNonWhitespaceToken();
551 private void detectInstanceMethodContext(String className, JSFunctionElement aMethod)
555 token = nextNonWhitespaceToken();
556 while (!token.isEOF())
558 int offset = scanner.getTokenOffset();
559 int length = scanner.getTokenLength();
560 String expression = getExpression(offset, length);
562 // if (token.equals(JSSyntaxScanner.TOKEN_BEGIN_CONTEXT))
563 if (expression.equals("{"))
565 parseInstanceMethodContext(className, aMethod);
569 token = nextNonWhitespaceToken();
573 private void parseClassMethodContext(JSFunctionElement aMethod)
577 token = nextNonWhitespaceToken();
578 while (!token.isEOF())
580 int offset = scanner.getTokenOffset();
581 int length = scanner.getTokenLength();
582 String expression = getExpression(offset, length);
584 if (expression.equals("}"))
587 } else if (expression.equals("{"))
589 parseClassMethodContext(aMethod);
592 token = nextNonWhitespaceToken();
596 private void detectClassMethodContext(JSFunctionElement aMethod)
598 IToken token = nextNonWhitespaceToken();
599 while (!token.isEOF())
601 int offset = scanner.getTokenOffset();
602 int length = scanner.getTokenLength();
603 String expression = getExpression(offset, length);
605 if (expression.equals("{"))
607 parseClassMethodContext(aMethod);
611 token = nextNonWhitespaceToken();
615 private void handleThisReference(String className, int expressionStart)
617 IToken token = nextNonWhitespaceToken();
620 int offset = scanner.getTokenOffset();
621 int length = scanner.getTokenLength();
623 String expression = getExpression(offset, length);
625 if(expression.equals("."))
627 token = nextNonWhitespaceToken();
630 int memberStart = scanner.getTokenOffset();
631 length = scanner.getTokenLength();
633 String memberName = getExpression(memberStart, length);
635 token = nextNonWhitespaceToken();
638 offset = scanner.getTokenOffset();
639 length = scanner.getTokenLength();
640 expression = getExpression(offset, length);
642 if (expression.equals("="))
644 addInstanceVariable(memberName, className, expressionStart, 1 + 4 - className.length());
652 private void parseFunctionContext(JSFunctionElement aFunction)
656 token = nextNonWhitespaceToken();
657 while (!token.isEOF())
659 int offset = scanner.getTokenOffset();
660 int length = scanner.getTokenLength();
661 String expression = getExpression(offset, length);
663 if (expression.equals("}"))
666 } else if (expression.equals("{"))
668 parseFunctionContext(aFunction);
669 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
671 if (expression.equals("this"))
673 handleThisReference(aFunction.getName(), offset);
677 token = nextNonWhitespaceToken();
681 private void detectFunctionContext(JSFunctionElement aFunction)
683 IToken token = nextNonWhitespaceToken();
684 while (!token.isEOF())
686 int offset = scanner.getTokenOffset();
687 int length = scanner.getTokenLength();
688 String expression = getExpression(offset, length);
690 if (expression.equals("{"))
692 parseFunctionContext(aFunction);
696 token = nextNonWhitespaceToken();
700 private JSInstanceMethodElement addInstanceMethod(
708 int signatureLength = functionOffset - classOffset + functionLength;
709 JSInstanceMethodElement aMethod =
710 new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
712 findOrCreateClass(className).addChildElement(aMethod);
717 private JSFunctionElement addClassMethod(
725 JSClassElement aClass = findOrCreateClass(className);
726 int signatureLength = functionOffset - classOffset + functionLength;
727 JSFunctionElement aMethod;
729 if(aClass.isPrototype()) {
730 aMethod = new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
732 aClass.addChildElement(aMethod);
733 detectInstanceMethodContext(className, aMethod);
735 aMethod = new JSClassMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
737 aClass.addChildElement(aMethod);
738 detectClassMethodContext(aMethod);
750 private JSElement addClassVariable(String memberName, String className, int classOffset)
752 //One extra char for "."
754 JSClassElement aClass = findOrCreateClass(className);
756 if(aClass.isPrototype())
758 aVariable = new JSInstanceVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
761 aVariable = new JSClassVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
763 aClass.addChildElement(aVariable);
768 private JSInstanceVariableElement addInstanceVariable(
774 //11 extra chars for ".prototype."
775 JSInstanceVariableElement aVariable =
776 new JSInstanceVariableElement(
780 className.length() + memberName.length() + paddingWidth);
782 findOrCreateClass(className).addChildElement(aVariable);
787 private JSGlobalVariableElement addGlobalVariable(String variableName, int offset)
789 JSGlobalVariableElement aVariable;
790 if (!globalVariables.containsKey(variableName))
792 aVariable = new JSGlobalVariableElement(this.sourceFile, variableName, offset, variableName.length());
794 elementList.add(aVariable);
795 globalVariables.put(variableName, aVariable);
798 aVariable = (JSGlobalVariableElement) classes.get(variableName);
804 private JSClassElement findOrCreateClass(String className)
806 JSClassElement aClass = null;
807 if (!classes.containsKey(className))
809 if(functions.containsKey(className))
811 //if we're creating a class from an existing function we must
812 //migrate the existing function to become a constructor in the class.
813 JSFunctionElement constructor = (JSFunctionElement) functions.get(className);
815 aClass = new JSClassElement(this.sourceFile, className, constructor.getStart(), constructor.getLength());
816 aClass.addChildElement(constructor);
818 elementList.remove(constructor);
819 elementList.add(aClass);
820 classes.put(className, aClass);
821 } else if(globalVariables.containsKey(className))
823 //if we're creating a class from an existing global variable we must
824 //migrate the existing function to become a constructor in the class.
825 JSGlobalVariableElement aVariable = (JSGlobalVariableElement) globalVariables.get(className);
827 aClass = new JSClassElement(this.sourceFile, className, aVariable.getStart(), aVariable.getLength());
829 elementList.remove(aVariable);
830 elementList.add(aClass);
831 classes.put(className, aClass);
832 globalVariables.remove(className);
834 //The final case is if we have no idea where this class came from, but shouldn't be ignored.
835 aClass = new JSClassElement(this.sourceFile, className, 0, 0);
837 elementList.add(aClass);
838 classes.put(className, aClass);
842 aClass = (JSClassElement) classes.get(className);
848 public boolean isSystemClass(String aClassName)
850 return systemClassMap.containsKey(aClassName);
857 private String getNaked(String funcName)
859 if (funcName == null)
864 funcName = funcName.trim().substring(FUNCTION.length()).trim();
865 funcName = replaceInString(funcName.trim(), LINE_SEPARATOR, "");
867 StringBuffer strBuf = new StringBuffer("");
868 int len = funcName.length();
869 boolean wasSpace = false;
870 for (int i = 0; i < len; i++)
872 char ch = funcName.charAt(i);
886 return strBuf.toString();
890 * replace in a string a string sequence with another string sequence
892 public static String replaceInString(String source, String whatBefore, String whatAfter)
894 if (null == source || source.length() == 0)
898 int beforeLen = whatBefore.length();
903 StringBuffer result = new StringBuffer("");
905 int index = source.indexOf(whatBefore, lastIndex);
908 result.append(source.substring(lastIndex, index));
909 result.append(whatAfter);
910 lastIndex = index + beforeLen;
913 index = source.indexOf(whatBefore, lastIndex);
915 result.append(source.substring(lastIndex));
916 return result.toString();
920 * @return Returns the elementList.
922 public List getElementList() {