X-Git-Url: http://git.phpeclipse.com diff --git a/archive/net.sourceforge.phpeclipse.js.core/src/net/sourceforge/phpeclipse/js/core/parser/JSParser.java b/archive/net.sourceforge.phpeclipse.js.core/src/net/sourceforge/phpeclipse/js/core/parser/JSParser.java new file mode 100644 index 0000000..7796b4a --- /dev/null +++ b/archive/net.sourceforge.phpeclipse.js.core/src/net/sourceforge/phpeclipse/js/core/parser/JSParser.java @@ -0,0 +1,926 @@ +/* + * $RCSfile: JSParser.java,v $ + * + * Copyright 2002 + * CH-1700 Fribourg, Switzerland + * All rights reserved. + * + *======================================================================== + * Modifications history + *======================================================================== + * $Log: not supported by cvs2svn $ + * Revision 1.1 2004/02/26 02:25:42 agfitzp + * renamed packages to match xml & css + * + * Revision 1.1 2004/02/05 03:10:08 agfitzp + * Initial Submission + * + * Revision 1.1.2.1 2003/12/12 21:37:24 agfitzp + * Experimental work for Classes view + * + * Revision 1.6 2003/12/10 20:19:16 agfitzp + * 3.0 port + * + * Revision 1.5 2003/06/21 03:48:51 agfitzp + * fixed global variables as functions bug + * fixed length calculation of instance variables + * Automatic outlining is now a preference + * + * Revision 1.4 2003/05/30 20:53:09 agfitzp + * 0.0.2 : Outlining is now done as the user types. Some other bug fixes. + * + * Revision 1.3 2003/05/28 20:47:58 agfitzp + * Outline the document, not the file. + * + * Revision 1.2 2003/05/28 15:20:00 agfitzp + * Trivial change to test CVS commit + * + * Revision 1.1 2003/05/28 15:17:12 agfitzp + * net.sourceforge.phpeclipse.js.core 0.0.1 code base + * + *======================================================================== +*/ + +package net.sourceforge.phpeclipse.js.core.parser; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import net.sourceforge.phpeclipse.js.core.model.*; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.rules.IToken; + +/** + * DOCUMENT ME! + * + * @author Addi + */ +public class JSParser +{ + + public static final String FUNCTION = "function"; + + /** + * line separator + */ + public static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + /** + * Array of system types to ignore. + */ + private static String[] systemClassNames= {"Array","String"}; + + + protected HashMap systemClassMap = new HashMap(); + + protected IFile sourceFile; + protected IDocument sourceDocument; + protected HashMap functions = new HashMap(); + protected HashMap classes = new HashMap(); + protected HashMap globalVariables = new HashMap(); + protected List elementList = new LinkedList(); + protected JSSyntaxScanner scanner = new JSSyntaxScanner(); + + /** + * Constructor for JSParser. + */ + public JSParser() + { + super(); + + int i; + + for(i = 0;i < systemClassNames.length; i++) + { + String aName = systemClassNames[i]; + systemClassMap.put(aName, aName); + } + } + + /** + * Returns a string containing the contents of the given file. Returns an empty string if there + * were any errors reading the file. + * @param file + * + * @return + */ + protected static String getText(IFile file) + { + try + { + InputStream in = file.getContents(); + return streamToString(in); + } catch (Exception e) + { + e.printStackTrace(); + } + return ""; + } + + protected static String streamToString(InputStream in) throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int read = in.read(buf); + + while (read > 0) + { + out.write(buf, 0, read); + read = in.read(buf); + } + + return out.toString(); + } + + /** + * Skips ahead and finds next non-whitespace token. + * + */ + public IToken nextNonWhitespaceToken() + { + IToken aToken = scanner.nextToken(); + + while (!aToken.isEOF() && aToken.isWhitespace()) + { + aToken = scanner.nextToken(); + } + + return aToken; + } + + /** + * Parses the input given by the argument. + * + * @param file the element containing the input text + * + * @return an element collection representing the parsed input + */ + public List parse(IFile file) + { + this.sourceFile = file; + return parse(new Document(getText(file))); + } + + /** + * Parses the input given by the argument. + * + * @param aSourceDocument the element containing the input text + * + * @return an element collection representing the parsed input + */ + public List parse(IDocument aSourceDocument) + { + sourceDocument = aSourceDocument; + + scanner.setRange(sourceDocument, 0, sourceDocument.getLength()); + IToken token = scanner.nextToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION)) + { + addFunction(expression, offset, length); + } + + if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT)) + { + //We need to check if the token is already a function or class + if (functions.containsKey(expression) || classes.containsKey(expression)) + { + token = nextNonWhitespaceToken(); + if (token.equals(JSSyntaxScanner.TOKEN_MEMBER)) + { + detectInstanceMethod(offset, expression); + } else + { + detectClassMethod(token, offset, expression); + } + } else + { + if (expression.equals("var")) + { + detectGlobalVariable(); + } + } + } + token = scanner.nextToken(); + } + return elementList; + } + + private void addFunction(String expression, int offset, int length) + { + String functionSignature = getNaked(expression); + int braceOffset = functionSignature.indexOf("("); + String functionName = functionSignature.substring(0, braceOffset).trim(); + String arguments = + functionSignature.substring(functionSignature.indexOf("("), functionSignature.indexOf(")") + 1); + + if (functionName.indexOf(".") >= 0) + { + //If the function signature includes .prototype. then it's a member. + if (functionName.indexOf(".prototype.") >= 0) + { + String className = functionName.substring(0, functionName.indexOf(".")); + String memberName = functionName.substring(functionName.lastIndexOf(".") + 1); + JSInstanceMethodElement aMethod = + this.addInstanceMethod(memberName, className, arguments, offset, offset, length); + detectInstanceMethodContext(className, aMethod); + } else + { + String className = functionName.substring(0, functionName.indexOf(".")); + if (functions.containsKey(className) || classes.containsKey(className)) + { + String memberName = functionName.substring(functionName.lastIndexOf(".") + 1); + JSFunctionElement aMethod = + this.addClassMethod(memberName, className, arguments, offset, offset, length); + } + } + } else + { + if(! functions.containsKey(functionName)) + { + JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, length); + + elementList.add(aFunction); + functions.put(functionName, aFunction); + + detectFunctionContext(aFunction); + } + } + } + + /** + * + */ + private void checkForSpecialGlobalTypes(JSGlobalVariableElement aVariable) + { + IToken token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + if(!checkForDynamicClass(aVariable, token)) + { + checkForAnonymousFunction(aVariable, token); + } + } + } + + /** + * + */ + private boolean checkForDynamicClass(JSGlobalVariableElement aVariable, IToken rhsToken) + { + if (rhsToken.equals(JSSyntaxScanner.TOKEN_DEFAULT)) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + + String expression = getExpression(offset, length); + + if (expression.equals("new")) + { + IToken token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT)) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + expression = getExpression(offset, length); + + if(! isSystemClass(expression)) + { + JSClassElement aClass = findOrCreateClass(aVariable.getName()); + if(aClass != null) + { + //Tell the class it's dynamically declared: what we will parse as class methods & vars are really instance methods & vars + aClass.setPrototype(true); + + return true; + } + } + } + } + } + } + return false; + } + + /** + * + */ + private boolean checkForAnonymousFunction(JSGlobalVariableElement aVariable, IToken rhsToken) + { + if (rhsToken.equals(JSSyntaxScanner.TOKEN_FUNCTION)) + { + String functionName = aVariable.getName(); + int offset = aVariable.getOffset(); + int length = aVariable.getLength(); + + int functionOffset = scanner.getTokenOffset(); + int functionLength = scanner.getTokenLength(); + String functionSignature = + getExpression(functionOffset, functionLength); + String arguments = getArgumentString(functionSignature); + + JSFunctionElement aFunction = new JSFunctionElement(this.sourceFile, functionName, arguments, offset, functionOffset - offset + functionLength); + + elementList.add(aFunction); + functions.put(functionName, aFunction); + + elementList.remove(aVariable); + globalVariables.remove(functionName); + + detectFunctionContext(aFunction); + + return true; + } + + return false; + } + + /** + * + */ + private String getExpression(int offset, int length) + { + String expression; + try { + expression = sourceDocument.get(offset, length);//sourceBuffer.substring(offset, offset + length); + } catch(BadLocationException e) + { + expression = ""; + } + return expression; + } + + /** + * + */ + private void detectGlobalVariable() + { + IToken token; + int length; + int offset; + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT)) + { + int varOffset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + String variableName = getExpression(varOffset, length); + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + if (expression.equals("=")) + { + JSGlobalVariableElement aVariable = addGlobalVariable(variableName, varOffset); + checkForSpecialGlobalTypes(aVariable); + } + } + } + } + } + + private void detectClassMethod(IToken token, int classOffset, String className) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + if (expression.equals(".")) + { + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + String memberName = getExpression(offset, length); + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + expression = getExpression(offset, length); + if (expression.equals("=")) + { + + token = nextNonWhitespaceToken(); + int tokenOffset = scanner.getTokenOffset(); + int tokenLength = scanner.getTokenLength(); + + if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION)) + { + String functionSignature = getExpression(tokenOffset, tokenLength); + String arguments = getArgumentString(functionSignature); + + JSFunctionElement aMethod = + addClassMethod(memberName, className, arguments, classOffset, tokenOffset, tokenLength); + + + } else + { + addClassVariable(memberName, className, classOffset); + } + } + } + } + } + } + + private String getArgumentString(String functionSignature) + { + return functionSignature.substring( + functionSignature.indexOf("("), + functionSignature.indexOf(")") + 1); + } + + private void detectInstanceMethod(int classOffset, String className) + { + String expression; + IToken token; + int length; + int offset; + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + expression = getExpression(offset, length); + + if (expression.equals(".")) + { + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + String memberName = getExpression(offset, length); + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + expression = getExpression(offset, length); + if (expression.equals("=")) + { + token = nextNonWhitespaceToken(); + if (token.equals(JSSyntaxScanner.TOKEN_FUNCTION)) + { + int functionOffset = scanner.getTokenOffset(); + int functionLength = scanner.getTokenLength(); + String functionSignature = + getExpression(functionOffset, functionLength); + String arguments = getArgumentString(functionSignature); + + JSInstanceMethodElement aMethod = + addInstanceMethod( + memberName, + className, + arguments, + classOffset, + functionOffset, + functionLength); + + detectInstanceMethodContext(className, aMethod); + + } else + { + addInstanceVariable(memberName, className, classOffset, (".prototype.").length()); + } + + } + } + } + } + } + } + + private void parseInstanceMethodContext(String className, JSFunctionElement aMethod) + { + IToken token; + + token = nextNonWhitespaceToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + // if (token.equals(JSSyntaxScanner.TOKEN_END_CONTEXT)) + if (expression.equals("}")) + { + return; + } else if (expression.equals("{")) + { + parseInstanceMethodContext(className, aMethod); + } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT)) + { + if (expression.equals("this")) + { + handleThisReference(className, offset); + } + } + + token = nextNonWhitespaceToken(); + } + } + + private void detectInstanceMethodContext(String className, JSFunctionElement aMethod) + { + IToken token; + + token = nextNonWhitespaceToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + // if (token.equals(JSSyntaxScanner.TOKEN_BEGIN_CONTEXT)) + if (expression.equals("{")) + { + parseInstanceMethodContext(className, aMethod); + return; + } + + token = nextNonWhitespaceToken(); + } + } + + private void parseClassMethodContext(JSFunctionElement aMethod) + { + IToken token; + + token = nextNonWhitespaceToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + if (expression.equals("}")) + { + return; + } else if (expression.equals("{")) + { + parseClassMethodContext(aMethod); + } + + token = nextNonWhitespaceToken(); + } + } + + private void detectClassMethodContext(JSFunctionElement aMethod) + { + IToken token = nextNonWhitespaceToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + if (expression.equals("{")) + { + parseClassMethodContext(aMethod); + return; + } + + token = nextNonWhitespaceToken(); + } + } + + private void handleThisReference(String className, int expressionStart) + { + IToken token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + + String expression = getExpression(offset, length); + + if(expression.equals(".")) + { + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + int memberStart = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + + String memberName = getExpression(memberStart, length); + + token = nextNonWhitespaceToken(); + if (!token.isEOF()) + { + offset = scanner.getTokenOffset(); + length = scanner.getTokenLength(); + expression = getExpression(offset, length); + + if (expression.equals("=")) + { + addInstanceVariable(memberName, className, expressionStart, 1 + 4 - className.length()); + } + } + } + } + } + } + + private void parseFunctionContext(JSFunctionElement aFunction) + { + IToken token; + + token = nextNonWhitespaceToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + if (expression.equals("}")) + { + return; + } else if (expression.equals("{")) + { + parseFunctionContext(aFunction); + } else if (token.equals(JSSyntaxScanner.TOKEN_DEFAULT)) + { + if (expression.equals("this")) + { + handleThisReference(aFunction.getName(), offset); + } + } + + token = nextNonWhitespaceToken(); + } + } + + private void detectFunctionContext(JSFunctionElement aFunction) + { + IToken token = nextNonWhitespaceToken(); + while (!token.isEOF()) + { + int offset = scanner.getTokenOffset(); + int length = scanner.getTokenLength(); + String expression = getExpression(offset, length); + + if (expression.equals("{")) + { + parseFunctionContext(aFunction); + return; + } + + token = nextNonWhitespaceToken(); + } + } + + private JSInstanceMethodElement addInstanceMethod( + String memberName, + String className, + String arguments, + int classOffset, + int functionOffset, + int functionLength) + { + int signatureLength = functionOffset - classOffset + functionLength; + JSInstanceMethodElement aMethod = + new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength); + + findOrCreateClass(className).addChildElement(aMethod); + + return aMethod; + } + + private JSFunctionElement addClassMethod( + String memberName, + String className, + String arguments, + int classOffset, + int functionOffset, + int functionLength) + { + JSClassElement aClass = findOrCreateClass(className); + int signatureLength = functionOffset - classOffset + functionLength; + JSFunctionElement aMethod; + + if(aClass.isPrototype()) { + aMethod = new JSInstanceMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength); + + aClass.addChildElement(aMethod); + detectInstanceMethodContext(className, aMethod); + } else { + aMethod = new JSClassMethodElement(this.sourceFile, memberName, arguments, classOffset, signatureLength); + + aClass.addChildElement(aMethod); + detectClassMethodContext(aMethod); + } + + return aMethod; + } + + /** + * @param memberName + * @param className + * @param classOffset + * @return + */ + private JSElement addClassVariable(String memberName, String className, int classOffset) + { + //One extra char for "." + JSElement aVariable; + JSClassElement aClass = findOrCreateClass(className); + + if(aClass.isPrototype()) + { + aVariable = new JSInstanceVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1); + + } else { + aVariable = new JSClassVariableElement(this.sourceFile, memberName, classOffset, className.length() + memberName.length() + 1); + } + aClass.addChildElement(aVariable); + + return aVariable; + } + + private JSInstanceVariableElement addInstanceVariable( + String memberName, + String className, + int classOffset, + int paddingWidth) + { + //11 extra chars for ".prototype." + JSInstanceVariableElement aVariable = + new JSInstanceVariableElement( + this.sourceFile, + memberName, + classOffset, + className.length() + memberName.length() + paddingWidth); + + findOrCreateClass(className).addChildElement(aVariable); + + return aVariable; + } + + private JSGlobalVariableElement addGlobalVariable(String variableName, int offset) + { + JSGlobalVariableElement aVariable; + if (!globalVariables.containsKey(variableName)) + { + aVariable = new JSGlobalVariableElement(this.sourceFile, variableName, offset, variableName.length()); + + elementList.add(aVariable); + globalVariables.put(variableName, aVariable); + } else + { + aVariable = (JSGlobalVariableElement) classes.get(variableName); + } + + return aVariable; + } + + private JSClassElement findOrCreateClass(String className) + { + JSClassElement aClass = null; + if (!classes.containsKey(className)) + { + if(functions.containsKey(className)) + { + //if we're creating a class from an existing function we must + //migrate the existing function to become a constructor in the class. + JSFunctionElement constructor = (JSFunctionElement) functions.get(className); + + aClass = new JSClassElement(this.sourceFile, className, constructor.getStart(), constructor.getLength()); + aClass.addChildElement(constructor); + + elementList.remove(constructor); + elementList.add(aClass); + classes.put(className, aClass); + } else if(globalVariables.containsKey(className)) + { + //if we're creating a class from an existing global variable we must + //migrate the existing function to become a constructor in the class. + JSGlobalVariableElement aVariable = (JSGlobalVariableElement) globalVariables.get(className); + + aClass = new JSClassElement(this.sourceFile, className, aVariable.getStart(), aVariable.getLength()); + + elementList.remove(aVariable); + elementList.add(aClass); + classes.put(className, aClass); + globalVariables.remove(className); + } else { + //The final case is if we have no idea where this class came from, but shouldn't be ignored. + aClass = new JSClassElement(this.sourceFile, className, 0, 0); + + elementList.add(aClass); + classes.put(className, aClass); + } + } else + { + aClass = (JSClassElement) classes.get(className); + } + + return aClass; + } + + public boolean isSystemClass(String aClassName) + { + return systemClassMap.containsKey(aClassName); + } + + /** + * Method getNaked. + * @param funcName + */ + private String getNaked(String funcName) + { + if (funcName == null) + { + return null; + } + + funcName = funcName.trim().substring(FUNCTION.length()).trim(); + funcName = replaceInString(funcName.trim(), LINE_SEPARATOR, ""); + + StringBuffer strBuf = new StringBuffer(""); + int len = funcName.length(); + boolean wasSpace = false; + for (int i = 0; i < len; i++) + { + char ch = funcName.charAt(i); + if (ch == ' ') + { + wasSpace = true; + } else // not space + { + if (wasSpace) + { + strBuf.append(' '); + } + strBuf.append(ch); + wasSpace = false; + } + } + return strBuf.toString(); + } + + /** + * replace in a string a string sequence with another string sequence + */ + public static String replaceInString(String source, String whatBefore, String whatAfter) + { + if (null == source || source.length() == 0) + { + return source; + } + int beforeLen = whatBefore.length(); + if (beforeLen == 0) + { + return source; + } + StringBuffer result = new StringBuffer(""); + int lastIndex = 0; + int index = source.indexOf(whatBefore, lastIndex); + while (index >= 0) + { + result.append(source.substring(lastIndex, index)); + result.append(whatAfter); + lastIndex = index + beforeLen; + + // get next + index = source.indexOf(whatBefore, lastIndex); + } + result.append(source.substring(lastIndex)); + return result.toString(); + } + + /** + * @return Returns the elementList. + */ + public List getElementList() { + return elementList; + } + +} \ No newline at end of file