using System.Collections.Generic;
using Esprima.Ast;
using Jint.Native;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interpreter.Expressions;
using Jint.Runtime.References;
namespace Jint.Runtime.Interpreter.Statements
{
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.4
///
internal sealed class JintForInStatement : JintStatement
{
private readonly JintStatement _body;
private readonly JintExpression _identifier;
private readonly JintExpression _right;
public JintForInStatement(Engine engine, ForInStatement statement) : base(engine, statement)
{
if (_statement.Left.Type == Nodes.VariableDeclaration)
{
_identifier = JintExpression.Build(engine, (Identifier) ((VariableDeclaration) _statement.Left).Declarations[0].Id);
}
else if (_statement.Left.Type == Nodes.MemberExpression)
{
_identifier = JintExpression.Build(engine, ((MemberExpression) _statement.Left));
}
else
{
_identifier = JintExpression.Build(engine, (Identifier) _statement.Left);
}
_body = Build(engine, _statement.Body);
_right = JintExpression.Build(engine, statement.Right);
}
protected override Completion ExecuteInternal()
{
var varRef = _identifier.Evaluate() as Reference;
var experValue = _right.GetValue();
if (experValue.IsNullOrUndefined())
{
return new Completion(CompletionType.Normal, null, null, Location);
}
var obj = TypeConverter.ToObject(_engine, experValue);
var v = Undefined.Instance;
// keys are constructed using the prototype chain
var cursor = obj;
var processedKeys = new HashSet();
while (!ReferenceEquals(cursor, null))
{
var keys = _engine.Object.GetOwnPropertyNames(Undefined.Instance, Arguments.From(cursor)).AsArray();
var length = keys.GetLength();
for (uint i = 0; i < length; i++)
{
var p = keys.GetOwnProperty(i).Value.AsStringWithoutTypeCheck();
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)
{
continue;
}
_engine.PutValue(varRef, p);
var stmt = _body.Execute();
if (!ReferenceEquals(stmt.Value, null))
{
v = stmt.Value;
}
if (stmt.Type == CompletionType.Break)
{
return new Completion(CompletionType.Normal, v, null, Location);
}
if (stmt.Type != CompletionType.Continue)
{
if (stmt.Type != CompletionType.Normal)
{
return stmt;
}
}
}
cursor = cursor.Prototype;
}
return new Completion(CompletionType.Normal, v, null, Location);
}
}
}