// Evaluator.java // FESI Copyright (c) Jean-Marc Lugrin, 1999 // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package FESI.Interpreter; import FESI.Exceptions.*; import FESI.Parser.*; import FESI.AST.*; import FESI.Extensions.Extension; import FESI.jslib.*; import FESI.Data.*; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.util.EventObject; import java.util.StringTokenizer; import java.io.*; import java.util.zip.*; /** * Defines the evaluation interface and contains the evaluation context. *
Important: This object is also used as the synchronization * object - all entries into the evaluation process must be synchronized * on the evaluator, as the inside of the evaluator is not synchronized * for speed reasons. */ public class Evaluator { // used to stop thread, 06.12.99 Hannes Wallnoefer public volatile Thread thread; private static String eol = System.getProperty("line.separator", "\n"); /** * Return the version identifier of the interpreter */ public static String getVersion() { return "1.1.4 (30-Jan-2000)"; } /** * Return the welcome text (including copyright and version) * of the interpreter (as two lines) */ public static String getWelcomeText() { return "FESI (pronounced like 'fuzzy'): an EcmaScript Interpreter" + eol + "Copyright (c) Jean-Marc Lugrin, 1998 - Version: " + Evaluator.getVersion(); } private boolean debugParse = false; // All privileged objects of interest of the evaluator private GlobalObject globalObject = null; private ESObject objectPrototype = null; private ESObject functionPrototype = null; private ESObject functionObject = null; private ESObject stringPrototype = null; private ESObject numberPrototype = null; private ESObject booleanPrototype = null; private ESObject arrayPrototype = null; private ESObject datePrototype = null; private ESObject packageObject = null; // Current environment private ScopeChain theScopeChain = null; private ESObject currentVariableObject = null; private ESObject currentThisObject = null; // Visitors used for interpretation private EcmaScriptFunctionVisitor functionDeclarationVisitor = null; private EcmaScriptVariableVisitor varDeclarationVisitor = null; //private EcmaScriptEvaluateVisitor evaluationVisitor = null; // List of loaded extensions private Hashtable extensions = null; /** * Reset the evaluator, forgetting all global definitions and loaded extensions */ protected void reset() { functionDeclarationVisitor = new EcmaScriptFunctionVisitor(this); varDeclarationVisitor = new EcmaScriptVariableVisitor(this); // evaluationVisitor = new EcmaScriptEvaluateVisitor(this); globalObject = GlobalObject.makeGlobalObject(this); packageObject = new ESPackages(this); extensions = new Hashtable(); // forget extensions } /** * Create a new empty evaluator */ public Evaluator () { reset(); } /** * Get the variable visitor of this evaluator * @return the Variable visitor */ public EcmaScriptVariableVisitor getVarDeclarationVisitor() { return varDeclarationVisitor; } //------------------------------------------------------------ // Access to special objects of the environment //------------------------------------------------------------ /** * Get the this object of this evaluator * @return the this object */ public ESObject getThisObject() { return currentThisObject; } /** * Get the global object of this evaluator * @return the global object */ public GlobalObject getGlobalObject() { return globalObject; } /** * Set the debug mode for the parser * @param dp true to set debug mode on */ public void setDebugParse(boolean dp) { debugParse = dp; } /** * Return the debug state for the parser * @return true if debug on */ public boolean isDebugParse() { return debugParse; } /** * Set the object prototype object *
Used only by initilization code * @param o the object */ public void setObjectPrototype(ESObject o) { objectPrototype = o; } /** * Get the Object prototype object * @return the ESObject */ public ESObject getObjectPrototype() { return objectPrototype; } /** * Set the Function prototype object *
Used only by initilization code * @param o the object */ public void setFunctionPrototype(ESObject o) { functionPrototype = o; } /** * Get the Function prototype object * @return the ESObject */ public ESObject getFunctionPrototype() { return functionPrototype; } /** * Set the Function object *
Used only by initilization code * @param o the object */ public void setFunctionObject(ESObject o) { functionObject = o; } /** * Get the Function object * @return the ESObject */ public ESObject getFunctionObject() { return functionObject; } /** * Set the String object prototype *
Used only by initilization code * @param o the object */ public void setStringPrototype(ESObject o) { stringPrototype = o; } /** * Get the String prototype object * @return the ESObject */ public ESObject getStringPrototype() { return stringPrototype; } /** * Set the Number object prototpe *
Used only by initilization code * @param o the object */ public void setNumberPrototype(ESObject o) { numberPrototype = o; } /** * Get the Number prototype object * @return the ESObject */ public ESObject getNumberPrototype() { return numberPrototype; } /** * Set the Boolean object prototype *
Used only by initilization code * @param o the object */ public void setBooleanPrototype(ESObject o) { booleanPrototype = o; } /** * Get the Boolean prototype object * @return the ESObject */ public ESObject getBooleanPrototype() { return booleanPrototype; } /** * Set the Array prototype object *
Used only by initilization code * @param o the object */ public void setArrayPrototype(ESObject o) { arrayPrototype = o; } /** * Get the Array prototype object * @return the ESObject */ public ESObject getArrayPrototype() { return arrayPrototype; } /** * Set the Date object prototype *
Used only by initilization code
* @param o the object
*/
public void setDatePrototype(ESObject o) {
datePrototype = o;
}
/**
* Get the Date prototype object
* @return the ESObject
*/
public ESObject getDatePrototype() {
return datePrototype;
}
/**
* Get the Package object
* @return the ESObject
*/
public ESObject getPackageObject() {
return packageObject;
}
//------------------------------------------------------------
// Extension support
//------------------------------------------------------------
/**
* Get a loaded extension by name
* @param name Extension to look up
* @return the extension or null if not loaded
*/
public Extension getExtension(String name) {
return (Extension) extensions.get(name);
}
/**
* Get the list of all extensions
* @return The extensions enumnerator
*/
public Enumeration getExtensions() {
return extensions.keys();
}
/**
* Add an extension by name, load it if not already loaded
* @param name the name of the extension to load
* @return the loaded object or null in case of error
* @exception Error ini initalizing the extension
*/
public Object addExtension(String name) throws EcmaScriptException {
Object extension = getExtension(name);
if (extension == null) {
try {
extension = Class.forName(name).newInstance();
if (extension instanceof Extension) {
((Extension) extension).initializeExtension(this);
} else if (extension instanceof JSExtension) {
GlobalObject go = this.getGlobalObject();
JSGlobalWrapper jgo = new JSGlobalWrapper(go,this);
try {
((JSExtension) extension).initializeExtension(jgo);
} catch (JSException e) {
return null;
}
} else {
return null;
}
extensions.put(name, extension);
} catch (ClassNotFoundException e) { // return null
extension = null;
} catch (NoClassDefFoundError e) { // return null
extension = null;
} catch (IllegalAccessException e) { // return null
extension = null;
} catch (InstantiationException e) { // return null
extension = null;
}
}
return extension;
}
/**
* Add an extension by name, load it if not already loaded.
* Generate an error if not found
* @param name the name of the extension to load
* @return the loaded object
* @exception EcmaScriptException if the extension cannot be loaded or error during initilization
*/
public Object addMandatoryExtension(String name) throws EcmaScriptException {
Object extension = getExtension(name);
if (extension == null) {
try {
extension = Class.forName(name).newInstance();
if (extension instanceof Extension) {
((Extension) extension).initializeExtension(this);
} else if (extension instanceof JSExtension) {
GlobalObject go = this.getGlobalObject();
JSGlobalWrapper jgo = new JSGlobalWrapper(go,this);
try {
((JSExtension) extension).initializeExtension(jgo);
} catch (JSException e) {
throw new EcmaScriptException("Error initializing extension " + name, e);
}
} else {
throw new EcmaScriptException("Extenstion object " + name + " of wrong type " + extension.getClass());
}
extensions.put(name, extension);
} catch (ClassNotFoundException e) {
throw new EcmaScriptException("Error loading extension " + name, e);
} catch (NoClassDefFoundError e) {
throw new EcmaScriptException("Error loading extension " + name, e);
} catch (IllegalAccessException e) {
throw new EcmaScriptException("Error loading extension " + name, e);
} catch (InstantiationException e) {
throw new EcmaScriptException("Error loading extension " + name, e);
}
}
return extension;
}
/**
* Add an initialized extension.
* Generate an error if not found
* @param name the name of the extension to load
* @param extension The extension object
* @return the loaded object
* @exception EcmaScriptException if the extension cannot be loaded or error during initilization
*/
public Object addMandatoryExtension(String name,FESI.jslib.JSExtension extension) throws EcmaScriptException {
GlobalObject go = this.getGlobalObject();
JSGlobalWrapper jgo = new JSGlobalWrapper(go,this);
try {
((JSExtension) extension).initializeExtension(jgo);
} catch (JSException e) {
throw new EcmaScriptException("Error initializing extension " + name, e);
}
extensions.put(name, extension);
return extension;
}
/**
* Get a reference to an indentifier (when its hash code is not known)
* @param identifier The name of the variable
* @return A reference object
*/
public ESReference getReference(String identifier) throws EcmaScriptException {
return theScopeChain.getReference(identifier);
}
/**
* Get a reference to an indentifier (when its hash code is known)
* @param identifier The name of the variable
* @param hash Its hash code (must be exact!)
* @return A reference object
*/
public ESReference getReference(String identifier,int hash) throws EcmaScriptException {
return theScopeChain.getReference(identifier, hash);
}
/**
* Get the value of a variable in the scope chain (when its hash code is not known)
* @param identifier The name of the variable
* @return A value
*/
public ESValue getValue(String identifier) throws EcmaScriptException {
return theScopeChain.getValue(identifier);
}
/**
* Get the value of a variable in the scope chain (when its hash code is known)
* @param identifier The name of the variable
* @param hash Its hash code (must be exact!)
* @return A value
*/
public ESValue getValue(String identifier,int hash) throws EcmaScriptException {
return theScopeChain.getValue(identifier, hash);
}
/**
* Call a routine referenced by name (in the scope chain)
* @param thisObject the this of the called routine
* @param functionName The name of the function
* @param Its hash code
* @param arguments The argument array
* @exception EmcaScriptException In case of any error during evaluation
* @return the resulting value
*/
public ESValue doIndirectCall(ESObject thisObject, String functionName,int hash, ESValue[] arguments) throws EcmaScriptException {
return theScopeChain.doIndirectCall(this, thisObject, functionName, hash, arguments);
}
/**
* Create variable only if does not already exist (do not overwrite parameters
* and functions of the same name)
* @param name the new variable name
* @param hashCode Its hash code
* @exception EmcaScriptException In case of any error during setting
*/
public void createVariable(String name, int hashCode) throws EcmaScriptException {
if (!currentVariableObject.hasProperty(name, hashCode)) {
ESReference newVar = new ESReference(currentVariableObject, name, hashCode);
newVar.putValue(currentVariableObject, ESUndefined.theUndefined);
}
}
/**
* Put a value in a variable (given as a reference)
* @param leftValue The reference to the variable to modify
* @param rightValue The value to set
* @exception EmcaScriptException In case of any error during evaluation
*/
public void putValue(ESReference leftValue, ESValue rightValue) throws EcmaScriptException {
leftValue.putValue(globalObject, rightValue);
}
/**
* Sub evaluator - evaluate an eval string in a program (not a top level evaluation !)
* @param theSource The string to evaluate
* @return The result of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
public ESValue evaluateEvalString(String theSource) throws EcmaScriptException {
ESValue theValue = ESUndefined.theUndefined;
java.io.StringReader is =
new java.io.StringReader(theSource);
EcmaScript parser = new EcmaScript(is);
ASTProgram programNode = null;
StringEvaluationSource es = new StringEvaluationSource(theSource, null);
try {
// ASTProgram n = parser.Program();
programNode = (ASTProgram)parser.Program();
if (debugParse) {
System.out.println();
System.out.println("Dump parse tree of eval (debugParse true)");
programNode.dump("");
}
} catch (ParseException e) {
if (debugParse) {
System.out.println("[[PARSING ERROR DETECTED: (debugParse true)]]");
System.out.println(e.getMessage());
System.out.println("[[BY ROUTINE:]]");
e.printStackTrace();
System.out.println();
}
throw new EcmaScriptParseException(e, es);
} catch (TokenMgrError e) {
if (debugParse) {
System.out.println("[[LEXICAL ERROR DETECTED: (debugParse true)]]");
System.out.println(e.getMessage());
System.out.println("[[BY ROUTINE:]]");
e.printStackTrace();
System.out.println();
}
throw new EcmaScriptLexicalException(e, es);
}
ESObject savedVariableObject = currentVariableObject;
currentVariableObject = globalObject;
try {
functionDeclarationVisitor.processFunctionDeclarations(programNode, es);
varDeclarationVisitor.processVariableDeclarations(programNode, es);
EcmaScriptEvaluateVisitor evaluationVisitor = new EcmaScriptEvaluateVisitor(this);
theValue = evaluationVisitor.evaluateProgram(programNode, es);
if (theValue==null) theValue = ESUndefined.theUndefined; // null is not a valid result
if (evaluationVisitor.getCompletionCode()!= EcmaScriptEvaluateVisitor.C_NORMAL) {
throw new EcmaScriptException("Unexpected " +
evaluationVisitor.getCompletionCodeString() +
" in eval parameter top level" );
}
} finally {
currentVariableObject = savedVariableObject;
}
return theValue;
}
/**
* Sub evaluator - evaluate a loaded file as program (not a top level evaluation !)
* @param file The file to load
* @return The last value of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
public ESValue evaluateLoadFile(File file) throws EcmaScriptException {
ESValue theValue = ESUndefined.theUndefined;
if (!file.isFile()) {
throw new EcmaScriptException("File '" + file.getPath() + "' does not exist or is not a text file");
}
EvaluationSource es = new FileEvaluationSource(file.getPath(), null);
FileReader fr=null;
try {
fr = new FileReader(file);
theValue = evaluate(fr, null, es, false); // no return on main file
if (theValue == null) theValue = ESUndefined.theUndefined;
} catch (IOException e) {
throw new EcmaScriptException("IO Error loading file " + file + ": " + e);
} finally {
if (fr!=null) {
try {
fr.close();
} catch (IOException ignore) {
}
}
}
return theValue;
}
/**
* Sub evaluator - evaluate a module (a file or jar entry loaded via
* the FESI.path) as program (not a top level evaluation !)
* @param moduleName The name of the module to load
* @return The last value of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
public ESValue evaluateLoadModule(String moduleName) throws EcmaScriptException {
ESValue theValue = ESUndefined.theUndefined;
if (moduleName == null) throw new EcmaScriptException("Missing file or module name for load");
String path = System.getProperty("FESI.path", null);
if (path == null) path = System.getProperty("java.class.path",null);
// System.out.println("** Try loading via " + path);
ESValue value = ESUndefined.theUndefined;
if (path == null) {
File file = new File(moduleName);
try {
value = evaluateLoadFile(file);
} catch (EcmaScriptParseException e) {
e.setNeverIncomplete();
throw e;
}
}
String lcModuleName = moduleName.toLowerCase();
boolean hasSuffix = lcModuleName.endsWith(".es") ||
lcModuleName.endsWith(".esw") ||
lcModuleName.endsWith(".js");
String separator = System.getProperty("path.separator",";");
StringTokenizer st = new StringTokenizer(path, separator);
while (st.hasMoreTokens()) {
String tryPath = st.nextToken();
value = tryLoad(tryPath, moduleName, hasSuffix);
if (value != null) break; // Found
}
if (value == null) {
// Not found
throw new EcmaScriptException("Module " + moduleName + " not found in " + path);
}
return value;
}
/**
* Try to load in a single path (directory or jar) environment
* (Utility routine to try load of each path entry)
* @param tryPath The path to try
* @param moduleName the name of the module to load
* @param hasSuffix true if the module name has a specified suffix
* @return The last value of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
private ESValue tryLoad(String tryPath, String moduleName, boolean hasSuffix) throws EcmaScriptException {
// System.out.println("** tryPath: " + tryPath);
File dir = new File(tryPath);
if (dir.isDirectory()) {
File file;
if (hasSuffix) {
file = new File(dir, moduleName);
} else {
file = new File(dir, moduleName+".es");
if (! file.exists()) {
file = new File(dir, moduleName+".esw");
}
if (! file.exists()) {
file = new File(dir, moduleName+".js");
}
}
if (!file.exists()) return null;
// A File is found, load it
String cp;
try {
cp = file.getCanonicalPath();
} catch (IOException e) {
throw new EcmaScriptException("IO error accessing module " + moduleName +
" in directory " + dir, e);
}
// System.out.println("** File found: " + cp);
return evaluateLoadFile(file);
} else if (dir.isFile()) {
// System.out.println("** Looking in jar/zip: " + dir);
ZipFile zipFile;
try {
String cp = dir.getCanonicalPath();
zipFile = new ZipFile(cp);
} catch (IOException e) {
return null; // Cannot open jar/zip, ignore
}
ZipEntry zipEntry;
if (hasSuffix) {
zipEntry = zipFile.getEntry(moduleName);
} else {
zipEntry = zipFile.getEntry(moduleName + ".es");
if (zipEntry==null) {
zipEntry = zipFile.getEntry(moduleName + ".esw");
}
if (zipEntry==null) {
zipEntry = zipFile.getEntry(moduleName + ".js");
}
}
if (zipEntry == null) return null; // Not found in this jar file
byte buf[] = null;
try {
InputStream inputStream = zipFile.getInputStream(zipEntry);
int limit = (int)zipEntry.getSize();
buf = new byte[limit];
int total = 0;
while (total < limit)
{
int ct = inputStream.read(buf,total,limit-total);
total = total + ct;
if (ct == 0) {
throw new IOException ("Only " +
total + " bytes out of " + limit + " read from entry '" +
moduleName + "' in jar '" + zipFile.getName() +"'");
}
}
inputStream.close();
} catch (IOException e) {
if (ESLoader.isDebugLoader()) System.out.println(" ** Error reading jar: " + e);
return null;
}
EvaluationSource es = new JarEvaluationSource(dir.getPath(), moduleName, null);
Reader r = new StringReader(new String(buf));
ESValue theValue = evaluate(r, null, es, false); // no return on main file
if (theValue == null) theValue = ESUndefined.theUndefined;
return theValue;
}
return null;
}
/**
* subevaluator - Evaluate a function node (inside a program evaluation)
* @param node The AST node representing the list of statements
* @param es The evaluation source information for backtracce
* @param localVariableNames The set of local variable to create
* @param thisObject The this of this evaluation (so to speak)
* @return The last value of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
public ESValue evaluateFunction(ASTStatementList node,
EvaluationSource es,
ESObject variableObject,
Vector localVariableNames,
ESObject thisObject) throws EcmaScriptException {
ESValue theValue = ESUndefined.theUndefined;
ESObject savedVariableObject = currentVariableObject;
ESObject savedThisObject = currentThisObject;
ScopeChain previousScopeChain = theScopeChain;
currentVariableObject = variableObject;
currentThisObject = thisObject;
theScopeChain = new ScopeChain(globalObject, null);
theScopeChain = new ScopeChain(variableObject, theScopeChain);
// EvaluationSource savedEvaluationSource = currentEvaluationSource;
// currentEvaluationSource = es;
try {
for (Enumeration e = localVariableNames.elements() ; e.hasMoreElements() ;) {
String variable =(String)(e.nextElement());
createVariable(variable, variable.hashCode());
}
EcmaScriptEvaluateVisitor evaluationVisitor = new EcmaScriptEvaluateVisitor(this);
theValue = evaluationVisitor.evaluateFunction(node, es);
int cc = evaluationVisitor.getCompletionCode();
if ((cc!= EcmaScriptEvaluateVisitor.C_NORMAL) &&
(cc!= EcmaScriptEvaluateVisitor.C_RETURN)) {
throw new EcmaScriptException("Unexpected " +
evaluationVisitor.getCompletionCodeString() +
" in function" );
}
} finally {
currentVariableObject = savedVariableObject;
theScopeChain = previousScopeChain;
currentThisObject = savedThisObject;
// currentEvaluationSource = savedEvaluationSource;
}
return theValue;
}
/**
* Sub evaluator - evaluate a with node (inside a program evaluation)
* @param node The with statement body
* @param scopeObject the new scope for this with
* @param es The evaluation source for back trace
* @return The last value of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
public ESValue evaluateWith(ASTStatement node,
ESObject scopeObject,
EvaluationSource es) throws EcmaScriptException {
ESValue theValue = ESUndefined.theUndefined;
theScopeChain = new ScopeChain(scopeObject, theScopeChain);
try {
EcmaScriptEvaluateVisitor evaluationVisitor = new EcmaScriptEvaluateVisitor(this);
theValue = evaluationVisitor.evaluateWith(node, es);
} finally {
theScopeChain = theScopeChain.previousScope();
}
return theValue;
}
/**
* Top level core evaluator (on parsed program),
* Must be called from a function synchronized on the evaluator
* @param program The parsed program information
* @param thisObject The this of the evaluation (usually the global object)
* @param acceptReturn If true accept a return statement in the body
* @return The last value of the evaluation
* @exception EmcaScriptException In case of any error during evaluation
*/
public ESValue evaluate(ParsedProgram program,
ESObject thisObject,
boolean acceptReturn) throws EcmaScriptException {
ASTProgram node = program.getProgramNode();
ESValue theValue = ESUndefined.theUndefined;
ESObject savedVariableObject = currentVariableObject;
ESObject savedThisObject = currentThisObject;
ScopeChain previousScopeChain = theScopeChain;
theScopeChain = new ScopeChain(globalObject, null);
currentVariableObject = globalObject;
currentThisObject = (thisObject != null) ? thisObject : globalObject;
EcmaScriptEvaluateVisitor evaluationVisitor = new EcmaScriptEvaluateVisitor(this);
try {
functionDeclarationVisitor.processFunctionDeclarations(node, program.getEvaluationSource(), thisObject);
Vector variables = program.getVariableNames();
for (Enumeration e = variables.elements() ; e.hasMoreElements() ;) {
String variable = (String)(e.nextElement());
createVariable(variable,variable.hashCode());
}
theValue = evaluationVisitor.evaluateProgram(node, program.getEvaluationSource());
} finally {
currentVariableObject = savedVariableObject;
theScopeChain = previousScopeChain;
currentThisObject = savedThisObject;
}
int completionCode = evaluationVisitor.getCompletionCode();
if (completionCode != EcmaScriptEvaluateVisitor.C_NORMAL) {
if (completionCode != EcmaScriptEvaluateVisitor.C_RETURN) {
throw new EcmaScriptException("Unexpected " +
evaluationVisitor.getCompletionCodeString() +
" in main program" );
} else if (!acceptReturn) {
throw new EcmaScriptException(
"Return is not accepted in main program with the 'eval' interface" );
}
}
return theValue;
}
/**
* subevaluator - Evaluate an event function - must be synchronized
* @param sourceObject The source of the event (wrapped)
* @param theFunction The function to call
* @param args The arguments of the event
* @exception EmcaScriptException In case of any error during evaluation
*/
synchronized public void evaluateEvent(
ESWrapper sourceObject,
ESObject theFunction,
Object [] args) throws EcmaScriptException {
ESValue [] esArgs = new ESValue[args.length];
for (int i=0; i