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/09/02 18:14:38 jsurfer
13 * intial source from ttp://www.sf.net/projects/wdte
15 * Revision 1.1 2004/02/26 02:25:42 agfitzp
16 * renamed packages to match xml & css
18 * Revision 1.1 2004/02/05 03:10:08 agfitzp
21 * Revision 1.1.2.1 2003/12/12 21:37:24 agfitzp
22 * Experimental work for Classes view
24 * Revision 1.6 2003/12/10 20:19:16 agfitzp
27 * Revision 1.5 2003/06/21 03:48:51 agfitzp
28 * fixed global variables as functions bug
29 * fixed length calculation of instance variables
30 * Automatic outlining is now a preference
32 * Revision 1.4 2003/05/30 20:53:09 agfitzp
33 * 0.0.2 : Outlining is now done as the user types. Some other bug fixes.
35 * Revision 1.3 2003/05/28 20:47:58 agfitzp
36 * Outline the document, not the file.
38 * Revision 1.2 2003/05/28 15:20:00 agfitzp
39 * Trivial change to test CVS commit
41 * Revision 1.1 2003/05/28 15:17:12 agfitzp
42 * net.sourceforge.phpeclipse.js.core 0.0.1 code base
44 *========================================================================
47 package net.sourceforge.phpeclipse.js.core.parser;
49 import java.io.ByteArrayOutputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.util.HashMap;
53 import java.util.LinkedList;
54 import java.util.List;
56 import net.sourceforge.phpeclipse.js.core.model.*;
58 import org.eclipse.core.resources.IFile;
59 import org.eclipse.jface.text.BadLocationException;
60 import org.eclipse.jface.text.Document;
61 import org.eclipse.jface.text.IDocument;
62 import org.eclipse.jface.text.rules.IToken;
72 public static final String FUNCTION = "function";
77 public static final String LINE_SEPARATOR = System.getProperty("line.separator");
80 * Array of system types to ignore.
82 private static String[] systemClassNames= {"Array","String"};
85 protected HashMap systemClassMap = new HashMap();
87 protected IFile sourceFile;
88 protected IDocument sourceDocument;
89 protected HashMap functions = new HashMap();
90 protected HashMap classes = new HashMap();
91 protected HashMap globalVariables = new HashMap();
92 protected List elementList = new LinkedList();
93 protected JSSyntaxScanner scanner = new JSSyntaxScanner();
96 * Constructor for JSParser.
104 for(i = 0;i < systemClassNames.length; i++)
106 String aName = systemClassNames[i];
107 systemClassMap.put(aName, aName);
112 * Returns a string containing the contents of the given file. Returns an empty string if there
113 * were any errors reading the file.
118 protected static String getText(IFile file)
122 InputStream in = file.getContents();
123 return streamToString(in);
124 } catch (Exception e)
131 protected static String streamToString(InputStream in) throws IOException
133 ByteArrayOutputStream out = new ByteArrayOutputStream();
134 byte[] buf = new byte[1024];
135 int read = in.read(buf);
139 out.write(buf, 0, read);
143 return out.toString();
147 * Skips ahead and finds next non-whitespace token.
150 public IToken nextNonWhitespaceToken()
152 IToken aToken = scanner.nextToken();
154 while (!aToken.isEOF() && aToken.isWhitespace())
156 aToken = scanner.nextToken();
163 * Parses the input given by the argument.
165 * @param file the element containing the input text
167 * @return an element collection representing the parsed input
169 public List parse(IFile file)
171 this.sourceFile = file;
172 return parse(new Document(getText(file)));
176 * Parses the input given by the argument.
178 * @param aSourceDocument the element containing the input text
180 * @return an element collection representing the parsed input
182 public List parse(IDocument aSourceDocument)
184 sourceDocument = aSourceDocument;
186 scanner.setRange(sourceDocument, 0, sourceDocument.getLength());
187 IToken token = scanner.nextToken();
188 while (!token.isEOF())
190 int offset = scanner.getTokenOffset();
191 int length = scanner.getTokenLength();
192 String expression = getExpression(offset, length);
194 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
196 addFunction(expression, offset, length);
199 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
201 //We need to check if the token is already a function or class
202 if (functions.containsKey(expression) || classes.containsKey(expression))
204 token = nextNonWhitespaceToken();
205 if (token.equals(JSSyntaxScanner.TOKEN_MEMBER))
207 detectInstanceMethod(offset, expression);
210 detectClassMethod(token, offset, expression);
214 if (expression.equals("var"))
216 detectGlobalVariable();
220 token = scanner.nextToken();
225 private void addFunction(String expression, int offset, int length)
227 String functionSignature = getNaked(expression);
228 int braceOffset = functionSignature.indexOf("(");
229 String functionName = functionSignature.substring(0, braceOffset).trim();
231 functionSignature.substring(functionSignature.indexOf("("), functionSignature.indexOf(")") + 1);
233 if (functionName.indexOf(".") >= 0)
235 //If the function signature includes .prototype. then it's a member.
236 if (functionName.indexOf(".prototype.") >= 0)
238 String className = functionName.substring(0, functionName.indexOf("."));
239 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
240 JSInstanceMethodElement aMethod =
241 this.addInstanceMethod(memberName, className, arguments, offset, offset, length);
242 detectInstanceMethodContext(className, aMethod);
245 String className = functionName.substring(0, functionName.indexOf("."));
246 if (functions.containsKey(className) || classes.containsKey(className))
248 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
249 JSFunctionElement aMethod =
250 this.addClassMethod(memberName, className, arguments, offset, offset, length);
255 if(! functions.containsKey(functionName))
257 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, length);
259 elementList.add(aFunction);
260 functions.put(functionName, aFunction);
262 detectFunctionContext(aFunction);
270 private void checkForSpecialGlobalTypes(JSGlobalVariableElement aVariable)
272 IToken token = nextNonWhitespaceToken();
275 if(!checkForDynamicClass(aVariable, token))
277 checkForAnonymousFunction(aVariable, token);
285 private boolean checkForDynamicClass(JSGlobalVariableElement aVariable, IToken rhsToken)
287 if (rhsToken.equals(JSSyntaxScanner.TOKEN_DEFAULT))
289 int offset = scanner.getTokenOffset();
290 int length = scanner.getTokenLength();
292 String expression = getExpression(offset, length);
294 if (expression.equals("new"))
296 IToken token = nextNonWhitespaceToken();
299 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
301 offset = scanner.getTokenOffset();
302 length = scanner.getTokenLength();
303 expression = getExpression(offset, length);
305 if(! isSystemClass(expression))
307 JSClassElement aClass = findOrCreateClass(aVariable.getName());
310 //Tell the class it's dynamically declared: what we will parse as class methods & vars are really instance methods & vars
311 aClass.setPrototype(true);
326 private boolean checkForAnonymousFunction(JSGlobalVariableElement aVariable, IToken rhsToken)
328 if (rhsToken.equals(JSSyntaxScanner.TOKEN_FUNCTION))
330 String functionName = aVariable.getName();
331 int offset = aVariable.getOffset();
332 int length = aVariable.getLength();
334 int functionOffset = scanner.getTokenOffset();
335 int functionLength = scanner.getTokenLength();
336 String functionSignature =
337 getExpression(functionOffset, functionLength);
338 String arguments = getArgumentString(functionSignature);
340 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, functionOffset - offset + functionLength);
342 elementList.add(aFunction);
343 functions.put(functionName, aFunction);
345 elementList.remove(aVariable);
346 globalVariables.remove(functionName);
348 detectFunctionContext(aFunction);
359 private String getExpression(int offset, int length)
363 expression = sourceDocument.get(offset, length);//sourceBuffer.substring(offset, offset + length);
364 } catch(BadLocationException e)
374 private void detectGlobalVariable()
380 token = nextNonWhitespaceToken();
383 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
385 int varOffset = scanner.getTokenOffset();
386 length = scanner.getTokenLength();
387 String variableName = getExpression(varOffset, length);
389 token = nextNonWhitespaceToken();
392 offset = scanner.getTokenOffset();
393 length = scanner.getTokenLength();
394 String expression = getExpression(offset, length);
395 if (expression.equals("="))
397 JSGlobalVariableElement aVariable = addGlobalVariable(variableName, varOffset);
398 if (aVariable!=null) {
399 checkForSpecialGlobalTypes(aVariable);
407 private void detectClassMethod(IToken token, int classOffset, String className)
409 int offset = scanner.getTokenOffset();
410 int length = scanner.getTokenLength();
411 String expression = getExpression(offset, length);
413 if (expression.equals("."))
416 token = nextNonWhitespaceToken();
419 offset = scanner.getTokenOffset();
420 length = scanner.getTokenLength();
421 String memberName = getExpression(offset, length);
423 token = nextNonWhitespaceToken();
426 offset = scanner.getTokenOffset();
427 length = scanner.getTokenLength();
428 expression = getExpression(offset, length);
429 if (expression.equals("="))
432 token = nextNonWhitespaceToken();
433 int tokenOffset = scanner.getTokenOffset();
434 int tokenLength = scanner.getTokenLength();
436 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
438 String functionSignature = getExpression(tokenOffset, tokenLength);
439 String arguments = getArgumentString(functionSignature);
441 JSFunctionElement aMethod =
442 addClassMethod(memberName, className, arguments, classOffset, tokenOffset, tokenLength);
447 addClassVariable(memberName, className, classOffset);
455 private String getArgumentString(String functionSignature)
457 return functionSignature.substring(
458 functionSignature.indexOf("("),
459 functionSignature.indexOf(")") + 1);
462 private void detectInstanceMethod(int classOffset, String className)
469 token = nextNonWhitespaceToken();
472 offset = scanner.getTokenOffset();
473 length = scanner.getTokenLength();
474 expression = getExpression(offset, length);
476 if (expression.equals("."))
479 token = nextNonWhitespaceToken();
482 offset = scanner.getTokenOffset();
483 length = scanner.getTokenLength();
484 String memberName = getExpression(offset, length);
486 token = nextNonWhitespaceToken();
489 offset = scanner.getTokenOffset();
490 length = scanner.getTokenLength();
491 expression = getExpression(offset, length);
492 if (expression.equals("="))
494 token = nextNonWhitespaceToken();
495 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
497 int functionOffset = scanner.getTokenOffset();
498 int functionLength = scanner.getTokenLength();
499 String functionSignature =
500 getExpression(functionOffset, functionLength);
501 String arguments = getArgumentString(functionSignature);
503 JSInstanceMethodElement aMethod =
512 detectInstanceMethodContext(className, aMethod);
516 addInstanceVariable(memberName, className, classOffset, (".prototype.").length());
526 private void parseInstanceMethodContext(String className, JSFunctionElement aMethod)
530 token = nextNonWhitespaceToken();
531 while (!token.isEOF())
533 int offset = scanner.getTokenOffset();
534 int length = scanner.getTokenLength();
535 String expression = getExpression(offset, length);
537 // if (token.equals(JSSyntaxScanner.TOKEN_END_CONTEXT))
538 if (expression.equals("}"))
541 } else if (expression.equals("{"))
543 parseInstanceMethodContext(className, aMethod);
544 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
546 if (expression.equals("this"))
548 handleThisReference(className, offset);
552 token = nextNonWhitespaceToken();
556 private void detectInstanceMethodContext(String className, JSFunctionElement aMethod)
560 token = nextNonWhitespaceToken();
561 while (!token.isEOF())
563 int offset = scanner.getTokenOffset();
564 int length = scanner.getTokenLength();
565 String expression = getExpression(offset, length);
567 // if (token.equals(JSSyntaxScanner.TOKEN_BEGIN_CONTEXT))
568 if (expression.equals("{"))
570 parseInstanceMethodContext(className, aMethod);
574 token = nextNonWhitespaceToken();
578 private void parseClassMethodContext(JSFunctionElement aMethod)
582 token = nextNonWhitespaceToken();
583 while (!token.isEOF())
585 int offset = scanner.getTokenOffset();
586 int length = scanner.getTokenLength();
587 String expression = getExpression(offset, length);
589 if (expression.equals("}"))
592 } else if (expression.equals("{"))
594 parseClassMethodContext(aMethod);
597 token = nextNonWhitespaceToken();
601 private void detectClassMethodContext(JSFunctionElement aMethod)
603 IToken token = nextNonWhitespaceToken();
604 while (!token.isEOF())
606 int offset = scanner.getTokenOffset();
607 int length = scanner.getTokenLength();
608 String expression = getExpression(offset, length);
610 if (expression.equals("{"))
612 parseClassMethodContext(aMethod);
616 token = nextNonWhitespaceToken();
620 private void handleThisReference(String className, int expressionStart)
622 IToken token = nextNonWhitespaceToken();
625 int offset = scanner.getTokenOffset();
626 int length = scanner.getTokenLength();
628 String expression = getExpression(offset, length);
630 if(expression.equals("."))
632 token = nextNonWhitespaceToken();
635 int memberStart = scanner.getTokenOffset();
636 length = scanner.getTokenLength();
638 String memberName = getExpression(memberStart, length);
640 token = nextNonWhitespaceToken();
643 offset = scanner.getTokenOffset();
644 length = scanner.getTokenLength();
645 expression = getExpression(offset, length);
647 if (expression.equals("="))
649 addInstanceVariable(memberName, className, expressionStart, 1 + 4 - className.length());
657 private void parseFunctionContext(JSFunctionElement aFunction)
661 token = nextNonWhitespaceToken();
662 while (!token.isEOF())
664 int offset = scanner.getTokenOffset();
665 int length = scanner.getTokenLength();
666 String expression = getExpression(offset, length);
668 if (expression.equals("}"))
671 } else if (expression.equals("{"))
673 parseFunctionContext(aFunction);
674 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
676 if (expression.equals("this"))
678 handleThisReference(aFunction.getName(), offset);
682 token = nextNonWhitespaceToken();
686 private void detectFunctionContext(JSFunctionElement aFunction)
688 IToken token = nextNonWhitespaceToken();
689 while (!token.isEOF())
691 int offset = scanner.getTokenOffset();
692 int length = scanner.getTokenLength();
693 String expression = getExpression(offset, length);
695 if (expression.equals("{"))
697 parseFunctionContext(aFunction);
701 token = nextNonWhitespaceToken();
705 private JSInstanceMethodElement addInstanceMethod(
713 int signatureLength = functionOffset - classOffset + functionLength;
714 JSInstanceMethodElement aMethod =
715 new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
717 findOrCreateClass(className).addChildElement(aMethod);
722 private JSFunctionElement addClassMethod(
730 JSClassElement aClass = findOrCreateClass(className);
731 int signatureLength = functionOffset - classOffset + functionLength;
732 JSFunctionElement aMethod;
734 if(aClass.isPrototype()) {
735 aMethod = new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
737 aClass.addChildElement(aMethod);
738 detectInstanceMethodContext(className, aMethod);
740 aMethod = new JSClassMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
742 aClass.addChildElement(aMethod);
743 detectClassMethodContext(aMethod);
755 private JSElement addClassVariable(String memberName, String className, int classOffset)
757 //One extra char for "."
759 JSClassElement aClass = findOrCreateClass(className);
761 if(aClass.isPrototype())
763 aVariable = new JSInstanceVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
766 aVariable = new JSClassVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
768 aClass.addChildElement(aVariable);
773 private JSInstanceVariableElement addInstanceVariable(
779 //11 extra chars for ".prototype."
780 JSInstanceVariableElement aVariable =
781 new JSInstanceVariableElement(
785 className.length() + memberName.length() + paddingWidth);
787 findOrCreateClass(className).addChildElement(aVariable);
792 private JSGlobalVariableElement addGlobalVariable(String variableName, int offset)
794 JSGlobalVariableElement aVariable;
795 if (!globalVariables.containsKey(variableName))
797 aVariable = new JSGlobalVariableElement(this.sourceFile, variableName, offset, variableName.length());
799 elementList.add(aVariable);
800 globalVariables.put(variableName, aVariable);
803 aVariable = (JSGlobalVariableElement) classes.get(variableName);
809 private JSClassElement findOrCreateClass(String className)
811 JSClassElement aClass = null;
812 if (!classes.containsKey(className))
814 if(functions.containsKey(className))
816 //if we're creating a class from an existing function we must
817 //migrate the existing function to become a constructor in the class.
818 JSFunctionElement constructor = (JSFunctionElement) functions.get(className);
820 aClass = new JSClassElement(this.sourceFile, className, constructor.getStart(), constructor.getLength());
821 aClass.addChildElement(constructor);
823 elementList.remove(constructor);
824 elementList.add(aClass);
825 classes.put(className, aClass);
826 } else if(globalVariables.containsKey(className))
828 //if we're creating a class from an existing global variable we must
829 //migrate the existing function to become a constructor in the class.
830 JSGlobalVariableElement aVariable = (JSGlobalVariableElement) globalVariables.get(className);
832 aClass = new JSClassElement(this.sourceFile, className, aVariable.getStart(), aVariable.getLength());
834 elementList.remove(aVariable);
835 elementList.add(aClass);
836 classes.put(className, aClass);
837 globalVariables.remove(className);
839 //The final case is if we have no idea where this class came from, but shouldn't be ignored.
840 aClass = new JSClassElement(this.sourceFile, className, 0, 0);
842 elementList.add(aClass);
843 classes.put(className, aClass);
847 aClass = (JSClassElement) classes.get(className);
853 public boolean isSystemClass(String aClassName)
855 return systemClassMap.containsKey(aClassName);
862 private String getNaked(String funcName)
864 if (funcName == null)
869 funcName = funcName.trim().substring(FUNCTION.length()).trim();
870 funcName = replaceInString(funcName.trim(), LINE_SEPARATOR, "");
872 StringBuffer strBuf = new StringBuffer("");
873 int len = funcName.length();
874 boolean wasSpace = false;
875 for (int i = 0; i < len; i++)
877 char ch = funcName.charAt(i);
891 return strBuf.toString();
895 * replace in a string a string sequence with another string sequence
897 public static String replaceInString(String source, String whatBefore, String whatAfter)
899 if (null == source || source.length() == 0)
903 int beforeLen = whatBefore.length();
908 StringBuffer result = new StringBuffer("");
910 int index = source.indexOf(whatBefore, lastIndex);
913 result.append(source.substring(lastIndex, index));
914 result.append(whatAfter);
915 lastIndex = index + beforeLen;
918 index = source.indexOf(whatBefore, lastIndex);
920 result.append(source.substring(lastIndex));
921 return result.toString();
925 * @return Returns the elementList.
927 public List getElementList() {