From aa9deb818f7d1a0865e1fa0390e3258695a8809c Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 18 Jan 2025 10:40:03 +0200 Subject: [PATCH] Implement Iterator Helpers --- .../Test262Harness.settings.json | 1 - Jint/Native/Global/GlobalObject.Properties.cs | 2 + Jint/Native/Iterator/IteratorConstructor.cs | 141 ++++++++ Jint/Native/Iterator/IteratorPrototype.cs | 332 +++++++++++++++++- Jint/Native/JsValue.cs | 16 + Jint/Native/Object/ObjectInstance.cs | 16 - Jint/Runtime/Intrinsics.cs | 4 + 7 files changed, 493 insertions(+), 19 deletions(-) create mode 100644 Jint/Native/Iterator/IteratorConstructor.cs diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 51d414a2a..4f3786c52 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -11,7 +11,6 @@ "decorators", "explicit-resource-management", "import-defer", - "iterator-helpers", "iterator-sequencing", "json-parse-with-source", "regexp-lookbehind", diff --git a/Jint/Native/Global/GlobalObject.Properties.cs b/Jint/Native/Global/GlobalObject.Properties.cs index 575aef1ba..df2518623 100644 --- a/Jint/Native/Global/GlobalObject.Properties.cs +++ b/Jint/Native/Global/GlobalObject.Properties.cs @@ -30,6 +30,7 @@ public partial class GlobalObject private static readonly Key propertyInt16Array = "Int16Array"; private static readonly Key propertyInt32Array = "Int32Array"; private static readonly Key propertyInt8Array = "Int8Array"; + private static readonly Key propertyIterator = "Iterator"; //private static readonly Key propertyIntl = "Intl"; private static readonly Key propertyJSON = "JSON"; private static readonly Key propertyMap = "Map"; @@ -110,6 +111,7 @@ protected override void Initialize() properties.AddDangerous(propertyInt32Array, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Int32Array, PropertyFlags)); properties.AddDangerous(propertyInt8Array, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Int8Array, PropertyFlags)); // TODO properties.AddDapropertygerous(propertyIntl, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Intl, propertyFlags)); + properties.AddDangerous(propertyIterator, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Iterator, PropertyFlags)); properties.AddDangerous(propertyJSON, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Json, PropertyFlags)); properties.AddDangerous(propertyMap, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Map, PropertyFlags)); properties.AddDangerous(propertyMath, new LazyPropertyDescriptor(this, static global => global._realm.Intrinsics.Math, PropertyFlags)); diff --git a/Jint/Native/Iterator/IteratorConstructor.cs b/Jint/Native/Iterator/IteratorConstructor.cs new file mode 100644 index 000000000..cc3f5effc --- /dev/null +++ b/Jint/Native/Iterator/IteratorConstructor.cs @@ -0,0 +1,141 @@ +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Native.Iterator; + +internal sealed class IteratorConstructor : Constructor +{ + private static readonly JsString _functionName = new("Iterator"); + + internal IteratorConstructor( + Engine engine, + Realm realm, + FunctionPrototype functionPrototype, + ObjectPrototype objectPrototype) + : base(engine, realm, _functionName) + { + _prototype = functionPrototype; + PrototypeObject = new IteratorPrototype(engine, realm, this); + _length = new PropertyDescriptor(0, PropertyFlag.Configurable); + _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); + } + + private IteratorPrototype PrototypeObject { get; } + + protected override void Initialize() + { + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + const PropertyFlag LengthFlags = PropertyFlag.Configurable; + var properties = new PropertyDictionary(1, checkExistingKeys: false) { ["from"] = new(new PropertyDescriptor(new ClrFunction(Engine, "from", From, 1, LengthFlags), PropertyFlags)), }; + SetProperties(properties); + } + + public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) + { + if (newTarget.IsUndefined() || ReferenceEquals(this, newTarget)) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + return OrdinaryCreateFromConstructor( + newTarget, + static intrinsics => intrinsics.Iterator.PrototypeObject, + static (Engine engine, Realm realm, object? _) => new JsObject(engine)); + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.from + /// + private JsValue From(JsValue thisObject, JsValue[] arguments) + { + var iteratorRecord = GetIteratorFlattenable(thisObject, StringHandlingType.IterateStringPrimitives); + var hasInstance = _engine.Intrinsics.Iterator.OrdinaryHasInstance(iteratorRecord); + if (hasInstance) + { + return iteratorRecord; + } + + var wrapper = new WrapForValidIteratorPrototype(_engine, iteratorRecord); + return wrapper; + } + + private IteratorInstance.ObjectIterator GetIteratorFlattenable(JsValue obj, StringHandlingType stringHandling) + { + if (obj is not ObjectInstance) + { + if (stringHandling == StringHandlingType.RejectStrings || obj.IsString()) + { + ExceptionHelper.ThrowTypeError(_realm); + } + } + + JsValue iterator; + var method = JsValue.GetMethod(_realm, obj, GlobalSymbolRegistry.Iterator); + if (method is null) + { + iterator = obj; + } + else + { + iterator = method.Call(obj); + } + + if (iterator is not ObjectInstance objectInstance) + { + ExceptionHelper.ThrowTypeError(_realm); + return null; + } + + return new IteratorInstance.ObjectIterator(objectInstance); + } + + private sealed class WrapForValidIteratorPrototype : ObjectInstance + { + public WrapForValidIteratorPrototype( + Engine engine, + IteratorInstance.ObjectIterator iterated) : base(engine) + { + Iterated = iterated; + SetPrototypeOf(engine.Intrinsics.IteratorPrototype); + } + + public IteratorInstance.ObjectIterator Iterated { get; } + + public ObjectInstance Next() + { + var iteratorRecord = Iterated; + iteratorRecord.TryIteratorStep(out var obj); + return obj; + } + + public JsValue Return() + { + var iterator = Iterated.GetIterator(_engine.Realm); + var returnMethod = iterator.GetMethod(CommonProperties.Return); + if (returnMethod is null) + { + return CreateIteratorResultObject(Undefined, done: JsBoolean.True); + } + + return returnMethod.Call(iterator); + } + + /// + /// https://tc39.es/ecma262/#sec-createiterresultobject + /// + private IteratorResult CreateIteratorResultObject(JsValue value, JsBoolean done) + { + return IteratorResult.CreateValueIteratorPosition(_engine, value, done); + } + } + + private enum StringHandlingType + { + IterateStringPrimitives, + RejectStrings + } +} diff --git a/Jint/Native/Iterator/IteratorPrototype.cs b/Jint/Native/Iterator/IteratorPrototype.cs index 60b9d0b67..bfed68421 100644 --- a/Jint/Native/Iterator/IteratorPrototype.cs +++ b/Jint/Native/Iterator/IteratorPrototype.cs @@ -1,3 +1,4 @@ +using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; @@ -13,21 +14,348 @@ internal class IteratorPrototype : Prototype internal IteratorPrototype( Engine engine, Realm realm, - Prototype objectPrototype) : base(engine, realm) + ObjectInstance objectPrototype) : base(engine, realm) { _prototype = objectPrototype; } protected override void Initialize() { - var symbols = new SymbolDictionary(2) + var properties = new PropertyDictionary(12, checkExistingKeys: false) + { + [KnownKeys.Constructor] = new GetSetPropertyDescriptor( + get: new ClrFunction(_engine, "Iterator.prototype.constructor", (_, _) => _engine.Intrinsics.Iterator), + set: new ClrFunction(_engine, "Iterator.prototype.constructor", (thisObject, arguments) => + { + SetterThatIgnoresPrototypeProperties(thisObject, _engine.Intrinsics.IteratorPrototype, CommonProperties.Constructor, arguments.At(0)); + return Undefined; + }), + PropertyFlag.Configurable), + ["map"] = new(new ClrFunction(_engine, "map", Map, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["filter"] = new(new ClrFunction(_engine, "filter", Filter, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["take"] = new(new ClrFunction(_engine, "take", Take, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["drop"] = new(new ClrFunction(_engine, "drop", Drop, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["flatMap"] = new(new ClrFunction(_engine, "flatMap", FlatMap, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["reduce"] = new(new ClrFunction(_engine, "reduce", Reduce, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["toArray"] = new(new ClrFunction(_engine, "toArray", ToArray, 0, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["forEach"] = new(new ClrFunction(_engine, "forEach", ForEach, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["some"] = new(new ClrFunction(_engine, "some", Some, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["every"] = new(new ClrFunction(_engine, "every", Every, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + ["find"] = new(new ClrFunction(_engine, "find", Find, 1, PropertyFlag.Configurable), PropertyFlag.Writable | PropertyFlag.Configurable), + }; + + SetProperties(properties); + + var symbols = new SymbolDictionary(3) { [GlobalSymbolRegistry.Iterator] = new(new ClrFunction(Engine, "[Symbol.iterator]", ToIterator, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), [GlobalSymbolRegistry.Dispose] = new(new ClrFunction(Engine, "[Symbol.dispose]", Dispose, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), + [GlobalSymbolRegistry.ToStringTag] = new GetSetPropertyDescriptor( + get: new ClrFunction(_engine, "get [Symbol.toStringTag]", (_, _) => "Iterator", 0, PropertyFlag.Configurable), + set: new ClrFunction(_engine, "set [Symbol.toStringTag]", (thisObject, arguments) => + { + SetterThatIgnoresPrototypeProperties(thisObject, _engine.Intrinsics.IteratorPrototype, GlobalSymbolRegistry.ToStringTag, arguments.At(0)); + return Undefined; + }, 0, PropertyFlag.Configurable), + PropertyFlag.Configurable) }; SetSymbols(symbols); } + /// + /// https://tc39.es/ecma262/#sec-SetterThatIgnoresPrototypeProperties + /// + private void SetterThatIgnoresPrototypeProperties(JsValue thisValue, ObjectInstance home, JsValue p, JsValue v) + { + if (thisValue is not ObjectInstance objectInstance) + { + ExceptionHelper.ThrowTypeError(_realm); + return; + } + + if (SameValue(thisValue, home)) + { + ExceptionHelper.ThrowTypeError(_realm); + return; + } + + var desc = objectInstance.GetOwnProperty(p); + if (desc == PropertyDescriptor.Undefined) + { + objectInstance.CreateDataPropertyOrThrow(p, v); + } + else + { + objectInstance.Set(p, v, throwOnError: true); + } + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.map + /// + private JsValue Map(JsValue thisObject, JsValue[] arguments) + { + if (thisObject is not ObjectInstance o) + { + ExceptionHelper.ThrowTypeError(_realm, "object must be an Object"); + return Undefined; + } + + var mapper = GetCallable(arguments.At(0)); + var iterated = GetIteratorDirect(o); + //var iterator = new iterao + + var closure = () => + { + //a. Let counter be 0. + // b. Repeat, + //i. Let value be ? IteratorStepValue(iterated). + // ii. If value is done, return undefined. + // iii. Let mapped be Completion(Call(mapper, undefined, « value, 𝔽(counter) »)). + //iv. IfAbruptCloseIterator(mapped, iterated). + // v. Let completion be Completion(Yield(mapped)). + // vi. IfAbruptCloseIterator(completion, iterated). + // vii. Set counter to counter + 1. + }; + + var result = new SuperFoo(_engine, closure, iterated); + return result; + } + + private static IteratorInstance.ObjectIterator GetIteratorDirect(ObjectInstance objectInstance) => new(objectInstance); + + private sealed class SuperFoo : IteratorInstance + { + public SuperFoo(Engine engine, Action closure, IteratorInstance iterated) : base(engine) + { + } + + public override bool TryIteratorStep(out ObjectInstance nextItem) + { + throw new NotImplementedException(); + } + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.filter + /// + private JsValue Filter(JsValue thisObject, JsValue[] arguments) + { + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.take + /// + private JsValue Take(JsValue thisObject, JsValue[] arguments) + { + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.drop + /// + private JsValue Drop(JsValue thisObject, JsValue[] arguments) + { + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.flatmap + /// + private JsValue FlatMap(JsValue thisObject, JsValue[] arguments) + { + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.reduce + /// + private JsValue Reduce(JsValue thisObject, JsValue[] arguments) + { + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.toarray + /// + private JsValue ToArray(JsValue thisObject, JsValue[] arguments) + { + if (thisObject is not ObjectInstance o) + { + ExceptionHelper.ThrowTypeError(_realm, "object must be an Object"); + return Undefined; + } + + var iterated = GetIteratorDirect(o); + var items = new JsArray(_engine); + while (iterated.TryIteratorStep(out var iteratorResult)) + { + try + { + var value = iteratorResult.Get(CommonProperties.Value); + items.Push(value); + } + catch + { + iterated.Close(CompletionType.Throw); + throw; + } + } + + return items; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.foreach + /// + private JsValue ForEach(JsValue thisObject, JsValue[] arguments) + { + if (thisObject is not ObjectInstance o) + { + ExceptionHelper.ThrowTypeError(_realm, "object must be an Object"); + return Undefined; + } + + var procedure = GetCallable(arguments.At(0)); + var iterated = GetIteratorDirect(o); + + var counter = 0; + while (iterated.TryIteratorStep(out var iteratorResult)) + { + try + { + var value = iteratorResult.Get(CommonProperties.Value); + procedure.Call(Undefined, [value, counter]); + counter++; + } + catch + { + iterated.Close(CompletionType.Throw); + throw; + } + } + + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.some + /// + private JsValue Some(JsValue thisObject, JsValue[] arguments) + { + if (thisObject is not ObjectInstance o) + { + ExceptionHelper.ThrowTypeError(_realm, "object must be an Object"); + return Undefined; + } + + var predicate = GetCallable(arguments.At(0)); + var iterated = GetIteratorDirect(o); + + var counter = 0; + while (iterated.TryIteratorStep(out var iteratorResult)) + { + try + { + var value = iteratorResult.Get(CommonProperties.Value); + var result = predicate.Call(Undefined, [value, counter]); + if (TypeConverter.ToBoolean(result)) + { + iterated.Close(CompletionType.Normal); + return JsBoolean.True; + } + + counter++; + } + catch + { + iterated.Close(CompletionType.Throw); + throw; + } + } + + return JsBoolean.False; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.every + /// + private JsValue Every(JsValue thisObject, JsValue[] arguments) + { + if (thisObject is not ObjectInstance o) + { + ExceptionHelper.ThrowTypeError(_realm, "object must be an Object"); + return Undefined; + } + + var predicate = GetCallable(arguments.At(0)); + var iterated = GetIteratorDirect(o); + + var counter = 0; + while (iterated.TryIteratorStep(out var iteratorResult)) + { + try + { + var value = iteratorResult.Get(CommonProperties.Value); + var result = predicate.Call(Undefined, [value, counter]); + if (!TypeConverter.ToBoolean(result)) + { + iterated.Close(CompletionType.Normal); + return JsBoolean.False; + } + + counter++; + } + catch + { + iterated.Close(CompletionType.Throw); + throw; + } + } + + return JsBoolean.True; + } + + /// + /// https://tc39.es/ecma262/#sec-iterator.prototype.find + /// + private JsValue Find(JsValue thisObject, JsValue[] arguments) + { + if (thisObject is not ObjectInstance o) + { + ExceptionHelper.ThrowTypeError(_realm, "object must be an Object"); + return Undefined; + } + + var predicate = GetCallable(arguments.At(0)); + var iterated = GetIteratorDirect(o); + + var counter = 0; + while (iterated.TryIteratorStep(out var iteratorResult)) + { + try + { + var value = iteratorResult.Get(CommonProperties.Value); + var result = predicate.Call(Undefined, [value, counter]); + if (TypeConverter.ToBoolean(result)) + { + iterated.Close(CompletionType.Normal); + return value; + } + + counter++; + } + catch + { + iterated.Close(CompletionType.Throw); + throw; + } + } + + return Undefined; + } + private static JsValue ToIterator(JsValue thisObject, JsCallArguments arguments) { return thisObject; diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 5ec5a4323..fb1cdd514 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -302,6 +302,22 @@ internal bool InstanceofOperator(JsValue target) return target.OrdinaryHasInstance(this); } + internal static ICallable? GetMethod(Realm realm, JsValue v, JsValue p) + { + var jsValue = v.Get(p); + if (jsValue.IsNullOrUndefined()) + { + return null; + } + + var callable = jsValue as ICallable; + if (callable is null) + { + ExceptionHelper.ThrowTypeError(realm, $"Value returned for property '{p}' of object is not a function"); + } + return callable; + } + public override string ToString() { return "None"; diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 5846ed504..9abbd2dca 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -1382,22 +1382,6 @@ internal static JsObject OrdinaryObjectCreate(Engine engine, ObjectInstance? pro return GetMethod(_engine.Realm, this, property); } - internal static ICallable? GetMethod(Realm realm, JsValue v, JsValue p) - { - var jsValue = v.Get(p); - if (jsValue.IsNullOrUndefined()) - { - return null; - } - - var callable = jsValue as ICallable; - if (callable is null) - { - ExceptionHelper.ThrowTypeError(realm, $"Value returned for property '{p}' of object is not a function"); - } - return callable; - } - internal ICallable? GetDisposeMethod(DisposeHint hint) { if (hint == DisposeHint.Async) diff --git a/Jint/Runtime/Intrinsics.cs b/Jint/Runtime/Intrinsics.cs index e268cb28d..9a4293c87 100644 --- a/Jint/Runtime/Intrinsics.cs +++ b/Jint/Runtime/Intrinsics.cs @@ -68,6 +68,7 @@ public sealed partial class Intrinsics private ReflectInstance? _reflect; private EvalFunction? _eval; private DateConstructor? _date; + private IteratorConstructor? _iteratorConstructor; private IteratorPrototype? _iteratorPrototype; private MathInstance? _math; private JsonInstance? _json; @@ -225,6 +226,9 @@ internal Intrinsics(Engine engine, Realm realm) internal PromiseConstructor Promise => _promise ??= new PromiseConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject); + internal IteratorConstructor Iterator => + _iteratorConstructor ??= new IteratorConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject); + internal IteratorPrototype IteratorPrototype => _iteratorPrototype ??= new IteratorPrototype(_engine, _realm, Object.PrototypeObject);