2 * $RCSfile: JSParser.java,v $
5 * CH-1700 Fribourg, Switzerland
8 *========================================================================
9 * Modifications history
10 *========================================================================
11 * $Log: not supported by cvs2svn $
12 * Revision 1.3 2005/05/06 00:58:28 stefanbjarni
14 * Changed one instance reference to a static method to a static reference
16 * Revision 1.2 2005/04/06 18:29:29 axelcl
17 * Avoid NullPointerException
19 * Revision 1.1 2004/09/02 18:14:38 jsurfer
20 * intial source from ttp://www.sf.net/projects/wdte
22 * Revision 1.1 2004/02/26 02:25:42 agfitzp
23 * renamed packages to match xml & css
25 * Revision 1.1 2004/02/05 03:10:08 agfitzp
28 * Revision 1.1.2.1 2003/12/12 21:37:24 agfitzp
29 * Experimental work for Classes view
31 * Revision 1.6 2003/12/10 20:19:16 agfitzp
34 * Revision 1.5 2003/06/21 03:48:51 agfitzp
35 * fixed global variables as functions bug
36 * fixed length calculation of instance variables
37 * Automatic outlining is now a preference
39 * Revision 1.4 2003/05/30 20:53:09 agfitzp
40 * 0.0.2 : Outlining is now done as the user types. Some other bug fixes.
42 * Revision 1.3 2003/05/28 20:47:58 agfitzp
43 * Outline the document, not the file.
45 * Revision 1.2 2003/05/28 15:20:00 agfitzp
46 * Trivial change to test CVS commit
48 * Revision 1.1 2003/05/28 15:17:12 agfitzp
49 * net.sourceforge.phpeclipse.js.core 0.0.1 code base
51 *========================================================================
54 package net.sourceforge.phpeclipse.js.core.parser;
56 import java.io.ByteArrayOutputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.util.HashMap;
60 import java.util.LinkedList;
61 import java.util.List;
63 import net.sourceforge.phpeclipse.js.core.model.JSClassElement;
64 import net.sourceforge.phpeclipse.js.core.model.JSClassMethodElement;
65 import net.sourceforge.phpeclipse.js.core.model.JSClassVariableElement;
66 import net.sourceforge.phpeclipse.js.core.model.JSElement;
67 import net.sourceforge.phpeclipse.js.core.model.JSFunctionElement;
68 import net.sourceforge.phpeclipse.js.core.model.JSGlobalVariableElement;
69 import net.sourceforge.phpeclipse.js.core.model.JSInstanceMethodElement;
70 import net.sourceforge.phpeclipse.js.core.model.JSInstanceVariableElement;
72 import org.eclipse.core.resources.IFile;
73 import org.eclipse.jface.text.BadLocationException;
74 import org.eclipse.jface.text.Document;
75 import org.eclipse.jface.text.IDocument;
76 import org.eclipse.jface.text.rules.IToken;
86 public static final String FUNCTION = "function";
91 public static final String LINE_SEPARATOR = System.getProperty("line.separator");
94 * Array of system types to ignore.
96 private static String[] systemClassNames= {"Array","String"};
99 protected HashMap systemClassMap = new HashMap();
101 protected IFile sourceFile;
102 protected IDocument sourceDocument;
103 protected HashMap functions = new HashMap();
104 protected HashMap classes = new HashMap();
105 protected HashMap globalVariables = new HashMap();
106 protected List elementList = new LinkedList();
107 protected JSSyntaxScanner scanner = new JSSyntaxScanner();
110 * Constructor for JSParser.
118 for(i = 0;i < systemClassNames.length; i++)
120 String aName = systemClassNames[i];
121 systemClassMap.put(aName, aName);
126 * Returns a string containing the contents of the given file. Returns an empty string if there
127 * were any errors reading the file.
132 protected static String getText(IFile file)
136 InputStream in = file.getContents();
137 return streamToString(in);
138 } catch (Exception e)
145 protected static String streamToString(InputStream in) throws IOException
147 ByteArrayOutputStream out = new ByteArrayOutputStream();
148 byte[] buf = new byte[1024];
149 int read = in.read(buf);
153 out.write(buf, 0, read);
157 return out.toString();
161 * Skips ahead and finds next non-whitespace token.
164 public IToken nextNonWhitespaceToken()
166 IToken aToken = scanner.nextToken();
168 while (!aToken.isEOF() && aToken.isWhitespace())
170 aToken = scanner.nextToken();
177 * Parses the input given by the argument.
179 * @param file the element containing the input text
181 * @return an element collection representing the parsed input
183 public List parse(IFile file)
185 this.sourceFile = file;
186 return parse(new Document(getText(file)));
190 * Parses the input given by the argument.
192 * @param aSourceDocument the element containing the input text
194 * @return an element collection representing the parsed input
196 public List parse(IDocument aSourceDocument)
198 sourceDocument = aSourceDocument;
200 scanner.setRange(sourceDocument, 0, sourceDocument.getLength());
201 IToken token = scanner.nextToken();
202 while (!token.isEOF())
204 int offset = scanner.getTokenOffset();
205 int length = scanner.getTokenLength();
206 String expression = getExpression(offset, length);
208 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
210 addFunction(expression, offset, length);
213 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
215 //We need to check if the token is already a function or class
216 if (functions.containsKey(expression) || classes.containsKey(expression))
218 token = nextNonWhitespaceToken();
219 if (token.equals(JSSyntaxScanner.TOKEN_MEMBER))
221 detectInstanceMethod(offset, expression);
224 detectClassMethod(token, offset, expression);
228 if (expression.equals("var"))
230 detectGlobalVariable();
234 token = scanner.nextToken();
239 private void addFunction(String expression, int offset, int length)
241 String functionSignature = getNaked(expression);
242 int braceOffset = functionSignature.indexOf("(");
243 String functionName = functionSignature.substring(0, braceOffset).trim();
245 functionSignature.substring(functionSignature.indexOf("("), functionSignature.indexOf(")") + 1);
247 if (functionName.indexOf(".") >= 0)
249 //If the function signature includes .prototype. then it's a member.
250 if (functionName.indexOf(".prototype.") >= 0)
252 String className = functionName.substring(0, functionName.indexOf("."));
253 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
254 JSInstanceMethodElement aMethod =
255 this.addInstanceMethod(memberName, className, arguments, offset, offset, length);
256 detectInstanceMethodContext(className, aMethod);
259 String className = functionName.substring(0, functionName.indexOf("."));
260 if (functions.containsKey(className) || classes.containsKey(className))
262 String memberName = functionName.substring(functionName.lastIndexOf(".") + 1);
263 JSFunctionElement aMethod =
264 this.addClassMethod(memberName, className, arguments, offset, offset, length);
269 if(! functions.containsKey(functionName))
271 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, length);
273 elementList.add(aFunction);
274 functions.put(functionName, aFunction);
276 detectFunctionContext(aFunction);
284 private void checkForSpecialGlobalTypes(JSGlobalVariableElement aVariable)
286 IToken token = nextNonWhitespaceToken();
289 if(!checkForDynamicClass(aVariable, token))
291 checkForAnonymousFunction(aVariable, token);
299 private boolean checkForDynamicClass(JSGlobalVariableElement aVariable, IToken rhsToken)
301 if (rhsToken.equals(JSSyntaxScanner.TOKEN_DEFAULT))
303 int offset = scanner.getTokenOffset();
304 int length = scanner.getTokenLength();
306 String expression = getExpression(offset, length);
308 if (expression.equals("new"))
310 IToken token = nextNonWhitespaceToken();
313 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
315 offset = scanner.getTokenOffset();
316 length = scanner.getTokenLength();
317 expression = getExpression(offset, length);
319 if(! isSystemClass(expression))
321 JSClassElement aClass = findOrCreateClass(aVariable.getName());
324 //Tell the class it's dynamically declared: what we will parse as class methods & vars are really instance methods & vars
325 aClass.setPrototype(true);
340 private boolean checkForAnonymousFunction(JSGlobalVariableElement aVariable, IToken rhsToken)
342 if (rhsToken.equals(JSSyntaxScanner.TOKEN_FUNCTION))
344 String functionName = aVariable.getName();
345 int offset = aVariable.getOffset();
346 int length = aVariable.getLength();
348 int functionOffset = scanner.getTokenOffset();
349 int functionLength = scanner.getTokenLength();
350 String functionSignature =
351 getExpression(functionOffset, functionLength);
352 String arguments = getArgumentString(functionSignature);
354 JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, functionOffset - offset + functionLength);
356 elementList.add(aFunction);
357 functions.put(functionName, aFunction);
359 elementList.remove(aVariable);
360 globalVariables.remove(functionName);
362 detectFunctionContext(aFunction);
373 private String getExpression(int offset, int length)
377 expression = sourceDocument.get(offset, length);//sourceBuffer.substring(offset, offset + length);
378 } catch(BadLocationException e)
388 private void detectGlobalVariable()
394 token = nextNonWhitespaceToken();
397 if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
399 int varOffset = scanner.getTokenOffset();
400 length = scanner.getTokenLength();
401 String variableName = getExpression(varOffset, length);
403 token = nextNonWhitespaceToken();
406 offset = scanner.getTokenOffset();
407 length = scanner.getTokenLength();
408 String expression = getExpression(offset, length);
409 if (expression.equals("="))
411 JSGlobalVariableElement aVariable = addGlobalVariable(variableName, varOffset);
412 if (aVariable!=null) {
413 checkForSpecialGlobalTypes(aVariable);
421 private void detectClassMethod(IToken token, int classOffset, String className)
423 int offset = scanner.getTokenOffset();
424 int length = scanner.getTokenLength();
425 String expression = getExpression(offset, length);
427 if (expression.equals("."))
430 token = nextNonWhitespaceToken();
433 offset = scanner.getTokenOffset();
434 length = scanner.getTokenLength();
435 String memberName = getExpression(offset, length);
437 token = nextNonWhitespaceToken();
440 offset = scanner.getTokenOffset();
441 length = scanner.getTokenLength();
442 expression = getExpression(offset, length);
443 if (expression.equals("="))
446 token = nextNonWhitespaceToken();
447 int tokenOffset = scanner.getTokenOffset();
448 int tokenLength = scanner.getTokenLength();
450 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
452 String functionSignature = getExpression(tokenOffset, tokenLength);
453 String arguments = getArgumentString(functionSignature);
455 JSFunctionElement aMethod =
456 addClassMethod(memberName, className, arguments, classOffset, tokenOffset, tokenLength);
461 addClassVariable(memberName, className, classOffset);
469 private String getArgumentString(String functionSignature)
471 return functionSignature.substring(
472 functionSignature.indexOf("("),
473 functionSignature.indexOf(")") + 1);
476 private void detectInstanceMethod(int classOffset, String className)
483 token = nextNonWhitespaceToken();
486 offset = scanner.getTokenOffset();
487 length = scanner.getTokenLength();
488 expression = getExpression(offset, length);
490 if (expression.equals("."))
493 token = nextNonWhitespaceToken();
496 offset = scanner.getTokenOffset();
497 length = scanner.getTokenLength();
498 String memberName = getExpression(offset, length);
500 token = nextNonWhitespaceToken();
503 offset = scanner.getTokenOffset();
504 length = scanner.getTokenLength();
505 expression = getExpression(offset, length);
506 if (expression.equals("="))
508 token = nextNonWhitespaceToken();
509 if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION))
511 int functionOffset = scanner.getTokenOffset();
512 int functionLength = scanner.getTokenLength();
513 String functionSignature =
514 getExpression(functionOffset, functionLength);
515 String arguments = getArgumentString(functionSignature);
517 JSInstanceMethodElement aMethod =
526 detectInstanceMethodContext(className, aMethod);
530 addInstanceVariable(memberName, className, classOffset, (".prototype.").length());
540 private void parseInstanceMethodContext(String className, JSFunctionElement aMethod)
544 token = nextNonWhitespaceToken();
545 while (!token.isEOF())
547 int offset = scanner.getTokenOffset();
548 int length = scanner.getTokenLength();
549 String expression = getExpression(offset, length);
551 // if (token.equals(JSSyntaxScanner.TOKEN_END_CONTEXT))
552 if (expression.equals("}"))
555 } else if (expression.equals("{"))
557 parseInstanceMethodContext(className, aMethod);
558 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
560 if (expression.equals("this"))
562 handleThisReference(className, offset);
566 token = nextNonWhitespaceToken();
570 private void detectInstanceMethodContext(String className, JSFunctionElement aMethod)
574 token = nextNonWhitespaceToken();
575 while (!token.isEOF())
577 int offset = scanner.getTokenOffset();
578 int length = scanner.getTokenLength();
579 String expression = getExpression(offset, length);
581 // if (token.equals(JSSyntaxScanner.TOKEN_BEGIN_CONTEXT))
582 if (expression.equals("{"))
584 parseInstanceMethodContext(className, aMethod);
588 token = nextNonWhitespaceToken();
592 private void parseClassMethodContext(JSFunctionElement aMethod)
596 token = nextNonWhitespaceToken();
597 while (!token.isEOF())
599 int offset = scanner.getTokenOffset();
600 int length = scanner.getTokenLength();
601 String expression = getExpression(offset, length);
603 if (expression.equals("}"))
606 } else if (expression.equals("{"))
608 parseClassMethodContext(aMethod);
611 token = nextNonWhitespaceToken();
615 private void detectClassMethodContext(JSFunctionElement aMethod)
617 IToken token = nextNonWhitespaceToken();
618 while (!token.isEOF())
620 int offset = scanner.getTokenOffset();
621 int length = scanner.getTokenLength();
622 String expression = getExpression(offset, length);
624 if (expression.equals("{"))
626 parseClassMethodContext(aMethod);
630 token = nextNonWhitespaceToken();
634 private void handleThisReference(String className, int expressionStart)
636 IToken token = nextNonWhitespaceToken();
639 int offset = scanner.getTokenOffset();
640 int length = scanner.getTokenLength();
642 String expression = getExpression(offset, length);
644 if(expression.equals("."))
646 token = nextNonWhitespaceToken();
649 int memberStart = scanner.getTokenOffset();
650 length = scanner.getTokenLength();
652 String memberName = getExpression(memberStart, length);
654 token = nextNonWhitespaceToken();
657 offset = scanner.getTokenOffset();
658 length = scanner.getTokenLength();
659 expression = getExpression(offset, length);
661 if (expression.equals("="))
663 addInstanceVariable(memberName, className, expressionStart, 1 + 4 - className.length());
671 private void parseFunctionContext(JSFunctionElement aFunction)
675 token = nextNonWhitespaceToken();
676 while (!token.isEOF())
678 int offset = scanner.getTokenOffset();
679 int length = scanner.getTokenLength();
680 String expression = getExpression(offset, length);
682 if (expression.equals("}"))
685 } else if (expression.equals("{"))
687 parseFunctionContext(aFunction);
688 } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT))
690 if (expression.equals("this"))
692 handleThisReference(aFunction.getName(), offset);
696 token = nextNonWhitespaceToken();
700 private void detectFunctionContext(JSFunctionElement aFunction)
702 IToken token = nextNonWhitespaceToken();
703 while (!token.isEOF())
705 int offset = scanner.getTokenOffset();
706 int length = scanner.getTokenLength();
707 String expression = getExpression(offset, length);
709 if (expression.equals("{"))
711 parseFunctionContext(aFunction);
715 token = nextNonWhitespaceToken();
719 private JSInstanceMethodElement addInstanceMethod(
727 int signatureLength = functionOffset - classOffset + functionLength;
728 JSInstanceMethodElement aMethod =
729 new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
731 findOrCreateClass(className).addChildElement(aMethod);
736 private JSFunctionElement addClassMethod(
744 JSClassElement aClass = findOrCreateClass(className);
745 int signatureLength = functionOffset - classOffset + functionLength;
746 JSFunctionElement aMethod;
748 if(aClass.isPrototype()) {
749 aMethod = new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
751 aClass.addChildElement(aMethod);
752 detectInstanceMethodContext(className, aMethod);
754 aMethod = new JSClassMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength);
756 aClass.addChildElement(aMethod);
757 detectClassMethodContext(aMethod);
769 private JSElement addClassVariable(String memberName, String className, int classOffset)
771 //One extra char for "."
773 JSClassElement aClass = findOrCreateClass(className);
775 if(aClass.isPrototype())
777 aVariable = new JSInstanceVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
780 aVariable = new JSClassVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1);
782 aClass.addChildElement(aVariable);
787 private JSInstanceVariableElement addInstanceVariable(
793 //11 extra chars for ".prototype."
794 JSInstanceVariableElement aVariable =
795 new JSInstanceVariableElement(
799 className.length() + memberName.length() + paddingWidth);
801 findOrCreateClass(className).addChildElement(aVariable);
806 private JSGlobalVariableElement addGlobalVariable(String variableName, int offset)
808 JSGlobalVariableElement aVariable;
809 if (!globalVariables.containsKey(variableName))
811 aVariable = new JSGlobalVariableElement(this.sourceFile, variableName, offset, variableName.length());
813 elementList.add(aVariable);
814 globalVariables.put(variableName, aVariable);
817 aVariable = (JSGlobalVariableElement) globalVariables.get(variableName);
823 private JSClassElement findOrCreateClass(String className)
825 JSClassElement aClass = null;
826 if (!classes.containsKey(className))
828 if(functions.containsKey(className))
830 //if we're creating a class from an existing function we must
831 //migrate the existing function to become a constructor in the class.
832 JSFunctionElement constructor = (JSFunctionElement) functions.get(className);
834 aClass = new JSClassElement(this.sourceFile, className, constructor.getStart(), constructor.getLength());
835 aClass.addChildElement(constructor);
837 elementList.remove(constructor);
838 elementList.add(aClass);
839 classes.put(className, aClass);
840 } else if(globalVariables.containsKey(className))
842 //if we're creating a class from an existing global variable we must
843 //migrate the existing function to become a constructor in the class.
844 JSGlobalVariableElement aVariable = (JSGlobalVariableElement) globalVariables.get(className);
846 aClass = new JSClassElement(this.sourceFile, className, aVariable.getStart(), aVariable.getLength());
848 elementList.remove(aVariable);
849 elementList.add(aClass);
850 classes.put(className, aClass);
851 globalVariables.remove(className);
853 //The final case is if we have no idea where this class came from, but shouldn't be ignored.
854 aClass = new JSClassElement(this.sourceFile, className, 0, 0);
856 elementList.add(aClass);
857 classes.put(className, aClass);
861 aClass = (JSClassElement) classes.get(className);
867 public boolean isSystemClass(String aClassName)
869 return systemClassMap.containsKey(aClassName);
876 private String getNaked(String funcName)
878 if (funcName == null)
883 funcName = funcName.trim().substring(FUNCTION.length()).trim();
884 funcName = replaceInString(funcName.trim(), LINE_SEPARATOR, "");
886 StringBuffer strBuf = new StringBuffer("");
887 int len = funcName.length();
888 boolean wasSpace = false;
889 for (int i = 0; i < len; i++)
891 char ch = funcName.charAt(i);
905 return strBuf.toString();
909 * replace in a string a string sequence with another string sequence
911 public static String replaceInString(String source, String whatBefore, String whatAfter)
913 if (null == source || source.length() == 0)
917 int beforeLen = whatBefore.length();
922 StringBuffer result = new StringBuffer("");
924 int index = source.indexOf(whatBefore, lastIndex);
927 result.append(source.substring(lastIndex, index));
928 result.append(whatAfter);
929 lastIndex = index + beforeLen;
932 index = source.indexOf(whatBefore, lastIndex);
934 result.append(source.substring(lastIndex));
935 return result.toString();
939 * @return Returns the elementList.
941 public List getElementList() {