using System;
using System.Collections.Generic;
using System.Linq;
using Jint.Native;
using Jint.Parser.Ast;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;
using Jint.Runtime.References;
namespace Jint.Runtime
{
public class StatementInterpreter
{
private readonly Engine _engine;
public StatementInterpreter(Engine engine)
{
_engine = engine;
}
private Completion ExecuteStatement(Statement statement)
{
return _engine.ExecuteStatement(statement);
}
public Completion ExecuteEmptyStatement(EmptyStatement emptyStatement)
{
return new Completion(Completion.Normal, null, null);
}
public Completion ExecuteExpressionStatement(ExpressionStatement expressionStatement)
{
var exprRef = _engine.EvaluateExpression(expressionStatement.Expression);
return new Completion(Completion.Normal, _engine.GetValue(exprRef), null);
}
public Completion ExecuteIfStatement(IfStatement ifStatement)
{
var exprRef = _engine.EvaluateExpression(ifStatement.Test);
Completion result;
if (TypeConverter.ToBoolean(_engine.GetValue(exprRef)))
{
result = ExecuteStatement(ifStatement.Consequent);
}
else if (ifStatement.Alternate != null)
{
result = ExecuteStatement(ifStatement.Alternate);
}
else
{
return new Completion(Completion.Normal, null, null);
}
return result;
}
public Completion ExecuteLabelledStatement(LabelledStatement labelledStatement)
{
labelledStatement.Body.LabelSet = labelledStatement.Label.Name;
var result = ExecuteStatement(labelledStatement.Body);
if (result.Type == Completion.Break && result.Identifier == labelledStatement.Label.Name)
{
return new Completion(Completion.Normal, result.Value, null);
}
return result;
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.1
///
///
///
public Completion ExecuteDoWhileStatement(DoWhileStatement doWhileStatement)
{
JsValue v = Undefined.Instance;
bool iterating;
do
{
var stmt = ExecuteStatement(doWhileStatement.Body);
if (stmt.Value != null)
{
v = stmt.Value;
}
if (stmt.Type != Completion.Continue || stmt.Identifier != doWhileStatement.LabelSet)
{
if (stmt.Type == Completion.Break && (stmt.Identifier == null || stmt.Identifier == doWhileStatement.LabelSet))
{
return new Completion(Completion.Normal, v, null);
}
if (stmt.Type != Completion.Normal)
{
return stmt;
}
}
var exprRef = _engine.EvaluateExpression(doWhileStatement.Test);
iterating = TypeConverter.ToBoolean(_engine.GetValue(exprRef));
} while (iterating);
return new Completion(Completion.Normal, v, null);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.2
///
///
///
public Completion ExecuteWhileStatement(WhileStatement whileStatement)
{
JsValue v = Undefined.Instance;
while (true)
{
var exprRef = _engine.EvaluateExpression(whileStatement.Test);
if (!TypeConverter.ToBoolean(_engine.GetValue(exprRef)))
{
return new Completion(Completion.Normal, v, null);
}
var stmt = ExecuteStatement(whileStatement.Body);
if (stmt.Value != null)
{
v = stmt.Value;
}
if (stmt.Type != Completion.Continue || stmt.Identifier != whileStatement.LabelSet)
{
if (stmt.Type == Completion.Break && (stmt.Identifier == null || stmt.Identifier == whileStatement.LabelSet))
{
return new Completion(Completion.Normal, v, null);
}
if (stmt.Type != Completion.Normal)
{
return stmt;
}
}
}
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.3
///
///
///
public Completion ExecuteForStatement(ForStatement forStatement)
{
if (forStatement.Init != null)
{
if (forStatement.Init.Type == SyntaxNodes.VariableDeclaration)
{
ExecuteStatement(forStatement.Init.As());
}
else
{
_engine.GetValue(_engine.EvaluateExpression(forStatement.Init.As()));
}
}
JsValue v = Undefined.Instance;
while (true)
{
if (forStatement.Test != null)
{
var testExprRef = _engine.EvaluateExpression(forStatement.Test);
if (!TypeConverter.ToBoolean(_engine.GetValue(testExprRef)))
{
return new Completion(Completion.Normal, v, null);
}
}
var stmt = ExecuteStatement(forStatement.Body);
if (stmt.Value != null)
{
v = stmt.Value;
}
if (stmt.Type == Completion.Break && (stmt.Identifier == null || stmt.Identifier == forStatement.LabelSet))
{
return new Completion(Completion.Normal, v, null);
}
if (stmt.Type != Completion.Continue || ((stmt.Identifier != null) && stmt.Identifier != forStatement.LabelSet))
{
if (stmt.Type != Completion.Normal)
{
return stmt;
}
}
if (forStatement.Update != null)
{
var incExprRef = _engine.EvaluateExpression(forStatement.Update);
_engine.GetValue(incExprRef);
}
}
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.4
///
///
///
public Completion ExecuteForInStatement(ForInStatement forInStatement)
{
Identifier identifier = forInStatement.Left.Type == SyntaxNodes.VariableDeclaration
? forInStatement.Left.As().Declarations.First().Id
: forInStatement.Left.As();
var varRef = _engine.EvaluateExpression(identifier) as Reference;
var exprRef = _engine.EvaluateExpression(forInStatement.Right);
var experValue = _engine.GetValue(exprRef);
if (experValue == Undefined.Instance || experValue == Null.Instance)
{
return new Completion(Completion.Normal, null, null);
}
var obj = TypeConverter.ToObject(_engine, experValue);
JsValue v = Null.Instance;
// keys are constructed using the prototype chain
var cursor = obj;
var processedKeys = new HashSet();
while (cursor != null)
{
var keys = _engine.Object.GetOwnPropertyNames(Undefined.Instance, Arguments.From(cursor)).AsArray();
for (var i = 0; i < keys.GetLength(); i++)
{
var p = keys.GetOwnProperty(i.ToString()).Value.AsString();
if (processedKeys.Contains(p))
{
continue;
}
processedKeys.Add(p);
// collection might be modified by inner statement
if (cursor.GetOwnProperty(p) == PropertyDescriptor.Undefined)
{
continue;
}
var value = cursor.GetOwnProperty(p);
if (!value.Enumerable.HasValue || !value.Enumerable.Value)
{
continue;
}
_engine.PutValue(varRef, p);
var stmt = ExecuteStatement(forInStatement.Body);
if (stmt.Value != null)
{
v = stmt.Value;
}
if (stmt.Type == Completion.Break)
{
return new Completion(Completion.Normal, v, null);
}
if (stmt.Type != Completion.Continue)
{
if (stmt.Type != Completion.Normal)
{
return stmt;
}
}
}
cursor = cursor.Prototype;
}
return new Completion(Completion.Normal, v, null);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.7
///
///
///
public Completion ExecuteContinueStatement(ContinueStatement continueStatement)
{
return new Completion(Completion.Continue, null, continueStatement.Label != null ? continueStatement.Label.Name : null);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.8
///
///
///
public Completion ExecuteBreakStatement(BreakStatement breakStatement)
{
return new Completion(Completion.Break, null, breakStatement.Label != null ? breakStatement.Label.Name : null);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.9
///
///
///
public Completion ExecuteReturnStatement(ReturnStatement statement)
{
if (statement.Argument == null)
{
return new Completion(Completion.Return, Undefined.Instance, null);
}
var exprRef = _engine.EvaluateExpression(statement.Argument);
return new Completion(Completion.Return, _engine.GetValue(exprRef), null);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.10
///
///
///
public Completion ExecuteWithStatement(WithStatement withStatement)
{
var val = _engine.EvaluateExpression(withStatement.Object);
var obj = TypeConverter.ToObject(_engine, _engine.GetValue(val));
var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
var newEnv = LexicalEnvironment.NewObjectEnvironment(_engine, obj, oldEnv, true);
_engine.ExecutionContext.LexicalEnvironment = newEnv;
Completion c;
try
{
c = ExecuteStatement(withStatement.Body);
}
catch (JavaScriptException e)
{
c = new Completion(Completion.Throw, e.Error, null);
c.Location = withStatement.Location;
}
finally
{
_engine.ExecutionContext.LexicalEnvironment = oldEnv;
}
return c;
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.11
///
///
///
public Completion ExecuteSwitchStatement(SwitchStatement switchStatement)
{
var exprRef = _engine.EvaluateExpression(switchStatement.Discriminant);
var r = ExecuteSwitchBlock(switchStatement.Cases, _engine.GetValue(exprRef));
if (r.Type == Completion.Break && r.Identifier == switchStatement.LabelSet)
{
return new Completion(Completion.Normal, r.Value, null);
}
return r;
}
public Completion ExecuteSwitchBlock(IEnumerable switchBlock, JsValue input)
{
JsValue v = Undefined.Instance;
SwitchCase defaultCase = null;
bool hit = false;
foreach (var clause in switchBlock)
{
if (clause.Test == null)
{
defaultCase = clause;
}
else
{
var clauseSelector = _engine.GetValue(_engine.EvaluateExpression(clause.Test));
if (ExpressionInterpreter.StrictlyEqual(clauseSelector, input))
{
hit = true;
}
}
if (hit && clause.Consequent != null)
{
var r = ExecuteStatementList(clause.Consequent);
if (r.Type != Completion.Normal)
{
return r;
}
v = r.Value != null ? r.Value : Undefined.Instance;
}
}
// do we need to execute the default case ?
if (hit == false && defaultCase != null)
{
var r = ExecuteStatementList(defaultCase.Consequent);
if (r.Type != Completion.Normal)
{
return r;
}
v = r.Value != null ? r.Value : Undefined.Instance;
}
return new Completion(Completion.Normal, v, null);
}
public Completion ExecuteStatementList(IEnumerable statementList)
{
var c = new Completion(Completion.Normal, null, null);
Completion sl = c;
Statement s = null;
try
{
foreach (var statement in statementList)
{
s = statement;
c = ExecuteStatement(statement);
if (c.Type != Completion.Normal)
{
return new Completion(c.Type, c.Value != null ? c.Value : sl.Value, c.Identifier)
{
Location = c.Location
};
}
sl = c;
}
}
catch (JavaScriptException v)
{
c = new Completion(Completion.Throw, v.Error, null);
c.Location = v.Location ?? s.Location;
return c;
}
return new Completion(c.Type, c.GetValueOrDefault(), c.Identifier);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.13
///
///
///
public Completion ExecuteThrowStatement(ThrowStatement throwStatement)
{
var exprRef = _engine.EvaluateExpression(throwStatement.Argument);
Completion c = new Completion(Completion.Throw, _engine.GetValue(exprRef), null);
c.Location = throwStatement.Location;
return c;
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.14
///
///
///
public Completion ExecuteTryStatement(TryStatement tryStatement)
{
var b = ExecuteStatement(tryStatement.Block);
if (b.Type == Completion.Throw)
{
// execute catch
if (tryStatement.Handlers.Any())
{
foreach (var catchClause in tryStatement.Handlers)
{
var c = _engine.GetValue(b);
var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
var catchEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);
catchEnv.Record.CreateMutableBinding(catchClause.Param.Name);
catchEnv.Record.SetMutableBinding(catchClause.Param.Name, c, false);
_engine.ExecutionContext.LexicalEnvironment = catchEnv;
b = ExecuteStatement(catchClause.Body);
_engine.ExecutionContext.LexicalEnvironment = oldEnv;
}
}
}
if (tryStatement.Finalizer != null)
{
var f = ExecuteStatement(tryStatement.Finalizer);
if (f.Type == Completion.Normal)
{
return b;
}
return f;
}
return b;
}
public Completion ExecuteProgram(Program program)
{
return ExecuteStatementList(program.Body);
}
public Completion ExecuteVariableDeclaration(VariableDeclaration statement)
{
foreach (var declaration in statement.Declarations)
{
if (declaration.Init != null)
{
var lhs = _engine.EvaluateExpression(declaration.Id) as Reference;
if (lhs == null)
{
throw new ArgumentException();
}
if (lhs.IsStrict() && lhs.GetBase().TryCast() != null &&
(lhs.GetReferencedName() == "eval" || lhs.GetReferencedName() == "arguments"))
{
throw new JavaScriptException(_engine.SyntaxError);
}
lhs.GetReferencedName();
var value = _engine.GetValue(_engine.EvaluateExpression(declaration.Init));
_engine.PutValue(lhs, value);
}
}
return new Completion(Completion.Normal, Undefined.Instance, null);
}
public Completion ExecuteBlockStatement(BlockStatement blockStatement)
{
return ExecuteStatementList(blockStatement.Body);
}
public Completion ExecuteDebuggerStatement(DebuggerStatement debuggerStatement)
{
if (_engine.Options._IsDebuggerStatementAllowed)
{
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
}
System.Diagnostics.Debugger.Break();
}
return new Completion(Completion.Normal, null, null);
}
}
}