/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.editor.js.environment;
import java.util.ArrayList;
import java.util.Stack;
import com.aptana.ide.core.IdeLog;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.editor.js.JSLanguageEnvironment;
import com.aptana.ide.editor.js.JSPlugin;
import com.aptana.ide.editor.js.lexing.JSTokenTypes;
import com.aptana.ide.editor.js.parsing.JSMimeType;
import com.aptana.ide.editor.js.parsing.JSParseState;
import com.aptana.ide.editor.js.runtime.Environment;
import com.aptana.ide.editor.js.runtime.FunctionBase;
import com.aptana.ide.editor.js.runtime.IFunction;
import com.aptana.ide.editor.js.runtime.IObject;
import com.aptana.ide.editor.js.runtime.IScope;
import com.aptana.ide.editor.js.runtime.JSFunction;
import com.aptana.ide.editor.js.runtime.JSObject;
import com.aptana.ide.editor.js.runtime.JSScope;
import com.aptana.ide.editor.js.runtime.ObjectBase;
import com.aptana.ide.editor.js.runtime.Property;
import com.aptana.ide.editor.js.runtime.Reference;
import com.aptana.ide.editor.scriptdoc.lexing.ScriptDocTokenTypes;
import com.aptana.ide.editor.scriptdoc.parsing.FunctionDocumentation;
import com.aptana.ide.editor.scriptdoc.parsing.PropertyDocumentation;
import com.aptana.ide.editor.scriptdoc.parsing.ScriptDocMimeType;
import com.aptana.ide.editor.scriptdoc.parsing.TypedDescription;
import com.aptana.ide.editors.managers.FileContextManager;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.lexer.LexemeList;
import com.aptana.ide.lexer.Range;
import com.aptana.ide.metadata.IDocumentation;
import com.aptana.ide.metadata.IDocumentationStore;
import com.aptana.ide.parsing.IParseState;
/**
* @author Spike Washburn
*/
public class LexemeConsumerHelper
{
Environment env;
LexemeBasedEnvironmentLoader envLoader;
LexemeList lexemeList;
int llSize;
private IParseState parseState;
/**
* LexemeConsumerHelper
*
* @param env
* @param envLoader
* @param parseState
*/
LexemeConsumerHelper(Environment env, LexemeBasedEnvironmentLoader envLoader, IParseState parseState)
{
this.env = env;
this.envLoader = envLoader;
this.parseState = parseState;
this.lexemeList = parseState.getLexemeList();
this.llSize = this.lexemeList.size();
}
/**
* consumeIdentifier
*
* @param startIndex
* @param scope
* @param isVar
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeIdentifier(int startIndex, IScope scope, boolean isVar)
throws EndOfFileException
{
JSIdentifierConsumer indentifier = new JSIdentifierConsumer(scope, isVar);
return indentifier.consume(startIndex);
}
/**
* consumeIdentifier
*
* @param startIndex
* @param scope
* @param parent
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeIdentifier(int startIndex, IScope scope, IObject parent)
throws EndOfFileException
{
JSIdentifierConsumer indentifier = new JSIdentifierConsumer(scope, parent);
return indentifier.consume(startIndex);
}
/**
* consumeStatements
*
* @param startIndex
* @param scope
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeStatements(int startIndex, IScope scope) throws EndOfFileException
{
JSStatementConsumer statements = new JSStatementConsumer(scope);
return statements.consume(startIndex);
}
/**
* consumeObjectLiteral
*
* @param startIndex
* @param scope
* @param objectLiteral
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeObjectLiteral(int startIndex, IScope scope, IObject objectLiteral)
throws EndOfFileException
{
JSObjectLiteralConsumer objectLiteralConsumer = new JSObjectLiteralConsumer(scope, objectLiteral);
return objectLiteralConsumer.consume(startIndex);
}
/**
* Adds any alias tags on an object to the environment
*
* @param alias
*/
private void addPotentialAliases(IObject obj, TypedDescription alias)
{
if (obj != null && alias != null && alias.getTypes().length > 0)
{
String[] aliasTypes = alias.getTypes();
for (int i = 0; i < aliasTypes.length; i++)
{
String type = aliasTypes[i];
if (type != null && type != "") //$NON-NLS-1$
{
String name = type;
IObject root = env.getGlobal();
if (type.indexOf(".") > -1) //$NON-NLS-1$
{
int dotLoc = type.lastIndexOf("."); //$NON-NLS-1$
String basename = type.substring(0, dotLoc);
name = type.substring(dotLoc + 1);
root = lookupOrCreateObject(basename, env);
}
if (!name.equals("")) //$NON-NLS-1$
{
JSReference aliasRef = new JSReference(root, name, false);
envLoader.putPropertyValue(aliasRef, obj);
// IObject newObj = envLoader.getPropertyValue(aliasRef, Integer.MAX_VALUE);
// newObj.setDocumentation(func.getDocumentation());
}
}
}
}
}
/**
* consumeArrayLiteral
*
* @param startIndex
* @param scope
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeArrayLiteral(int startIndex, IScope scope) throws EndOfFileException
{
JSArrayLiteralConsumer arrayLiteral = new JSArrayLiteralConsumer(scope);
return arrayLiteral.consume(startIndex);
}
/**
* consumeFunction
*
* @param startIndex
* @param scope
* @param ref
* @param functionDoc
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeFunction(int startIndex, IScope scope, JSReference ref,
FunctionDocumentation functionDoc) throws EndOfFileException
{
JSFunctionConsumer function = new JSFunctionConsumer(scope, ref, functionDoc);
return function.consume(startIndex);
}
/**
* consumeAssignment
*
* @param startIndex
* @param scope
* @param reference
* @param referenceStartIndex
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeAssignment(int startIndex, IScope scope, JSReference reference,
int referenceStartIndex) throws EndOfFileException
{
JSAssignmentConsumer assignment = new JSAssignmentConsumer(scope, reference, referenceStartIndex);
return assignment.consume(startIndex);
}
/**
* consumeNewStatement
*
* @param startIndex
* @param scope
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
protected LexemeConsumerResult consumeNewStatement(int startIndex, IScope scope) throws EndOfFileException
{
JSNewStatementConsumer newStatement = new JSNewStatementConsumer(scope);
return newStatement.consume(startIndex);
}
/**
* @author Spike Washburn
*/
class LexemeConsumerResult
{
int endIndex;
Object value;
/**
* LexemeConsumerResult
*
* @param endIndex
* @param value
*/
LexemeConsumerResult(int endIndex, Object value)
{
this.endIndex = endIndex;
this.value = value;
}
}
/**
* @author Spike Washburn
*/
abstract class LexemeConsumer
{
/**
* consume
*
* @param startIndex
* @return LexemeConsumerResult
* @throws EndOfFileException
*/
public abstract LexemeConsumerResult consume(int startIndex) throws EndOfFileException;
/**
* Returns the index of the next lexeme that is not a comment or other documentation
*
* @param startIndex
* The index at which to begin searching
* @return The index of the next lexeme in the lexeme list
* @throws EndOfFileException
* Thrown if there is no next lexeme after any EOF whitespace
*/
protected int skipWhitespace(int startIndex) throws EndOfFileException
{
int index = startIndex;
while (index < llSize)
{
index = getJSLexeme(index);
Lexeme l = lastLexeme;
if (l.typeIndex != JSTokenTypes.COMMENT && l.typeIndex != JSTokenTypes.DOCUMENTATION)
{
return index;
}
index++;
}
throw new EndOfFileException();
}
/**
* Retrieves the function documentation from the documentation store.
*
* @param lexeme
* The lexeme for which to retrieve documentation
* @return A new FunctionDocumentation object, or null if not found
*/
protected FunctionDocumentation getFunctionDocumentation(Lexeme lexeme)
{
if (lexeme == null)
{
return null;
}
JSParseState jsps = (JSParseState) parseState.getParseState(JSMimeType.MimeType);
if (jsps != null)
{
IDocumentationStore store = jsps.getDocumentationStore();
IDocumentation doc = store.getDocumentationFromOffset(lexeme.getEndingOffset());
if (doc instanceof FunctionDocumentation)
{
return (FunctionDocumentation) doc;
}
}
return null;
}
/**
* Retrieves the property documentation from the documentation store.
*
* @param lexeme
* The lexeme for which to retrieve documentation
* @return A new PropertyDocumentation object, or null if not found
*/
protected IDocumentation getPropertyDocumentation(Lexeme lexeme)
{
if (lexeme == null)
{
return null;
}
JSParseState jsps = (JSParseState) parseState.getParseState(JSMimeType.MimeType);
if (jsps != null)
{
IDocumentationStore store = jsps.getDocumentationStore();
IDocumentation doc = store.getDocumentationFromOffset(lexeme.offset + lexeme.getLength());
return doc;
}
return null;
}
/**
* findDocumentationLexemeAboveIndex
*
* @param index
* @return Lexeme
*/
protected Lexeme findDocumentationLexemeAboveIndex(int index)
{
int i = index - 1;
int maxLookback = 10;
int minIndex = i > maxLookback ? i - maxLookback : 0;
while (i >= minIndex) // need to limit this
{
Lexeme lexeme = lexemeList.get(i); // needs to get all lang lexemes
if (lexeme.typeIndex == ScriptDocTokenTypes.END_DOCUMENTATION
&& lexeme.getToken().getLanguage().equals(ScriptDocMimeType.MimeType))
{
return lexeme;
}
else
{
switch (lexeme.typeIndex)
{
case JSTokenTypes.COMMENT:
break;
case JSTokenTypes.VAR:
break;
case JSTokenTypes.IDENTIFIER: // for obj literal assignments {
// x:function(){} }
break;
case JSTokenTypes.COLON:
break;
default:
return null;
}
}
i--;
}
return null;
}
}
/**
* @author Spike Washburn
*/
class JSAssignmentConsumer extends LexemeConsumer
{
IScope scope;
boolean isVar;
JSReference reference;
int referenceStartIndex;
/**
* JSAssignmentConsumer
*
* @param scope
* @param reference
* @param referenceStartIndex
*/
JSAssignmentConsumer(IScope scope, JSReference reference, int referenceStartIndex)
{
this.scope = scope;
this.reference = reference;
this.referenceStartIndex = referenceStartIndex;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
int index = startIndex;
// Lexeme lexeme = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme lexeme = lastLexeme;
if (lexeme.typeIndex != JSTokenTypes.EQUAL && lexeme.typeIndex != JSTokenTypes.COLON)
{
throw new IllegalStateException();
}
LexemeConsumerResult result;
index = skipWhitespace(index + 1);
// lexeme = getJSLexeme(index);
index = getJSLexeme(index);
lexeme = lastLexeme;
IObject assignValue = null;
switch (lexeme.typeIndex)
{
case JSTokenTypes.FUNCTION:
if (lastObjectKind.size() > 0)
{
lastObjectKind.pop();
lastObjectKind.push(FUNCTION);
}
FunctionDocumentation functionDoc = getFunctionDocumentation(findDocumentationLexemeAboveIndex(referenceStartIndex));
JSFunctionConsumerResult fResult = (JSFunctionConsumerResult) consumeFunction(index, scope,
reference, functionDoc);
if (fResult.function != null)
{
envLoader.registerScope(fResult.function.getBodyScope(), fResult.scopeStartingOffset,
fResult.scopeEndingOffset);
if (reference.getObjectBase() instanceof JSObject)
{
fResult.function.setGuessedMemberObject((JSObject) reference.getObjectBase());
}
}
index = fResult.endIndex;
break;
case JSTokenTypes.NEW:
result = consumeNewStatement(index, scope);
index = result.endIndex;
// assign the new'd object to the property reference
assignValue = (IObject) result.value;
if (assignValue != null)
{
envLoader.putPropertyValue(reference, assignValue);
}
break;
case JSTokenTypes.STRING:
index++;
assignValue = envLoader.createNewInstance("String", lexeme.getStartingOffset(), false); //$NON-NLS-1$
envLoader.putPropertyValue(reference, assignValue);
break;
case JSTokenTypes.NUMBER:
index++;
assignValue = envLoader.createNewInstance("Number", lexeme.getStartingOffset(), false); //$NON-NLS-1$
envLoader.putPropertyValue(reference, assignValue);
break;
case JSTokenTypes.NULL:
index++;
assignValue = ObjectBase.NULL;
envLoader.putPropertyValue(reference, assignValue);
break;
case JSTokenTypes.TRUE:
case JSTokenTypes.FALSE:
index++;
assignValue = envLoader.createNewInstance("Boolean", lexeme.getStartingOffset(), false); //$NON-NLS-1$
envLoader.putPropertyValue(reference, assignValue);
break;
case JSTokenTypes.REGEX:
index++;
assignValue = envLoader.createNewInstance("RegExp", lexeme.getStartingOffset(), false); //$NON-NLS-1$
envLoader.putPropertyValue(reference, assignValue);
break;
case JSTokenTypes.LCURLY:
// an object literal has been encountered, so let the object literal consumer
// process it
if (lastObjectKind.size() > 0)
{
lastObjectKind.pop();
lastObjectKind.push(OBJECT_LITERAL);
}
// create an object to hold the properties defined by the object literal
IObject objectLiteral = new JSObject(
new Range(lexeme.getStartingOffset(), lexeme.getEndingOffset()));
if (reference != null)
{
IObject o = null;
Property p = reference.getObjectBase().getProperty(reference.getPropertyName());
if (p != null)
{
o = p.getValue(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
if (o != null)
{
objectLiteral = o;
}
envLoader.putPropertyValue(reference, objectLiteral);
}
result = consumeObjectLiteral(index, scope, objectLiteral);
index = result.endIndex;
// assign the object literal value to the property reference
assignValue = (IObject) result.value;
if (assignValue != null)
{
envLoader.putPropertyValue(reference, assignValue);
}
break;
case JSTokenTypes.LBRACKET:
// an array literal has been encountered, so let the array literal consumer
// process it
result = consumeArrayLiteral(index, scope);
index = result.endIndex;
// assign the object literal value to the property reference
assignValue = (IObject) result.value;
if (assignValue != null)
{
envLoader.putPropertyValue(reference, assignValue);
}
break;
case JSTokenTypes.IDENTIFIER:
String hashName = LexemeConsumerHelper.getNameHash(index, lexemeList);
boolean isInvoking = hashName.endsWith(")"); //$NON-NLS-1$
// checking docs first
String retName = LexemeConsumerHelper.lookupReturnStringFromHash(hashName, scope, env);
if (retName != null && !retName.equals("")) //$NON-NLS-1$
{
assignValue = envLoader.createNewInstance(retName, lexeme.getStartingOffset(), isInvoking);
envLoader.putPropertyValue(reference, assignValue);
}
else
// else checking assignment chain
{
IObject obj = LexemeConsumerHelper.lookupReturnTypeFromNameHash(hashName, scope, env);
int offset = lexeme.getStartingOffset();
int fileIndex = envLoader.getFileIndex();
if (obj instanceof IFunction) // functions can be used as ctors
{
IFunction f = (IFunction) obj;
assignValue = f.construct(env, FunctionBase.EmptyArgs, fileIndex, new Range(offset,
offset + 1));
envLoader.putPropertyValue(reference, assignValue);
}
else if (obj != null) // else make a dup of the result type
{
IObject fobj = obj.getPropertyValue("constructor", fileIndex, offset); //$NON-NLS-1$
if (fobj != null && fobj instanceof IFunction)
{
IFunction f = (IFunction) fobj;
assignValue = f.construct(env, FunctionBase.EmptyArgs, fileIndex, new Range(offset,
offset + 1));
envLoader.putPropertyValue(reference, assignValue);
}
}
}
// if(!hashName.endsWith(")") && obj != null && obj.getDocumentation()!= null &&
// obj.getDocumentation() instanceof PropertyDocumentation)
// {
// PropertyDocumentation pdoc = (PropertyDocumentation)obj.getDocumentation();
// if(pdoc.getReturn() != null && pdoc.getReturn().getTypes()!= null )
// {
// String[] types = pdoc.getReturn().getTypes();
// if(types.length > 0)
// {
// assignValue = envLoader.createNewInstance(types[0],
// lexeme.getStartingOffset());
// envLoader.putPropertyValue(reference, assignValue);
// }
// }
// }
break;
default:
break;
}
return new LexemeConsumerResult(index, assignValue);
}
}
/**
* @author Spike Washburn
*/
class JSStatementConsumer extends LexemeConsumer
{
boolean isVar;
IScope parentScope;
int curlyDepth;
JSReference assignToReference; // an environment reference for an identifier that is being
// assigned to a value
int assignToLexemeStartIndex; // the startIndex of an identifier that is being assigned to
// a value
/**
* JSStatementConsumer
*
* @param parentScope
*/
JSStatementConsumer(IScope parentScope)
{
this.parentScope = parentScope;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
int index = startIndex;
while (index < llSize)
{
index = skipWhitespace(index);
// Lexeme lexeme = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme lexeme = lastLexeme;
switch (lexeme.typeIndex)
{
case JSTokenTypes.FUNCTION:
JSFunctionConsumerResult fResult = (JSFunctionConsumerResult) consumeFunction(index,
parentScope, null, null);
if (fResult.function != null) // && fResult.name != null)
{
// envLoader.putPropertyValue(parentScope, fResult.name,
// fResult.function, true);
envLoader.registerScope(fResult.function.getBodyScope(), fResult.scopeStartingOffset,
fResult.scopeEndingOffset);
}
index = fResult.endIndex;
break;
case JSTokenTypes.IDENTIFIER:
case JSTokenTypes.THIS:
int identifierStartIndex = index;
LexemeConsumerHelper.LexemeConsumerResult result = consumeIdentifier(index, parentScope, isVar);
isVar = false;
index = result.endIndex;
// scan ahead to see if this variable is about6 to get an assignment of a
// function, or a new
// object
// if so, consume the right side and save the result into the value of the
// identifier's property
int nextIndex = skipWhitespace(index + 1);
// Lexeme nextLexeme = getJSLexeme(nextIndex);
nextIndex = getJSLexeme(nextIndex);
Lexeme nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.EQUAL)
{
assignToLexemeStartIndex = identifierStartIndex;
assignToReference = (JSReference) result.value;
}
break;
case JSTokenTypes.VAR:
isVar = true;
break;
case JSTokenTypes.LCURLY:
curlyDepth++;
break;
case JSTokenTypes.RCURLY:
curlyDepth--;
if (curlyDepth < 0)
{
return new LexemeConsumerResult(index, null);
}
break;
case JSTokenTypes.EQUAL:
{
IObject assignedValue = null;
if (assignToReference != null)
{
nextIndex = skipWhitespace(index + 1);
// nextLexeme = getJSLexeme(nextIndex);
nextIndex = getJSLexeme(nextIndex);
nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.FUNCTION // function value
// assignment
|| nextLexeme.typeIndex == JSTokenTypes.NEW // 'new' object
// value assignment
|| nextLexeme.typeIndex == JSTokenTypes.LCURLY // object
// literal value
// assignment
|| nextLexeme.typeIndex == JSTokenTypes.LBRACKET // object
// literal
// value
// assignment
)
{
result = consumeAssignment(index, parentScope, assignToReference,
assignToLexemeStartIndex);
index = result.endIndex;
assignedValue = (IObject) result.value;
}
else if (nextLexeme.typeIndex == JSTokenTypes.STRING
|| nextLexeme.typeIndex == JSTokenTypes.NUMBER
|| nextLexeme.typeIndex == JSTokenTypes.REGEX
|| nextLexeme.typeIndex == JSTokenTypes.TRUE
|| nextLexeme.typeIndex == JSTokenTypes.FALSE)
{
// skip ahead one more lexeme to see if its a semicolon
nextIndex = skipWhitespace(nextIndex + 1);
// nextLexeme = getJSLexeme(nextIndex);
nextIndex = getJSLexeme(nextIndex);
nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.SEMICOLON)
{
result = consumeAssignment(index, parentScope, assignToReference,
assignToLexemeStartIndex);
index = result.endIndex;
assignedValue = (IObject) result.value;
}
}
else if (nextLexeme.typeIndex == JSTokenTypes.IDENTIFIER)
{
result = consumeAssignment(index, parentScope, assignToReference,
assignToLexemeStartIndex);
index = result.endIndex;
assignedValue = (IObject) result.value;
}
// this is an unsupported assignment
// if there was a documentation block above this assignment, then bind
// it to the property
IDocumentation propertyDoc = getPropertyDocumentation(findDocumentationLexemeAboveIndex(assignToLexemeStartIndex));
if (propertyDoc != null)
{
// TODO: Check with Kevin...should the documentation be attached to
// the property and/or
// the value?
// assignToReference.getProperty().setDocumentation(propertyDoc);
IObject propValue = assignedValue;
if (propValue == null || propValue == ObjectBase.UNDEFINED)
{
propValue = envLoader.getPropertyValue(assignToReference, lexeme
.getStartingOffset());
}
if (propValue != ObjectBase.UNDEFINED)
{
try
{
if (propertyDoc instanceof PropertyDocumentation)
{
PropertyDocumentation pDoc = (PropertyDocumentation) propertyDoc;
IObject retValue = null;
TypedDescription typeDesc = pDoc.getReturn();
if (typeDesc != null && typeDesc.getTypes().length > 0)
{
String type = typeDesc.getTypes()[0];
if (type != null)
{
retValue = envLoader.createNewInstance(type, lexeme
.getStartingOffset(), false);
envLoader.putPropertyValue(assignToReference, retValue);
propValue.setPrototype(retValue);
}
}
addPotentialAliases(propValue, pDoc.getAliases());
// TypedDescription alias = pDoc.getAlias();
// String[] aliasTypes = alias.getTypes();
// if(retValue != null && alias != null &&
// aliasTypes.length > 0)
// {
// for (int i = 0; i < aliasTypes.length; i++)
// {
// String type = aliasTypes[i];
// if(type != null && type != "")
// {
// String name = type;
// IObject root = env.getGlobal();
// if(type.indexOf(".") > -1)
// {
// int dotLoc = type.lastIndexOf(".");
// String basename = type.substring(0, dotLoc);
// name = type.substring(dotLoc);
// root = lookupOrCreateObject(basename, env);
// }
// JSReference aliasRef = new JSReference(root, name,
// true);
// envLoader.putPropertyValue(aliasRef, retValue);
// }
// }
// }
}
}
catch (Exception e)
{
IdeLog.logError(JSPlugin.getDefault(), Messages.LexemeConsumerHelper_ErrorSettingPrototype, e);
}
if (propValue != null)
{
propValue.setDocumentation(propertyDoc);
}
}
}
// remove the assignToReference now that the assignment is complete.
assignToReference = null;
assignToLexemeStartIndex = -1;
}
break;
}
default:
break;
}
index++;
}
throw new EndOfFileException();
}
}
/**
* @author Spike Washburn
*/
class JSObjectLiteralConsumer extends LexemeConsumer
{
IScope parentScope;
IObject objectLiteral;
/**
* JSObjectLiteralConsumer
*
* @param parentScope
* @param objectLiteral
*/
JSObjectLiteralConsumer(IScope parentScope, IObject objectLiteral)
{
this.parentScope = parentScope;
this.objectLiteral = objectLiteral;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
// Lexeme lexeme = getJSLexeme(startIndex);
startIndex = getJSLexeme(startIndex);
Lexeme lexeme = lastLexeme;
if (lexeme.typeIndex != JSTokenTypes.LCURLY)
{
throw new IllegalStateException();
}
// consumes the LCurly, and process until the rCurly is hit
int curlyDepth = 1;
int index = startIndex;
JSReference currentReference = null; // the reference to the property associated
// with the named identifiers
int currentReferenceStartIndex = -1;
while (index < llSize && curlyDepth > 0)
{
if (index + 1 < llSize)
{
index = skipWhitespace(index + 1);
}
index = getJSLexeme(index);
lexeme = lastLexeme;
LexemeConsumerResult result;
switch (lexeme.typeIndex)
{
case JSTokenTypes.RCURLY:
curlyDepth--;
break;
case JSTokenTypes.COMMA:
// NOTE: If we don't advance here, last lexemes that are commas cause an infinite loop
if (index + 1 == llSize)
{
index++;
}
// skip commas
break;
case JSTokenTypes.IDENTIFIER:
currentReferenceStartIndex = index;
result = consumeIdentifier(index, parentScope, objectLiteral);
// save the identifier's property reference so it can be assigned a value.
currentReference = (JSReference) result.value;
index = result.endIndex;
break;
case JSTokenTypes.COLON:
// int firstIndex = startIndex;
// int curAssignmentStart = index + 1;
lastObjectKind.push("unknown"); //$NON-NLS-1$
if (currentReference != null)
{
// let the assignment processor consume the r-side value of the
// assignment
result = consumeAssignment(index, parentScope, currentReference, currentReferenceStartIndex);
index = result.endIndex;
}
String lastObj = (String) lastObjectKind.pop();
boolean wasObjLit = lastObj.equals(OBJECT_LITERAL);
boolean wasFn = lastObj.equals(FUNCTION);
// int lastIndex = wasObjectLiteral ? curAssignmentStart : index;
// we need to check if that was an object literal,
// however we have no context...
// a) if we are on a rcurly and this wasn't an object literal or fn, that is
// a close brace
if (!wasObjLit && !wasFn && index < llSize)
{
Lexeme curLexeme = lexemeList.get(index);
if (curLexeme.typeIndex == JSTokenTypes.RCURLY)
{
curlyDepth--;
}
}
// b) Don't need this empty obj literal test as the above will catch it
// if(wasObjLit && index < llSize && index > 0)// && firstIndex < llSize &&
// lastIndex < llSize)
// {
// Lexeme lastLexeme = lexemeList.get(index - 1);
// if(lastLexeme.typeIndex == JSTokenTypes.LCURLY)
// {
// curlyDepth--;
// }
// }
break;
default:
curlyDepth = 0;
break;
}
}
return new LexemeConsumerResult(index, objectLiteral);
}
}
/**
* @author Spike Washburn
*/
class JSArrayLiteralConsumer extends LexemeConsumer
{
IScope parentScope;
/**
* JSArrayLiteralConsumer
*
* @param parentScope
*/
JSArrayLiteralConsumer(IScope parentScope)
{
this.parentScope = parentScope;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
startIndex = getJSLexeme(startIndex);
Lexeme lexeme = lastLexeme;
if (lexeme.typeIndex != JSTokenTypes.LBRACKET)
{
throw new IllegalStateException();
}
int startingOffset = lexeme.getStartingOffset();
// consumes the LCurly, and process until the rCurly is hit
int bracketDepth = 1;
int index = startIndex;
if (index + 1 < llSize)
{
index = skipWhitespace(index + 1);
}
// get the first object to guess the type of array, if possible
String type = "Object"; //$NON-NLS-1$
// lexeme = getJSLexeme(index);
index = getJSLexeme(index);
lexeme = lastLexeme;
switch (lexeme.typeIndex)
{
case JSTokenTypes.RBRACKET:
bracketDepth--;
break;
case JSTokenTypes.STRING:
type = "String"; //$NON-NLS-1$
break;
case JSTokenTypes.NUMBER:
type = "Number"; //$NON-NLS-1$
break;
case JSTokenTypes.TRUE:
case JSTokenTypes.FALSE:
type = "Boolean"; //$NON-NLS-1$
break;
case JSTokenTypes.REGEX:
type = "RegExp"; //$NON-NLS-1$
break;
case JSTokenTypes.LBRACKET:
type = "Array"; //$NON-NLS-1$
break;
case JSTokenTypes.FUNCTION:
type = "Function"; //$NON-NLS-1$
break;
case JSTokenTypes.NEW:
if (index + 1 < llSize)
{
index = skipWhitespace(index + 1);
}
index = getJSLexeme(index);
lexeme = lastLexeme;
if (lexeme.typeIndex == JSTokenTypes.RBRACKET)
{
bracketDepth = 0;
}
else if (lexeme.typeIndex == JSTokenTypes.IDENTIFIER)
{
// todo: maybe consume ns ident
type = lexeme.getText();
}
break;
default:
break;
}
while (index < llSize && bracketDepth > 0)
{
if (index + 1 < llSize)
{
index = skipWhitespace(index + 1);
}
else
{
bracketDepth = 0;
break;
}
index = getJSLexeme(index);
lexeme = lastLexeme;
switch (lexeme.typeIndex)
{
case JSTokenTypes.RBRACKET:
bracketDepth--;
break;
default:
break;
}
if (lexeme.isAfterEOL())
{
bracketDepth = 0;
}
}
// add return type to doc
PropertyDocumentation doc = new PropertyDocumentation();
doc.getReturn().addType("Array"); //$NON-NLS-1$
doc.getReturn().addType(type);
// create an object to hold the properties defined by the object literal
IObject array = envLoader.createNewInstance("Array", startingOffset, false); //$NON-NLS-1$
array.setDocumentation(doc);
return new LexemeConsumerResult(index, array);
}
}
/**
* @author Spike Washburn
*/
class JSNewStatementConsumer extends LexemeConsumer
{
IScope scope;
/**
* JSNewStatementConsumer
*
* @param scope
*/
JSNewStatementConsumer(IScope scope)
{
this.scope = scope;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
int index = startIndex;
// Lexeme lexeme = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme lexeme = lastLexeme;
if (lexeme.typeIndex != JSTokenTypes.NEW)
{
throw new IllegalStateException();
}
Range range = new Range(lexeme.getStartingOffset(), lexeme.getEndingOffset());
index = skipWhitespace(index + 1);
// lexeme = getJSLexeme(index);
index = getJSLexeme(index);
lexeme = lastLexeme;
IObject newValue = null;
if (lexeme.typeIndex == JSTokenTypes.IDENTIFIER)
{
LexemeConsumerResult result = consumeIdentifier(index, scope, false);
index = result.endIndex;
JSReference propReference = (JSReference) result.value;
IObject value;
if (propReference.getObjectBase() instanceof IScope)
{
value = envLoader.getVariableValue((IScope) propReference.getObjectBase(), propReference
.getPropertyName(), range.getStartingOffset());
}
else
{
value = envLoader.getPropertyValue(propReference, range.getStartingOffset());
}
// todo: need to check here for more elements, eg var x = new Date().now;
if (value instanceof IFunction)
{
// create a new instance of the function
// Note: createNewInstance hooks the function's prototype values up to the new
// instance
newValue = envLoader.createNewInstance(range.getStartingOffset(), (IFunction) value);
}
}
else
{
// this new operator is invalid, so roll our consumption back to the new lexeme
index = startIndex;
}
if (newValue == null)
{
// there was a problem creating an instance of a function, so just return an empty
// object
newValue = new JSObject(range);
}
return new LexemeConsumerResult(index, newValue);
}
}
/**
* @author Spike Washburn
*/
class JSFunctionConsumer extends LexemeConsumer
{
private IScope parentScope;
private String functionName;
private ArrayList functionArgs = new ArrayList();
private JSReference reference; // the reference to attach the function to
private FunctionDocumentation functionDocs;
/**
* JSFunctionConsumer
*
* @param parentScope
* @param ref
* @param functionDocs
*/
JSFunctionConsumer(IScope parentScope, JSReference ref, FunctionDocumentation functionDocs)
{
this.parentScope = parentScope;
this.reference = ref;
this.functionDocs = functionDocs;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
int index = startIndex;
// Lexeme lexeme = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme lexeme = lastLexeme;
if (lexeme.typeIndex != JSTokenTypes.FUNCTION)
{
throw new IllegalStateException();
}
int functionStart = lexeme.getEndingOffset();
FunctionDocumentation functionDoc = functionDocs;
if (functionDoc == null)
{
functionDoc = getFunctionDocumentation(findDocumentationLexemeAboveIndex(index));
}
index = advanceToLCurly(index);
// Lexeme lCurly = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme lCurly = lastLexeme;
if (lCurly.typeIndex != JSTokenTypes.LCURLY)
{
return new JSFunctionConsumerResult(index, null, functionName, 0, 0);
}
// setup the function arguments
String[] funcArgs = new String[this.functionArgs.size()];
for (int i = 0; i < funcArgs.length; i++)
{
Lexeme l = (Lexeme) functionArgs.get(i);
funcArgs[i] = l.getText();
}
// create the function
int functionOffset = lexeme.getStartingOffset();
if (functionName != null)
{
// HACK: we need function definitions to be declared before all other objects in the
// file
// since global declarations are theoretically interpreted first. To create this
// appearance,
// we set the function offset to a negative value so that all non-Function
// assignments
functionOffset = Integer.MIN_VALUE + 1 + lexeme.getStartingOffset();
}
JSFunction func = envLoader.createFunctionInstance(functionOffset, false);
// JSFunction func = envLoader.createNewInstance(functionName, functionOffset);
// [RD] adding probable assignement ref for prototype mapping
if (this.reference != null && this.reference.getObjectBase() instanceof JSObject)
{
func.setGuessedMemberObject((JSObject) this.reference.getObjectBase());
}
else
{
func.setGuessedMemberObject((JSObject) func.getLocalProperty("prototype").getValue( //$NON-NLS-1$
FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE));
}
IScope bodyScope = new JSScope();
// set body's parent scope
bodyScope.setParentScope(parentScope);
bodyScope.setEnclosingFunction(func);
func.setBodyScope(bodyScope);
// func.setGuessedMemberObject((JSObject)func.getLocalProperty("prototype").getValue(Integer.MAX_VALUE,
// Integer.MAX_VALUE));
if (functionDoc != null)
{
func.setDocumentation(functionDoc);
}
// set the function parameter names, and pre-add the args to the function's scope
func.setParameters(funcArgs);
TypedDescription[] argTypes = functionDoc != null ? functionDoc.getParams() : new TypedDescription[0];
int size = functionArgs.size();
for (int i = 0; i < size; i++)
{
String type = null;
TypedDescription argDesc = null;
if (argTypes.length > i)
{
argDesc = argTypes[i];
String[] types = argDesc.getTypes();
type = types.length > 0 ? types[0] : type;
}
Lexeme l = (Lexeme) functionArgs.get(i);
if (type == null)
{
envLoader.addVariable(bodyScope, l.getText(), l.getStartingOffset(), true);
}
else
{
// here we want to create an instance of the arg type
// this will only happen if the type is an IFunction
// otherwise it will just use the passed type (assuming a passed object with
// props directly on it)
IObject argVal = null;
IObject classType = lookupReturnTypeFromNameHash(type, env.getGlobal(), env);
boolean isFunction = true;
if (classType instanceof IFunction)
{
argVal = envLoader.createNewInstance(functionOffset, (IFunction) classType);
}
else
{
isFunction = false;
argVal = classType;// envLoader.createNewInstance(type,
// l.getStartingOffset(), false);
}
if (argVal != null)
{
// [RD] set docs using newly created doc based on the param arg
envLoader.putVariableValue(bodyScope, l.getText(), argVal, true);
PropertyDocumentation argDoc = new PropertyDocumentation();
String desc = argDesc.getDescription() == null ? "" : argDesc.getDescription(); //$NON-NLS-1$
argDoc.setDescription(desc);
// only doc return types on functions
if (isFunction)
{
argDoc.getReturn().addType(type);
}
argVal.setDocumentation(argDoc);
}
}
}
// add arguments object
IObject argsArray = envLoader.addVariable(bodyScope, "arguments", lCurly.getStartingOffset(), true); //$NON-NLS-1$
int fileIndex = FileContextManager.BUILT_IN_FILE_INDEX;
IObject gFn = env.getGlobal().getPropertyValue("Function", fileIndex, 0); //$NON-NLS-1$
IObject fnprot = gFn.getPropertyValue("prototype", fileIndex, 0); //$NON-NLS-1$
IObject args = fnprot.getPropertyValue("arguments", fileIndex, 0); //$NON-NLS-1$
argsArray.setDocumentation(args.getDocumentation());
if (functionDoc != null && functionDoc.getExtends() != null)
{
String[] types = functionDoc.getExtends().getTypes();
IObject fnObj = null;
if (types.length > 0)
{
String type = types[0];
if (!type.equals("Object")) //$NON-NLS-1$
{
// maybe this just needs to be global lookup? (not return type?)
fnObj = lookupReturnTypeFromNameHash(type, env.getGlobal(), env);
if (fnObj != null)
{
Property prot = fnObj.getProperty("prototype"); //$NON-NLS-1$
if (prot != null)
{
// this should only work on local properties, so safe
func.deletePropertyName("prototype"); //$NON-NLS-1$
// probably should be adding via putPropertyValue() here to be safe
// with refs
func.putPropertyValue("prototype", prot.getAssignment(0), envLoader.getFileIndex()); //$NON-NLS-1$
// func.putLocalProperty("prototype", prot);
}
}
}
}
addPotentialAliases(func, functionDoc.getAliases());
}
// Register the function with the environment to guarantee all references made to the
// function inside the body augment this function instance instead of creating an
// implicit JSObject.
// Note: if the function is not assigned to the environment before the statements in the
// body are
// processed, then any references to the function inside the body will cause a JSObject
// to be created
// implicitly. Since those assignments have a higher starting offset than the function's
// starting offset,
// the function's real declaration would no longer be visible.
if (reference != null)
{
envLoader.putPropertyValue(reference, func);
}
else if (functionName != null)
{
envLoader.replaceFunctionDeclaration(lexeme.getStartingOffset(), parentScope, functionName, func);
}
// save the scope's starting offset
// int startScopeOffset = lCurly.getStartingOffset();
// Consume the lCurly, and then let the statement consumer have it (it will stop when it
// hits the first
// unbalanced rCurly)
index = skipWhitespace(index + 1);
LexemeConsumerHelper.LexemeConsumerResult result = consumeStatements(index, bodyScope);
// leave the final index at the function's rCurly (which was the stopping point of the
// statement consumer
index = result.endIndex;
// save the scope's ending offset
index = getJSLexeme(index);
Lexeme offsetLx = lastLexeme;
int endScopeOffset = offsetLx.getEndingOffset();
// return the function result
// [RD]changed this to start after the word function so the name and args are in the
// function scope
return new JSFunctionConsumerResult(result.endIndex, func, functionName, functionStart, endScopeOffset);// startScopeOffset,
// endScopeOffset);
}
private int advanceToLCurly(int functionIndex) throws EndOfFileException
{
int index = skipWhitespace(functionIndex + 1);
// consume the function's declared name if there is one
// Lexeme nextLexeme = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.IDENTIFIER)
{
functionName = nextLexeme.getText();
index = this.skipWhitespace(index + 1);
}
// consume the functions arguments
// nextLexeme = getJSLexeme(index);
index = getJSLexeme(index);
nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.LPAREN)
{
index = skipWhitespace(index + 1);
// nextLexeme = getJSLexeme(index);
index = getJSLexeme(index);
nextLexeme = lastLexeme;
while (nextLexeme.typeIndex != JSTokenTypes.RPAREN)
{
if (nextLexeme.typeIndex == JSTokenTypes.IDENTIFIER)
{
functionArgs.add(nextLexeme);
}
else if (nextLexeme.typeIndex != JSTokenTypes.COMMA)
{
return index; // an invalid argument token was hit, so abort
}
index = skipWhitespace(index + 1);
// nextLexeme = getJSLexeme(index);
index = getJSLexeme(index);
nextLexeme = lastLexeme;
}
// advance to the next lexeme after the rParen (should be the lcurly...)
index = skipWhitespace(index + 1);
// nextLexeme = getJSLexeme(index);
index = getJSLexeme(index);
nextLexeme = lastLexeme;
}
// the index should now be at the LCURLY, if its not this is still the correct place to
// stop
if (nextLexeme.typeIndex == JSTokenTypes.LCURLY)
{
return index;
}
return index;
}
}
/**
* @author Spike Washburn
*/
class JSFunctionConsumerResult extends LexemeConsumerResult
{
String name;
JSFunction function;
int scopeStartingOffset;
int scopeEndingOffset;
/**
* JSFunctionConsumerResult
*
* @param endIndex
* @param function
* @param name
* @param scopeStartingOffset
* @param scopeEndingOffset
*/
JSFunctionConsumerResult(int endIndex, JSFunction function, String name, int scopeStartingOffset,
int scopeEndingOffset)
{
super(endIndex, function);
this.name = name;
this.function = function;
this.scopeStartingOffset = scopeStartingOffset;
this.scopeEndingOffset = scopeEndingOffset;
}
}
/**
* @author Spike Washburn
*/
class JSIdentifierConsumer extends LexemeConsumer
{
private IScope scope;
private boolean isVar;
private JSReference reference;
IObject parentObject;
/**
* JSIdentifierConsumer
*
* @param scope
* @param isVar
*/
JSIdentifierConsumer(IScope scope, boolean isVar)
{
this.scope = scope;
this.isVar = isVar;
parentObject = scope;
}
JSIdentifierConsumer(IScope scope, IObject parentObject)
{
this.scope = scope;
this.isVar = false;
this.parentObject = parentObject;
}
/**
* @see com.aptana.ide.editor.js.environment.LexemeConsumerHelper.LexemeConsumer#consume(int)
*/
public LexemeConsumerResult consume(int startIndex) throws EndOfFileException
{
// int index = skipWhitespace(startIndex);
int index = startIndex;
// Lexeme lexeme = getJSLexeme(index);
index = getJSLexeme(index);
Lexeme lexeme = lastLexeme;
boolean isLocalVar = isVar || lexeme.typeIndex == JSTokenTypes.THIS;
while (lexeme != null && index != -1)
{
if (lexeme.typeIndex == JSTokenTypes.IDENTIFIER || lexeme.typeIndex == JSTokenTypes.THIS)
{
String propertyName = lexeme.getText();
// if there are brackets in name, skip over them
// TODO: support nexting
int nextIndex = skipWhitespace(index + 1);
// Lexeme nextLexeme = getJSLexeme(nextIndex);
nextIndex = getJSLexeme(nextIndex);
Lexeme nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.LBRACKET)
{
int bracketDepth = 1;
while (bracketDepth > 0 && nextLexeme != null)
{
nextIndex = skipWhitespace(nextIndex + 1);
// nextLexeme = getJSLexeme(nextIndex);
nextIndex = getJSLexeme(nextIndex);
nextLexeme = lastLexeme;
if (nextLexeme.typeIndex == JSTokenTypes.LBRACKET)
{
bracketDepth++;
}
else if (nextLexeme.typeIndex == JSTokenTypes.RBRACKET)
{
bracketDepth--;
}
}
index = nextIndex;
// update the name to include [] so that there's a place to hang properties
// referenced via the
// brackets.
propertyName = propertyName + "[]"; //$NON-NLS-1$
}
reference = new JSReference(parentObject, propertyName, isLocalVar);
IObject parentTemp = parentObject;
if (parentObject instanceof IScope)
{
parentObject = envLoader.addVariable(scope, propertyName, lexeme.getStartingOffset(),
isLocalVar);
isLocalVar = false; // the variable is no longer a var since the next parent
// is an object
}
else
{
parentObject = envLoader.addProperty(scope, parentObject, propertyName, lexeme
.getStartingOffset());
}
// look for special 'this' case and reset parent obj if found
if (propertyName.equals("this") && parentTemp instanceof IScope) //$NON-NLS-1$
{
IFunction fn = ((IScope) parentTemp).getEnclosingFunction();
if (fn instanceof JSFunction)
{
JSObject guessedPrototype = ((JSFunction) fn).getGuessedMemberObject();
if (guessedPrototype != null)
{
parentObject = guessedPrototype;// envLoader.addProperty(fn.getBodyScope(),
// guessedPrototype, propertyName,
// lexeme.getStartingOffset());
}
}
}
}
else if (lexeme.typeIndex != JSTokenTypes.DOT)
{
// the current token is not part of the property name, so go back one and return
index = index - 1;
break;
}
index++;
if (llSize > index)
{
// lexeme = getJSLexeme(index);
index = getJSLexeme(index);
lexeme = lastLexeme;
}
else
{
lexeme = null;
}
}
return new LexemeConsumerResult(index, reference);
}
}
private int lastIndex = -1;
private Lexeme lastLexeme = null;
private Stack lastObjectKind = new Stack();
private final String OBJECT_LITERAL = "__objectLiteral__"; //$NON-NLS-1$
private final String FUNCTION = "__function__"; //$NON-NLS-1$
private int getJSLexeme(int index)
{
if (index == lastIndex)
{
return index;
}
lastLexeme = lexemeList.get(index);
if (!lastLexeme.getLanguage().equals(JSMimeType.MimeType))
{
// lx = null;
while (++index < llSize)
{
lastLexeme = lexemeList.get(index);
if (lastLexeme.getLanguage().equals(JSMimeType.MimeType))
{
break;
}
}
}
lastIndex = index;
return lastIndex;
// return lastLexeme;
}
// ******************************
// * helper methods
// ******************************
private static String NOT_AN_IDENTIFIER = "NOT_AN_IDENTIFIER"; //$NON-NLS-1$
private static String getNameHash(int position, LexemeList lexemes)
{
String name = ""; //$NON-NLS-1$
int parenCount = 0;
int lexLen = lexemes.size();
// backtrack over lexemes to find name - this can include parens and
// dots and commas, but no args - eg - "Math.fn(,,).tostring()"
while (position < lexLen)
{
Lexeme curLexeme = lexemes.get(position);
// reset name unless in a new stmt.
if (name.equals(NOT_AN_IDENTIFIER))
{
name = ""; //$NON-NLS-1$
}
// now continue to add to string
switch (curLexeme.typeIndex)
{
case JSTokenTypes.LPAREN:
int startParenCount = parenCount;
name += "("; //$NON-NLS-1$
parenCount++;
while (++position < lexLen)
{
Lexeme lx = lexemes.get(position);
if (lx.typeIndex == JSTokenTypes.LPAREN)
{
parenCount++;
}
else if (lx.typeIndex == JSTokenTypes.RPAREN)
{
parenCount--;
}
if (startParenCount == parenCount)
{
name += ")"; //$NON-NLS-1$
break;
}
}
break;
case JSTokenTypes.RPAREN:
name += ")"; //$NON-NLS-1$
parenCount--;
// if(parenCount == 0) // inside parens ( like "if(" )
// {
// if(position < lexLen)
// {
// Lexeme nextLex =lexemes.get(position + 1);
// if(nextLex.getCategoryIndex() == JSTokenCategories.KEYWORD &&
// nextLex.typeIndex !=
// JSTokenTypes.TYPEOF)
// {
// return name;
// }
// else
// {
// position = 0;
// break;
// }
// }
// }
break;
case JSTokenTypes.IDENTIFIER:
name += curLexeme.getText();
break;
// case JSTokenTypes.COMMA:
case JSTokenTypes.DOT:
name += curLexeme.getText();
break;
case JSTokenTypes.WHITESPACE:
break;
default:
position = lexLen; // end backtrack loop
break;
}
if (curLexeme.isAfterEOL())
{
break;
}
position++;
}
return name;
}
private static String lookupReturnStringFromHash(String fullname, IScope scope, Environment environment)
{
if (fullname.equals(NOT_AN_IDENTIFIER))
{
return null;
}
if (fullname.length() == 0)
{
return null;
}
String result = ""; //$NON-NLS-1$
// look up object of base part
int lastDot = fullname.lastIndexOf("."); //$NON-NLS-1$
String baseName = ""; //$NON-NLS-1$
String name = fullname;
if (lastDot > -1)
{
baseName = fullname.substring(0, lastDot);
name = fullname.substring(lastDot + 1);
}
IObject obj = environment.getGlobal();
if (baseName != "") //$NON-NLS-1$
{
obj = lookupReturnTypeFromNameHash(baseName, scope, environment);
}
if (obj == null)
{
return ""; //$NON-NLS-1$
}
boolean isInvoking = false;
int firstParen = name.indexOf("("); //$NON-NLS-1$
if (name.endsWith(")") && firstParen > -1) //$NON-NLS-1$
{
isInvoking = true;
name = name.substring(0, firstParen);
}
IObject robj = obj.getPropertyValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE)
.getInstance(environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
if (!isInvoking && robj instanceof IFunction)
{
// result = "Function";
result = fullname;// name;
}
else if (robj.getDocumentation() != null && robj.getDocumentation() instanceof PropertyDocumentation)
{
PropertyDocumentation pdoc = ((PropertyDocumentation) robj.getDocumentation());
if (pdoc.getReturn() != null && pdoc.getReturn().getTypes() != null)
{
String[] types = pdoc.getReturn().getTypes();
if (types.length > 0)
{
if (pdoc.getIsInstance())
{
// FIXME: This is a nice hack to indicate that we need to create an instance
// in LexemeBasedEnvironmentLoader.createInstance
result = "+" + types[0]; //$NON-NLS-1$
}
else
{
result = types[0];
}
}
}
}
else
{
name = name.toString();
}
return result;
}
private static IObject lookupReturnTypeFromNameHash(String fullname, IScope scope, Environment environment)
{
if (fullname.equals(NOT_AN_IDENTIFIER))
{
return null;
}
if (fullname.length() == 0)
{
return null;
}
// split the full name, then look up each segment, find return types if
// needed as we parse the name.
String[] names = fullname.split("\\."); //$NON-NLS-1$
IObject obj = scope;
for (int i = 0; i < names.length; i++)
{
String name = names[i];
boolean isMethodCall = false;
if (name.endsWith("()")) //$NON-NLS-1$
{
isMethodCall = true;
name = name.substring(0, name.length() - 2);
}
if (i == 0)
{
if (name.equals("this") && obj instanceof IScope) //$NON-NLS-1$
{
IFunction enclFn = ((IScope) obj).getEnclosingFunction();
if (enclFn != null && enclFn instanceof JSFunction)
{
JSFunction fn = (JSFunction) enclFn;
IDocumentation doc = fn.getDocumentation();
if (doc instanceof PropertyDocumentation)
{
PropertyDocumentation pdoc = (PropertyDocumentation) doc;
if (pdoc instanceof FunctionDocumentation
&& ((FunctionDocumentation) pdoc).getIsConstructor())
{
obj = fn.getPropertyValue("prototype", FileContextManager.CURRENT_FILE_INDEX, //$NON-NLS-1$
Integer.MAX_VALUE);
}
else
{
String rettype = fn.getMemberOf();
if (rettype != null && !rettype.equals("")) //$NON-NLS-1$
{
if (rettype.indexOf(".") > -1) //$NON-NLS-1$
{
obj = lookupNamespaceFromNameHash(rettype, environment);
}
else
{
obj = lookupReturnTypeFromNameHash(rettype, environment.getGlobal(),
environment);
}
if (obj != null) // new fix
{
obj = obj.getPropertyValue("prototype", FileContextManager.CURRENT_FILE_INDEX, //$NON-NLS-1$
Integer.MAX_VALUE);
}
}
}
}
else
{
obj = fn.getGuessedMemberObject();
}
}
}
else
{
obj = scope.getVariableValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE)
.getInstance(environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
}
else
{
obj = obj.getPropertyValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE).getInstance(
environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
if (obj == null || obj == ObjectBase.UNDEFINED)
{
return null;
}
// now we have the new object, check if we need to use the return
// type or the declared type
if (isMethodCall || (i == names.length - 1 && fullname.endsWith("."))) //$NON-NLS-1$
{
IDocumentation doc = obj.getDocumentation();
if (doc instanceof PropertyDocumentation)
{
PropertyDocumentation pdoc = (PropertyDocumentation) doc;
String[] rettypes = pdoc.getReturn().getTypes();
if (rettypes.length > 0)
{
// todo: handle multiple return types in the future
// todo: handle [optional] and ... (params) return types
String rettype = rettypes[0];
// method calls always return an instance, so we look on prototype
if (rettype.indexOf(".") > -1) //$NON-NLS-1$
{
obj = lookupNamespaceFromNameHash(rettype, environment);
}
else
{
obj = lookupReturnTypeFromNameHash(rettype, environment.getGlobal(), environment);
}
if (obj != null) // new fix
{
obj = obj.getPropertyValue("prototype", FileContextManager.CURRENT_FILE_INDEX, //$NON-NLS-1$
Integer.MAX_VALUE);
}
}
}
else
{
// no docs, eventually look at return statements here
// for now return 'Object'
if (obj instanceof JSFunction)
{
obj = ((JSFunction) obj).getGuessedMemberObject();
}
else
{
obj = environment.getGlobal().getPropertyValue("Object", FileContextManager.CURRENT_FILE_INDEX, //$NON-NLS-1$
Integer.MAX_VALUE);
}
}
}
}
return obj;
}
private static IObject lookupNamespaceFromNameHash(String fullname, Environment environment)
{
if (fullname.length() == 0)
{
return null;
}
// split the full name, then look up each segment, find return types if
// needed as we parse the name.
String[] names = fullname.split("\\."); //$NON-NLS-1$
IScope scope = environment.getGlobal();
IObject obj = scope;
for (int i = 0; i < names.length; i++)
{
String name = names[i];
if (i == 0)
{
obj = scope.getVariableValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE)
.getInstance(environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
else
{
obj = obj.getPropertyValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE).getInstance(
environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
if (obj == ObjectBase.UNDEFINED)
{
return null;
}
}
return obj;
}
private static IObject lookupOrCreateObject(String fullname, Environment environment)
{
if (fullname.length() == 0)
{
return environment.getGlobal();
}
// split the full name, then look up each segment, find return types if
// needed as we parse the name.
String[] names = fullname.split("\\."); //$NON-NLS-1$
IScope scope = environment.getGlobal();
IObject obj = scope;
IObject prevObj = scope;
String path = ""; //$NON-NLS-1$
for (int i = 0; i < names.length; i++)
{
String name = names[i];
if (i == 0)
{
path = name;
obj = scope.getVariableValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE)
.getInstance(environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
else
{
path += "." + name; //$NON-NLS-1$
obj = obj.getPropertyValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE).getInstance(
environment, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
if (obj == ObjectBase.UNDEFINED)
{
Range r = new Range(0, 0);
IObject guessedObj = new JSObject(r);
Property p = new Property(guessedObj, FileContextManager.BUILT_IN_FILE_INDEX, 0);
prevObj.putLocalProperty(name, p);
obj = guessedObj;
prevObj = guessedObj;
}
}
return obj;
}
/**
* addDocHolderToEnvironment
*
* @param id
* @param doc
* @param parseState
* @return Reference
*/
public static Reference addDocHolderToEnvironment(String id, IDocumentation doc, IParseState parseState)
{
Reference result = null;
Environment environment = (Environment) JSLanguageEnvironment.getInstance().getRuntimeEnvironment();
String[] path = (id.indexOf(".") > -1) ? id.split("\\.") : new String[] { id }; //$NON-NLS-1$ //$NON-NLS-2$
IObject scope = environment.getGlobal();
boolean hasDef = true;
for (int i = 0; i < path.length; i++)
{
String name = path[i];
if (scope.hasLocalProperty(name))
{
scope = scope.getPropertyValue(name, FileContextManager.CURRENT_FILE_INDEX, Integer.MAX_VALUE);
}
else
{
hasDef = false;
break;
}
}
if (!hasDef)
{
IObject objBase = environment.getGlobal();
String name = id;
int dotLoc = id.lastIndexOf("."); //$NON-NLS-1$
if (dotLoc > -1)
{
name = id.substring(dotLoc + 1);
String basename = id.substring(0, dotLoc);
objBase = lookupOrCreateObject(basename, environment);
}
if ("".equals(name)) //$NON-NLS-1$
{
IdeLog.logInfo(JSPlugin.getDefault(), StringUtils.format(Messages.LexemeConsumerHelper_MalformedIdTag, id));
}
// [IM] Guard in case where id ends with a ".". This cause all sorts of problems to
// break loose.
if (objBase != null && !"".equals(name)) //$NON-NLS-1$
{
Range r = new Range(0, 0);
IObject obj = new JSObject(r);
Property p = new Property(obj, parseState.getFileIndex(), 0);
objBase.putLocalProperty(name, p);
// don't add these to undo list as parse
result = new JSReference(objBase, name, false);
// parseState.getUpdatedProperties().put(reference.getProperty(), reference);
if (obj.getDocumentation() == null)
{
obj.setDocumentation(doc);
}
}
}
return result;
}
}