From 66c1aeb9e007a1b21dfb34342168f34e93b96f2b Mon Sep 17 00:00:00 2001 From: bendtherules Date: Mon, 7 Sep 2020 10:19:00 +0530 Subject: [PATCH] Clone engine262 in /engine262 --- engine262/.eslintignore | 3 + engine262/.eslintrc.js | 81 + engine262/.github/FUNDING.yml | 12 + engine262/.github/workflows/publish.yml | 55 + engine262/.gitignore | 6 + engine262/.gitmodules | 6 + engine262/.npmignore | 9 + engine262/.npmrc | 1 + engine262/CODE_OF_CONDUCT.md | 46 + engine262/LICENSE | 19 + engine262/README.md | 163 + engine262/bin/engine262.js | 217 ++ engine262/bin/snekparse.js | 76 + engine262/bin/test262_realm.js | 129 + engine262/inspector/context.js | 330 ++ engine262/inspector/index.js | 6 + engine262/inspector/js_protocol.json | 3288 +++++++++++++++++ engine262/inspector/methods.js | 105 + engine262/inspector/server.js | 87 + engine262/package.json | 54 + engine262/rollup.config.js | 55 + .../scripts/tag_version_with_git_hash.js | 20 + engine262/scripts/transform.js | 260 ++ engine262/src/abstract-ops/all.mjs | 32 + .../src/abstract-ops/arguments-operations.mjs | 248 ++ engine262/src/abstract-ops/array-objects.mjs | 250 ++ .../src/abstract-ops/arraybuffer-objects.mjs | 213 ++ .../async-function-operations.mjs | 42 + .../abstract-ops/async-generator-objects.mjs | 211 ++ .../abstract-ops/data-types-and-values.mjs | 39 + .../src/abstract-ops/dataview-objects.mjs | 91 + engine262/src/abstract-ops/date-objects.mjs | 233 ++ .../src/abstract-ops/execution-contexts.mjs | 76 + .../src/abstract-ops/function-operations.mjs | 449 +++ .../src/abstract-ops/generator-operations.mjs | 200 + engine262/src/abstract-ops/global-object.mjs | 332 ++ .../immutable-prototype-objects.mjs | 17 + .../abstract-ops/integer-indexed-objects.mjs | 314 ++ .../src/abstract-ops/iterator-operations.mjs | 255 ++ .../module-namespace-exotic-objects.mjs | 218 ++ engine262/src/abstract-ops/module-records.mjs | 263 ++ .../abstract-ops/notational-conventions.mjs | 48 + .../src/abstract-ops/object-operations.mjs | 433 +++ engine262/src/abstract-ops/objects.mjs | 405 ++ .../src/abstract-ops/promise-operations.mjs | 423 +++ engine262/src/abstract-ops/proxy-objects.mjs | 579 +++ engine262/src/abstract-ops/realms.mjs | 366 ++ .../src/abstract-ops/reference-operations.mjs | 142 + engine262/src/abstract-ops/regexp-objects.mjs | 254 ++ engine262/src/abstract-ops/spec-types.mjs | 210 ++ engine262/src/abstract-ops/string-objects.mjs | 145 + engine262/src/abstract-ops/symbol-objects.mjs | 12 + .../src/abstract-ops/testing-comparison.mjs | 412 +++ .../src/abstract-ops/type-conversion.mjs | 469 +++ .../src/abstract-ops/typedarray-objects.mjs | 240 ++ .../src/abstract-ops/weak-operations.mjs | 60 + engine262/src/api.mjs | 269 ++ engine262/src/completion.mjs | 136 + engine262/src/engine.mjs | 404 ++ engine262/src/environment.mjs | 982 +++++ engine262/src/evaluator.mjs | 247 ++ engine262/src/helpers.mjs | 349 ++ engine262/src/inspect.mjs | 205 + engine262/src/intrinsics/AggregateError.mjs | 56 + .../intrinsics/AggregateErrorPrototype.mjs | 11 + engine262/src/intrinsics/Array.mjs | 215 ++ engine262/src/intrinsics/ArrayBuffer.mjs | 44 + .../src/intrinsics/ArrayBufferPrototype.mjs | 115 + .../src/intrinsics/ArrayIteratorPrototype.mjs | 95 + engine262/src/intrinsics/ArrayPrototype.mjs | 591 +++ .../src/intrinsics/ArrayPrototypeShared.mjs | 561 +++ .../AsyncFromSyncIteratorPrototype.mjs | 140 + engine262/src/intrinsics/AsyncFunction.mjs | 28 + .../src/intrinsics/AsyncFunctionPrototype.mjs | 7 + engine262/src/intrinsics/AsyncGenerator.mjs | 18 + .../src/intrinsics/AsyncGeneratorFunction.mjs | 34 + .../intrinsics/AsyncGeneratorPrototype.mjs | 49 + .../src/intrinsics/AsyncIteratorPrototype.mjs | 16 + engine262/src/intrinsics/BigInt.mjs | 53 + engine262/src/intrinsics/BigIntPrototype.mjs | 74 + engine262/src/intrinsics/Boolean.mjs | 32 + engine262/src/intrinsics/BooleanPrototype.mjs | 53 + engine262/src/intrinsics/Bootstrap.mjs | 116 + engine262/src/intrinsics/DataView.mjs | 65 + .../src/intrinsics/DataViewPrototype.mjs | 267 ++ engine262/src/intrinsics/Date.mjs | 204 + engine262/src/intrinsics/DatePrototype.mjs | 705 ++++ engine262/src/intrinsics/Error.mjs | 51 + engine262/src/intrinsics/ErrorPrototype.mjs | 59 + .../src/intrinsics/FinalizationRegistry.mjs | 42 + .../FinalizationRegistryPrototype.mjs | 101 + .../src/intrinsics/ForInIteratorPrototype.mjs | 104 + engine262/src/intrinsics/Function.mjs | 18 + .../src/intrinsics/FunctionPrototype.mjs | 240 ++ engine262/src/intrinsics/Generator.mjs | 21 + .../src/intrinsics/GeneratorFunction.mjs | 30 + .../src/intrinsics/GeneratorPrototype.mjs | 49 + .../src/intrinsics/IteratorPrototype.mjs | 16 + engine262/src/intrinsics/JSON.mjs | 548 +++ engine262/src/intrinsics/Map.mjs | 87 + .../src/intrinsics/MapIteratorPrototype.mjs | 79 + engine262/src/intrinsics/MapPrototype.mjs | 229 ++ engine262/src/intrinsics/Math.mjs | 179 + engine262/src/intrinsics/NativeError.mjs | 73 + engine262/src/intrinsics/Number.mjs | 120 + engine262/src/intrinsics/NumberPrototype.mjs | 117 + engine262/src/intrinsics/Object.mjs | 430 +++ engine262/src/intrinsics/ObjectPrototype.mjs | 142 + engine262/src/intrinsics/Promise.mjs | 681 ++++ engine262/src/intrinsics/PromisePrototype.mjs | 130 + engine262/src/intrinsics/Proxy.mjs | 82 + engine262/src/intrinsics/Reflect.mjs | 209 ++ engine262/src/intrinsics/RegExp.mjs | 87 + engine262/src/intrinsics/RegExpPrototype.mjs | 815 ++++ .../RegExpStringIteratorPrototype.mjs | 81 + engine262/src/intrinsics/Set.mjs | 69 + .../src/intrinsics/SetIteratorPrototype.mjs | 71 + engine262/src/intrinsics/SetPrototype.mjs | 194 + engine262/src/intrinsics/String.mjs | 114 + .../intrinsics/StringIteratorPrototype.mjs | 75 + engine262/src/intrinsics/StringPrototype.mjs | 720 ++++ engine262/src/intrinsics/Symbol.mjs | 98 + engine262/src/intrinsics/SymbolPrototype.mjs | 79 + engine262/src/intrinsics/ThrowTypeError.mjs | 36 + engine262/src/intrinsics/TypedArray.mjs | 145 + .../src/intrinsics/TypedArrayConstructors.mjs | 286 ++ .../src/intrinsics/TypedArrayPrototype.mjs | 841 +++++ .../src/intrinsics/TypedArrayPrototypes.mjs | 16 + engine262/src/intrinsics/URIHandling.mjs | 325 ++ engine262/src/intrinsics/WeakMap.mjs | 39 + engine262/src/intrinsics/WeakMapPrototype.mjs | 127 + engine262/src/intrinsics/WeakRef.mjs | 31 + engine262/src/intrinsics/WeakRefPrototype.mjs | 21 + engine262/src/intrinsics/WeakSet.mjs | 60 + engine262/src/intrinsics/WeakSetPrototype.mjs | 97 + engine262/src/intrinsics/eval.mjs | 30 + engine262/src/intrinsics/isFinite.mjs | 27 + engine262/src/intrinsics/isNaN.mjs | 27 + engine262/src/intrinsics/parseFloat.mjs | 81 + engine262/src/intrinsics/parseInt.mjs | 104 + engine262/src/messages.mjs | 160 + engine262/src/modules.mjs | 519 +++ engine262/src/parse.mjs | 185 + engine262/src/parser/BaseParser.mjs | 3 + engine262/src/parser/ExpressionParser.mjs | 1235 +++++++ engine262/src/parser/FunctionParser.mjs | 322 ++ engine262/src/parser/IdentifierParser.mjs | 138 + engine262/src/parser/LanguageParser.mjs | 96 + engine262/src/parser/Lexer.mjs | 896 +++++ engine262/src/parser/Parser.mjs | 153 + engine262/src/parser/RegExpParser.mjs | 911 +++++ engine262/src/parser/Scope.mjs | 371 ++ engine262/src/parser/StatementParser.mjs | 1209 ++++++ engine262/src/parser/tokens.mjs | 175 + .../runtime-semantics/AdditiveExpression.mjs | 27 + .../ApplyStringOrNumericBinaryOperator.mjs | 56 + .../ArgumentListEvaluation.mjs | 177 + .../src/runtime-semantics/ArrayLiteral.mjs | 96 + .../src/runtime-semantics/ArrowFunction.mjs | 7 + .../AssignmentExpression.mjs | 278 ++ .../runtime-semantics/AsyncArrowFunction.mjs | 7 + .../AsyncFunctionExpression.mjs | 41 + .../AsyncGeneratorExpression.mjs | 56 + .../src/runtime-semantics/AwaitExpression.mjs | 14 + .../BindingInitialization.mjs | 88 + .../runtime-semantics/BitwiseOperators.mjs | 12 + engine262/src/runtime-semantics/Block.mjs | 70 + .../src/runtime-semantics/BreakStatement.mjs | 17 + .../runtime-semantics/BreakableStatement.mjs | 17 + .../src/runtime-semantics/CallExpression.mjs | 59 + .../runtime-semantics/ClassDeclaration.mjs | 43 + .../ClassDefinitionEvaluation.mjs | 161 + .../src/runtime-semantics/ClassExpression.mjs | 29 + .../runtime-semantics/CoalesceExpression.mjs | 23 + .../src/runtime-semantics/CommaOperator.mjs | 16 + .../ConditionalExpression.mjs | 30 + .../runtime-semantics/ContinueStatement.mjs | 17 + .../CreateDynamicFunction.mjs | 193 + .../runtime-semantics/DebuggerStatement.mjs | 19 + .../src/runtime-semantics/DefineMethod.mjs | 44 + .../DestructuringAssignmentEvaluation.mjs | 346 ++ .../src/runtime-semantics/EmptyStatement.mjs | 8 + .../runtime-semantics/EqualityExpression.mjs | 60 + .../src/runtime-semantics/EvaluateBody.mjs | 148 + .../src/runtime-semantics/EvaluateCall.mjs | 62 + .../EvaluatePropertyAccess.mjs | 46 + ...valuateStringOrNumericBinaryExpression.mjs | 18 + .../ExponentiationExpression.mjs | 9 + .../runtime-semantics/ExportDeclaration.mjs | 88 + .../runtime-semantics/ExpressionStatement.mjs | 13 + .../runtime-semantics/FunctionDeclaration.mjs | 10 + .../FunctionDeclarationInstantiation.mjs | 267 ++ .../runtime-semantics/FunctionExpression.mjs | 42 + .../FunctionStatementList.mjs | 10 + .../runtime-semantics/GeneratorExpression.mjs | 56 + .../src/runtime-semantics/GetSubstitution.mjs | 117 + .../GlobalDeclarationInstantiation.mjs | 145 + .../HoistableDeclaration.mjs | 11 + .../runtime-semantics/IdentifierReference.mjs | 13 + .../src/runtime-semantics/IfStatement.mjs | 48 + .../src/runtime-semantics/ImportCall.mjs | 30 + .../runtime-semantics/ImportDeclaration.mjs | 8 + .../src/runtime-semantics/ImportMeta.mjs | 44 + .../InstantiateFunctionObject.mjs | 121 + .../IteratorBindingInitialization.mjs | 290 ++ .../KeyedBindingInitialization.mjs | 57 + .../runtime-semantics/LabelledEvaluation.mjs | 741 ++++ .../runtime-semantics/LabelledStatement.mjs | 10 + .../runtime-semantics/LexicalDeclaration.mjs | 96 + engine262/src/runtime-semantics/Literal.mjs | 34 + .../LogicalANDExpression.mjs | 24 + .../runtime-semantics/LogicalORExpression.mjs | 24 + engine262/src/runtime-semantics/MV.mjs | 10 + .../runtime-semantics/MemberExpression.mjs | 52 + engine262/src/runtime-semantics/Module.mjs | 14 + .../src/runtime-semantics/ModuleBody.mjs | 7 + .../MultiplicativeExpression.mjs | 16 + .../src/runtime-semantics/NamedEvaluation.mjs | 178 + .../src/runtime-semantics/NewExpression.mjs | 50 + engine262/src/runtime-semantics/NewTarget.mjs | 8 + .../src/runtime-semantics/NumberToBigInt.mjs | 15 + .../src/runtime-semantics/ObjectLiteral.mjs | 24 + .../runtime-semantics/OptionalExpression.mjs | 106 + .../ParenthesizedExpression.mjs | 7 + .../PropertyBindingInitialization.mjs | 40 + .../PropertyDefinitionEvaluation.mjs | 307 ++ .../src/runtime-semantics/PropertyName.mjs | 38 + engine262/src/runtime-semantics/RegExp.mjs | 1164 ++++++ .../RegularExpressionLiteral.mjs | 15 + .../RelationalExpression.mjs | 120 + .../RestBindingInitialization.mjs | 27 + .../src/runtime-semantics/ReturnStatement.mjs | 29 + engine262/src/runtime-semantics/Script.mjs | 14 + .../src/runtime-semantics/ScriptBody.mjs | 6 + .../src/runtime-semantics/ShiftExpression.mjs | 15 + .../src/runtime-semantics/StatementList.mjs | 30 + .../src/runtime-semantics/StringIndexOf.mjs | 43 + engine262/src/runtime-semantics/StringPad.mjs | 31 + engine262/src/runtime-semantics/SuperCall.mjs | 52 + .../src/runtime-semantics/SuperProperty.mjs | 60 + .../src/runtime-semantics/SwitchStatement.mjs | 218 ++ .../TaggedTemplateExpression.mjs | 22 + .../src/runtime-semantics/TemplateLiteral.mjs | 33 + engine262/src/runtime-semantics/This.mjs | 8 + .../src/runtime-semantics/ThrowStatement.mjs | 21 + .../src/runtime-semantics/TrimString.mjs | 19 + .../src/runtime-semantics/TryStatement.mjs | 119 + .../src/runtime-semantics/UnaryExpression.mjs | 195 + engine262/src/runtime-semantics/Unicode.mjs | 542 +++ .../runtime-semantics/UpdateExpression.mjs | 77 + .../runtime-semantics/VariableStatement.mjs | 69 + .../src/runtime-semantics/WithStatement.mjs | 34 + .../src/runtime-semantics/YieldExpression.mjs | 178 + engine262/src/runtime-semantics/all.mjs | 99 + engine262/src/static-semantics/BodyText.mjs | 5 + engine262/src/static-semantics/BoundNames.mjs | 99 + .../src/static-semantics/CharacterValue.mjs | 91 + .../src/static-semantics/CodePointAt.mjs | 53 + .../CodePointToUTF16CodeUnits.mjs | 17 + .../static-semantics/CodePointsToString.mjs | 15 + .../static-semantics/ConstructorMethod.mjs | 9 + .../static-semantics/ContainsExpression.mjs | 56 + .../src/static-semantics/DeclarationPart.mjs | 3 + .../ExpectedArgumentCount.mjs | 25 + .../src/static-semantics/ExportEntries.mjs | 119 + .../ExportEntriesForModule.mjs | 131 + engine262/src/static-semantics/FlagText.mjs | 5 + .../src/static-semantics/HasInitializer.mjs | 3 + engine262/src/static-semantics/HasName.mjs | 6 + .../src/static-semantics/ImportEntries.mjs | 28 + .../ImportEntriesForModule.mjs | 107 + .../static-semantics/ImportedLocalNames.mjs | 12 + .../IsAnonymousFunctionDefinition.mjs | 17 + .../IsConstantDeclaration.mjs | 3 + .../src/static-semantics/IsDestructuring.mjs | 18 + .../static-semantics/IsFunctionDefinition.mjs | 12 + .../src/static-semantics/IsIdentifierRef.mjs | 3 + .../src/static-semantics/IsInTailPosition.mjs | 3 + .../IsSimpleParameterList.mjs | 22 + engine262/src/static-semantics/IsStatic.mjs | 8 + engine262/src/static-semantics/IsStrict.mjs | 5 + .../static-semantics/IsStringValidUnicode.mjs | 22 + .../LexicallyDeclaredNames.mjs | 22 + .../LexicallyScopedDeclarations.mjs | 78 + .../src/static-semantics/ModuleRequests.mjs | 32 + .../NonConstructorMethodDefinitions.mjs | 14 + .../src/static-semantics/NumericValue.mjs | 6 + engine262/src/static-semantics/PropName.mjs | 20 + .../static-semantics/StringToCodePoints.mjs | 23 + .../src/static-semantics/StringValue.mjs | 17 + .../src/static-semantics/TemplateStrings.mjs | 104 + .../TopLevelLexicallyDeclaredNames.mjs | 18 + .../TopLevelLexicallyScopedDeclarations.mjs | 16 + .../TopLevelVarDeclaredNames.mjs | 23 + .../TopLevelVarScopedDeclarations.mjs | 23 + .../UTF16SurrogatePairToCodePoint.mjs | 12 + .../src/static-semantics/VarDeclaredNames.mjs | 104 + .../VarScopedDeclarations.mjs | 111 + engine262/src/static-semantics/all.mjs | 44 + engine262/src/value.mjs | 855 +++++ engine262/test/base.js | 110 + .../test/eslint-plugin-engine262/index.js | 9 + .../eslint-plugin-engine262/no-use-in-def.js | 68 + .../eslint-plugin-engine262/valid-feature.js | 32 + .../eslint-plugin-engine262/valid-throw.js | 63 + engine262/test/json/json.js | 64 + engine262/test/stepped.js | 66 + engine262/test/supplemental.js | 267 ++ engine262/test/test262/features | 35 + engine262/test/test262/skiplist | 59 + engine262/test/test262/slowlist | 26 + engine262/test/test262/test262.js | 324 ++ engine262/test/test_root.sh | 11 + 313 files changed, 46014 insertions(+) create mode 100644 engine262/.eslintignore create mode 100644 engine262/.eslintrc.js create mode 100644 engine262/.github/FUNDING.yml create mode 100644 engine262/.github/workflows/publish.yml create mode 100644 engine262/.gitignore create mode 100644 engine262/.gitmodules create mode 100644 engine262/.npmignore create mode 100644 engine262/.npmrc create mode 100644 engine262/CODE_OF_CONDUCT.md create mode 100644 engine262/LICENSE create mode 100644 engine262/README.md create mode 100755 engine262/bin/engine262.js create mode 100644 engine262/bin/snekparse.js create mode 100644 engine262/bin/test262_realm.js create mode 100644 engine262/inspector/context.js create mode 100644 engine262/inspector/index.js create mode 100644 engine262/inspector/js_protocol.json create mode 100644 engine262/inspector/methods.js create mode 100644 engine262/inspector/server.js create mode 100644 engine262/package.json create mode 100644 engine262/rollup.config.js create mode 100644 engine262/scripts/tag_version_with_git_hash.js create mode 100644 engine262/scripts/transform.js create mode 100644 engine262/src/abstract-ops/all.mjs create mode 100644 engine262/src/abstract-ops/arguments-operations.mjs create mode 100644 engine262/src/abstract-ops/array-objects.mjs create mode 100644 engine262/src/abstract-ops/arraybuffer-objects.mjs create mode 100644 engine262/src/abstract-ops/async-function-operations.mjs create mode 100644 engine262/src/abstract-ops/async-generator-objects.mjs create mode 100644 engine262/src/abstract-ops/data-types-and-values.mjs create mode 100644 engine262/src/abstract-ops/dataview-objects.mjs create mode 100644 engine262/src/abstract-ops/date-objects.mjs create mode 100644 engine262/src/abstract-ops/execution-contexts.mjs create mode 100644 engine262/src/abstract-ops/function-operations.mjs create mode 100644 engine262/src/abstract-ops/generator-operations.mjs create mode 100644 engine262/src/abstract-ops/global-object.mjs create mode 100644 engine262/src/abstract-ops/immutable-prototype-objects.mjs create mode 100644 engine262/src/abstract-ops/integer-indexed-objects.mjs create mode 100644 engine262/src/abstract-ops/iterator-operations.mjs create mode 100644 engine262/src/abstract-ops/module-namespace-exotic-objects.mjs create mode 100644 engine262/src/abstract-ops/module-records.mjs create mode 100644 engine262/src/abstract-ops/notational-conventions.mjs create mode 100644 engine262/src/abstract-ops/object-operations.mjs create mode 100644 engine262/src/abstract-ops/objects.mjs create mode 100644 engine262/src/abstract-ops/promise-operations.mjs create mode 100644 engine262/src/abstract-ops/proxy-objects.mjs create mode 100644 engine262/src/abstract-ops/realms.mjs create mode 100644 engine262/src/abstract-ops/reference-operations.mjs create mode 100644 engine262/src/abstract-ops/regexp-objects.mjs create mode 100644 engine262/src/abstract-ops/spec-types.mjs create mode 100644 engine262/src/abstract-ops/string-objects.mjs create mode 100644 engine262/src/abstract-ops/symbol-objects.mjs create mode 100644 engine262/src/abstract-ops/testing-comparison.mjs create mode 100644 engine262/src/abstract-ops/type-conversion.mjs create mode 100644 engine262/src/abstract-ops/typedarray-objects.mjs create mode 100644 engine262/src/abstract-ops/weak-operations.mjs create mode 100644 engine262/src/api.mjs create mode 100644 engine262/src/completion.mjs create mode 100644 engine262/src/engine.mjs create mode 100644 engine262/src/environment.mjs create mode 100644 engine262/src/evaluator.mjs create mode 100644 engine262/src/helpers.mjs create mode 100644 engine262/src/inspect.mjs create mode 100644 engine262/src/intrinsics/AggregateError.mjs create mode 100644 engine262/src/intrinsics/AggregateErrorPrototype.mjs create mode 100644 engine262/src/intrinsics/Array.mjs create mode 100644 engine262/src/intrinsics/ArrayBuffer.mjs create mode 100644 engine262/src/intrinsics/ArrayBufferPrototype.mjs create mode 100644 engine262/src/intrinsics/ArrayIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/ArrayPrototype.mjs create mode 100644 engine262/src/intrinsics/ArrayPrototypeShared.mjs create mode 100644 engine262/src/intrinsics/AsyncFromSyncIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/AsyncFunction.mjs create mode 100644 engine262/src/intrinsics/AsyncFunctionPrototype.mjs create mode 100644 engine262/src/intrinsics/AsyncGenerator.mjs create mode 100644 engine262/src/intrinsics/AsyncGeneratorFunction.mjs create mode 100644 engine262/src/intrinsics/AsyncGeneratorPrototype.mjs create mode 100644 engine262/src/intrinsics/AsyncIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/BigInt.mjs create mode 100644 engine262/src/intrinsics/BigIntPrototype.mjs create mode 100644 engine262/src/intrinsics/Boolean.mjs create mode 100644 engine262/src/intrinsics/BooleanPrototype.mjs create mode 100644 engine262/src/intrinsics/Bootstrap.mjs create mode 100644 engine262/src/intrinsics/DataView.mjs create mode 100644 engine262/src/intrinsics/DataViewPrototype.mjs create mode 100644 engine262/src/intrinsics/Date.mjs create mode 100644 engine262/src/intrinsics/DatePrototype.mjs create mode 100644 engine262/src/intrinsics/Error.mjs create mode 100644 engine262/src/intrinsics/ErrorPrototype.mjs create mode 100644 engine262/src/intrinsics/FinalizationRegistry.mjs create mode 100644 engine262/src/intrinsics/FinalizationRegistryPrototype.mjs create mode 100644 engine262/src/intrinsics/ForInIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/Function.mjs create mode 100644 engine262/src/intrinsics/FunctionPrototype.mjs create mode 100644 engine262/src/intrinsics/Generator.mjs create mode 100644 engine262/src/intrinsics/GeneratorFunction.mjs create mode 100644 engine262/src/intrinsics/GeneratorPrototype.mjs create mode 100644 engine262/src/intrinsics/IteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/JSON.mjs create mode 100644 engine262/src/intrinsics/Map.mjs create mode 100644 engine262/src/intrinsics/MapIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/MapPrototype.mjs create mode 100644 engine262/src/intrinsics/Math.mjs create mode 100644 engine262/src/intrinsics/NativeError.mjs create mode 100644 engine262/src/intrinsics/Number.mjs create mode 100644 engine262/src/intrinsics/NumberPrototype.mjs create mode 100644 engine262/src/intrinsics/Object.mjs create mode 100644 engine262/src/intrinsics/ObjectPrototype.mjs create mode 100644 engine262/src/intrinsics/Promise.mjs create mode 100644 engine262/src/intrinsics/PromisePrototype.mjs create mode 100644 engine262/src/intrinsics/Proxy.mjs create mode 100644 engine262/src/intrinsics/Reflect.mjs create mode 100644 engine262/src/intrinsics/RegExp.mjs create mode 100644 engine262/src/intrinsics/RegExpPrototype.mjs create mode 100644 engine262/src/intrinsics/RegExpStringIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/Set.mjs create mode 100644 engine262/src/intrinsics/SetIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/SetPrototype.mjs create mode 100644 engine262/src/intrinsics/String.mjs create mode 100644 engine262/src/intrinsics/StringIteratorPrototype.mjs create mode 100644 engine262/src/intrinsics/StringPrototype.mjs create mode 100644 engine262/src/intrinsics/Symbol.mjs create mode 100644 engine262/src/intrinsics/SymbolPrototype.mjs create mode 100644 engine262/src/intrinsics/ThrowTypeError.mjs create mode 100644 engine262/src/intrinsics/TypedArray.mjs create mode 100644 engine262/src/intrinsics/TypedArrayConstructors.mjs create mode 100644 engine262/src/intrinsics/TypedArrayPrototype.mjs create mode 100644 engine262/src/intrinsics/TypedArrayPrototypes.mjs create mode 100644 engine262/src/intrinsics/URIHandling.mjs create mode 100644 engine262/src/intrinsics/WeakMap.mjs create mode 100644 engine262/src/intrinsics/WeakMapPrototype.mjs create mode 100644 engine262/src/intrinsics/WeakRef.mjs create mode 100644 engine262/src/intrinsics/WeakRefPrototype.mjs create mode 100644 engine262/src/intrinsics/WeakSet.mjs create mode 100644 engine262/src/intrinsics/WeakSetPrototype.mjs create mode 100644 engine262/src/intrinsics/eval.mjs create mode 100644 engine262/src/intrinsics/isFinite.mjs create mode 100644 engine262/src/intrinsics/isNaN.mjs create mode 100644 engine262/src/intrinsics/parseFloat.mjs create mode 100644 engine262/src/intrinsics/parseInt.mjs create mode 100644 engine262/src/messages.mjs create mode 100644 engine262/src/modules.mjs create mode 100644 engine262/src/parse.mjs create mode 100644 engine262/src/parser/BaseParser.mjs create mode 100644 engine262/src/parser/ExpressionParser.mjs create mode 100644 engine262/src/parser/FunctionParser.mjs create mode 100644 engine262/src/parser/IdentifierParser.mjs create mode 100644 engine262/src/parser/LanguageParser.mjs create mode 100644 engine262/src/parser/Lexer.mjs create mode 100644 engine262/src/parser/Parser.mjs create mode 100644 engine262/src/parser/RegExpParser.mjs create mode 100644 engine262/src/parser/Scope.mjs create mode 100644 engine262/src/parser/StatementParser.mjs create mode 100644 engine262/src/parser/tokens.mjs create mode 100644 engine262/src/runtime-semantics/AdditiveExpression.mjs create mode 100644 engine262/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mjs create mode 100644 engine262/src/runtime-semantics/ArgumentListEvaluation.mjs create mode 100644 engine262/src/runtime-semantics/ArrayLiteral.mjs create mode 100644 engine262/src/runtime-semantics/ArrowFunction.mjs create mode 100644 engine262/src/runtime-semantics/AssignmentExpression.mjs create mode 100644 engine262/src/runtime-semantics/AsyncArrowFunction.mjs create mode 100644 engine262/src/runtime-semantics/AsyncFunctionExpression.mjs create mode 100644 engine262/src/runtime-semantics/AsyncGeneratorExpression.mjs create mode 100644 engine262/src/runtime-semantics/AwaitExpression.mjs create mode 100644 engine262/src/runtime-semantics/BindingInitialization.mjs create mode 100644 engine262/src/runtime-semantics/BitwiseOperators.mjs create mode 100644 engine262/src/runtime-semantics/Block.mjs create mode 100644 engine262/src/runtime-semantics/BreakStatement.mjs create mode 100644 engine262/src/runtime-semantics/BreakableStatement.mjs create mode 100644 engine262/src/runtime-semantics/CallExpression.mjs create mode 100644 engine262/src/runtime-semantics/ClassDeclaration.mjs create mode 100644 engine262/src/runtime-semantics/ClassDefinitionEvaluation.mjs create mode 100644 engine262/src/runtime-semantics/ClassExpression.mjs create mode 100644 engine262/src/runtime-semantics/CoalesceExpression.mjs create mode 100644 engine262/src/runtime-semantics/CommaOperator.mjs create mode 100644 engine262/src/runtime-semantics/ConditionalExpression.mjs create mode 100644 engine262/src/runtime-semantics/ContinueStatement.mjs create mode 100644 engine262/src/runtime-semantics/CreateDynamicFunction.mjs create mode 100644 engine262/src/runtime-semantics/DebuggerStatement.mjs create mode 100644 engine262/src/runtime-semantics/DefineMethod.mjs create mode 100644 engine262/src/runtime-semantics/DestructuringAssignmentEvaluation.mjs create mode 100644 engine262/src/runtime-semantics/EmptyStatement.mjs create mode 100644 engine262/src/runtime-semantics/EqualityExpression.mjs create mode 100644 engine262/src/runtime-semantics/EvaluateBody.mjs create mode 100644 engine262/src/runtime-semantics/EvaluateCall.mjs create mode 100644 engine262/src/runtime-semantics/EvaluatePropertyAccess.mjs create mode 100644 engine262/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mjs create mode 100644 engine262/src/runtime-semantics/ExponentiationExpression.mjs create mode 100644 engine262/src/runtime-semantics/ExportDeclaration.mjs create mode 100644 engine262/src/runtime-semantics/ExpressionStatement.mjs create mode 100644 engine262/src/runtime-semantics/FunctionDeclaration.mjs create mode 100644 engine262/src/runtime-semantics/FunctionDeclarationInstantiation.mjs create mode 100644 engine262/src/runtime-semantics/FunctionExpression.mjs create mode 100644 engine262/src/runtime-semantics/FunctionStatementList.mjs create mode 100644 engine262/src/runtime-semantics/GeneratorExpression.mjs create mode 100644 engine262/src/runtime-semantics/GetSubstitution.mjs create mode 100644 engine262/src/runtime-semantics/GlobalDeclarationInstantiation.mjs create mode 100644 engine262/src/runtime-semantics/HoistableDeclaration.mjs create mode 100644 engine262/src/runtime-semantics/IdentifierReference.mjs create mode 100644 engine262/src/runtime-semantics/IfStatement.mjs create mode 100644 engine262/src/runtime-semantics/ImportCall.mjs create mode 100644 engine262/src/runtime-semantics/ImportDeclaration.mjs create mode 100644 engine262/src/runtime-semantics/ImportMeta.mjs create mode 100644 engine262/src/runtime-semantics/InstantiateFunctionObject.mjs create mode 100644 engine262/src/runtime-semantics/IteratorBindingInitialization.mjs create mode 100644 engine262/src/runtime-semantics/KeyedBindingInitialization.mjs create mode 100644 engine262/src/runtime-semantics/LabelledEvaluation.mjs create mode 100644 engine262/src/runtime-semantics/LabelledStatement.mjs create mode 100644 engine262/src/runtime-semantics/LexicalDeclaration.mjs create mode 100644 engine262/src/runtime-semantics/Literal.mjs create mode 100644 engine262/src/runtime-semantics/LogicalANDExpression.mjs create mode 100644 engine262/src/runtime-semantics/LogicalORExpression.mjs create mode 100644 engine262/src/runtime-semantics/MV.mjs create mode 100644 engine262/src/runtime-semantics/MemberExpression.mjs create mode 100644 engine262/src/runtime-semantics/Module.mjs create mode 100644 engine262/src/runtime-semantics/ModuleBody.mjs create mode 100644 engine262/src/runtime-semantics/MultiplicativeExpression.mjs create mode 100644 engine262/src/runtime-semantics/NamedEvaluation.mjs create mode 100644 engine262/src/runtime-semantics/NewExpression.mjs create mode 100644 engine262/src/runtime-semantics/NewTarget.mjs create mode 100644 engine262/src/runtime-semantics/NumberToBigInt.mjs create mode 100644 engine262/src/runtime-semantics/ObjectLiteral.mjs create mode 100644 engine262/src/runtime-semantics/OptionalExpression.mjs create mode 100644 engine262/src/runtime-semantics/ParenthesizedExpression.mjs create mode 100644 engine262/src/runtime-semantics/PropertyBindingInitialization.mjs create mode 100644 engine262/src/runtime-semantics/PropertyDefinitionEvaluation.mjs create mode 100644 engine262/src/runtime-semantics/PropertyName.mjs create mode 100644 engine262/src/runtime-semantics/RegExp.mjs create mode 100644 engine262/src/runtime-semantics/RegularExpressionLiteral.mjs create mode 100644 engine262/src/runtime-semantics/RelationalExpression.mjs create mode 100644 engine262/src/runtime-semantics/RestBindingInitialization.mjs create mode 100644 engine262/src/runtime-semantics/ReturnStatement.mjs create mode 100644 engine262/src/runtime-semantics/Script.mjs create mode 100644 engine262/src/runtime-semantics/ScriptBody.mjs create mode 100644 engine262/src/runtime-semantics/ShiftExpression.mjs create mode 100644 engine262/src/runtime-semantics/StatementList.mjs create mode 100644 engine262/src/runtime-semantics/StringIndexOf.mjs create mode 100644 engine262/src/runtime-semantics/StringPad.mjs create mode 100644 engine262/src/runtime-semantics/SuperCall.mjs create mode 100644 engine262/src/runtime-semantics/SuperProperty.mjs create mode 100644 engine262/src/runtime-semantics/SwitchStatement.mjs create mode 100644 engine262/src/runtime-semantics/TaggedTemplateExpression.mjs create mode 100644 engine262/src/runtime-semantics/TemplateLiteral.mjs create mode 100644 engine262/src/runtime-semantics/This.mjs create mode 100644 engine262/src/runtime-semantics/ThrowStatement.mjs create mode 100644 engine262/src/runtime-semantics/TrimString.mjs create mode 100644 engine262/src/runtime-semantics/TryStatement.mjs create mode 100644 engine262/src/runtime-semantics/UnaryExpression.mjs create mode 100644 engine262/src/runtime-semantics/Unicode.mjs create mode 100644 engine262/src/runtime-semantics/UpdateExpression.mjs create mode 100644 engine262/src/runtime-semantics/VariableStatement.mjs create mode 100644 engine262/src/runtime-semantics/WithStatement.mjs create mode 100644 engine262/src/runtime-semantics/YieldExpression.mjs create mode 100644 engine262/src/runtime-semantics/all.mjs create mode 100644 engine262/src/static-semantics/BodyText.mjs create mode 100644 engine262/src/static-semantics/BoundNames.mjs create mode 100644 engine262/src/static-semantics/CharacterValue.mjs create mode 100644 engine262/src/static-semantics/CodePointAt.mjs create mode 100644 engine262/src/static-semantics/CodePointToUTF16CodeUnits.mjs create mode 100644 engine262/src/static-semantics/CodePointsToString.mjs create mode 100644 engine262/src/static-semantics/ConstructorMethod.mjs create mode 100644 engine262/src/static-semantics/ContainsExpression.mjs create mode 100644 engine262/src/static-semantics/DeclarationPart.mjs create mode 100644 engine262/src/static-semantics/ExpectedArgumentCount.mjs create mode 100644 engine262/src/static-semantics/ExportEntries.mjs create mode 100644 engine262/src/static-semantics/ExportEntriesForModule.mjs create mode 100644 engine262/src/static-semantics/FlagText.mjs create mode 100644 engine262/src/static-semantics/HasInitializer.mjs create mode 100644 engine262/src/static-semantics/HasName.mjs create mode 100644 engine262/src/static-semantics/ImportEntries.mjs create mode 100644 engine262/src/static-semantics/ImportEntriesForModule.mjs create mode 100644 engine262/src/static-semantics/ImportedLocalNames.mjs create mode 100644 engine262/src/static-semantics/IsAnonymousFunctionDefinition.mjs create mode 100644 engine262/src/static-semantics/IsConstantDeclaration.mjs create mode 100644 engine262/src/static-semantics/IsDestructuring.mjs create mode 100644 engine262/src/static-semantics/IsFunctionDefinition.mjs create mode 100644 engine262/src/static-semantics/IsIdentifierRef.mjs create mode 100644 engine262/src/static-semantics/IsInTailPosition.mjs create mode 100644 engine262/src/static-semantics/IsSimpleParameterList.mjs create mode 100644 engine262/src/static-semantics/IsStatic.mjs create mode 100644 engine262/src/static-semantics/IsStrict.mjs create mode 100644 engine262/src/static-semantics/IsStringValidUnicode.mjs create mode 100644 engine262/src/static-semantics/LexicallyDeclaredNames.mjs create mode 100644 engine262/src/static-semantics/LexicallyScopedDeclarations.mjs create mode 100644 engine262/src/static-semantics/ModuleRequests.mjs create mode 100644 engine262/src/static-semantics/NonConstructorMethodDefinitions.mjs create mode 100644 engine262/src/static-semantics/NumericValue.mjs create mode 100644 engine262/src/static-semantics/PropName.mjs create mode 100644 engine262/src/static-semantics/StringToCodePoints.mjs create mode 100644 engine262/src/static-semantics/StringValue.mjs create mode 100644 engine262/src/static-semantics/TemplateStrings.mjs create mode 100644 engine262/src/static-semantics/TopLevelLexicallyDeclaredNames.mjs create mode 100644 engine262/src/static-semantics/TopLevelLexicallyScopedDeclarations.mjs create mode 100644 engine262/src/static-semantics/TopLevelVarDeclaredNames.mjs create mode 100644 engine262/src/static-semantics/TopLevelVarScopedDeclarations.mjs create mode 100644 engine262/src/static-semantics/UTF16SurrogatePairToCodePoint.mjs create mode 100644 engine262/src/static-semantics/VarDeclaredNames.mjs create mode 100644 engine262/src/static-semantics/VarScopedDeclarations.mjs create mode 100644 engine262/src/static-semantics/all.mjs create mode 100644 engine262/src/value.mjs create mode 100644 engine262/test/base.js create mode 100644 engine262/test/eslint-plugin-engine262/index.js create mode 100644 engine262/test/eslint-plugin-engine262/no-use-in-def.js create mode 100644 engine262/test/eslint-plugin-engine262/valid-feature.js create mode 100644 engine262/test/eslint-plugin-engine262/valid-throw.js create mode 100644 engine262/test/json/json.js create mode 100644 engine262/test/stepped.js create mode 100644 engine262/test/supplemental.js create mode 100644 engine262/test/test262/features create mode 100644 engine262/test/test262/skiplist create mode 100644 engine262/test/test262/slowlist create mode 100644 engine262/test/test262/test262.js create mode 100755 engine262/test/test_root.sh diff --git a/engine262/.eslintignore b/engine262/.eslintignore new file mode 100644 index 0000000..fccf642 --- /dev/null +++ b/engine262/.eslintignore @@ -0,0 +1,3 @@ +!.eslintrc.js +test/test262/test262 +test/json/JSONTestSuite diff --git a/engine262/.eslintrc.js b/engine262/.eslintrc.js new file mode 100644 index 0000000..7ebaba6 --- /dev/null +++ b/engine262/.eslintrc.js @@ -0,0 +1,81 @@ +'use strict'; + +const Module = require('module'); + +const ModuleFindPath = Module._findPath; +const hacks = [ + 'eslint-plugin-engine262', +]; +Module._findPath = (request, paths, isMain) => { + const r = ModuleFindPath(request, paths, isMain); + if (!r && hacks.includes(request)) { + return require.resolve(`./test/${request}`); + } + return r; +}; + +module.exports = { + root: true, + extends: 'airbnb-base', + plugins: ['engine262'], + parser: '@babel/eslint-parser', + parserOptions: { + ecmaVersion: 2020, + requireConfigFile: false, + }, + overrides: [ + { + files: ['*.js'], + parserOptions: { sourceType: 'script' }, + }, + ], + globals: { + globalThis: false, + Atomics: false, + BigInt: false, + BigUint64Array: false, + SharedArrayBuffer: false, + }, + rules: { + 'arrow-parens': ['error', 'always'], + 'brace-style': ['error', '1tbs', { allowSingleLine: false }], + 'curly': ['error', 'all'], + 'engine262/no-use-in-def': 'error', + 'engine262/valid-feature': 'error', + 'engine262/valid-throw': 'error', + 'import/order': ['error', { 'newlines-between': 'never' }], + 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], + 'no-multiple-empty-lines': ['error', { maxBOF: 0, max: 2 }], + 'no-unused-vars': ['error', { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }], + 'no-empty': ['error', { allowEmptyCatch: true }], + 'quote-props': ['error', 'consistent'], + 'strict': ['error', 'global'], + + 'camelcase': 'off', + 'class-methods-use-this': 'off', + 'global-require': 'off', + 'import/extensions': 'off', + 'import/no-cycle': 'off', + 'import/no-mutable-exports': 'off', + 'import/prefer-default-export': 'off', + 'lines-between-class-members': 'off', + 'max-classes-per-file': 'off', + 'max-len': 'off', + 'no-bitwise': 'off', + 'no-constant-condition': 'off', + 'no-continue': 'off', + 'no-else-return': 'off', + 'no-lonely-if': 'off', + 'no-loop-func': 'off', + 'no-param-reassign': 'off', + 'no-restricted-syntax': 'off', + 'no-underscore-dangle': 'off', + 'no-use-before-define': 'off', + 'prefer-destructuring': 'off', + }, +}; diff --git a/engine262/.github/FUNDING.yml b/engine262/.github/FUNDING.yml new file mode 100644 index 0000000..fb79233 --- /dev/null +++ b/engine262/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [engine262, devsnek] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with a single custom sponsorship URL diff --git a/engine262/.github/workflows/publish.yml b/engine262/.github/workflows/publish.yml new file mode 100644 index 0000000..e842b2e --- /dev/null +++ b/engine262/.github/workflows/publish.yml @@ -0,0 +1,55 @@ +name: Publish Package + +on: + push: + branches: + - main + +jobs: + publish-gpr: + runs-on: ubuntu-latest + steps: + # Set everything up + - uses: actions/checkout@v2 + with: + persist-credentials: false + - uses: actions/setup-node@master + with: + node-version: 12 + registry-url: https://npm.pkg.github.com/ + scope: '@engine262' + always-auth: true # required if using yarn + - run: git submodule update --init --recursive + + # Run tests and whatnot + - run: npm install + - run: npm run build + - run: npm run lint + - run: npm run coverage + env: + CONTINUOUS_INTEGRATION: 1 + + # Upload coverage data + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{github.token}} + + # Publish built package + - run: npm publish --access=public + env: + NODE_AUTH_TOKEN: ${{github.token}} + + # Push build to to gh-pages + - run: | + git config --global user.email "gha@example.com" + git config --global user.name "GHA" + git remote add github "https://$GITHUB_ACTOR:$BETTER_GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git" + git fetch github + git checkout gh-pages + cp dist/* . + git add engine262.* + git commit -m "autobuild" || exit 0 # exit silently if nothing changed + git push -u github gh-pages + env: + BETTER_GITHUB_TOKEN: ${{secrets.BETTER_GITHUB_TOKEN}} diff --git a/engine262/.gitignore b/engine262/.gitignore new file mode 100644 index 0000000..24bcd18 --- /dev/null +++ b/engine262/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +test.mjs +.eslintcache +coverage +.nyc_output diff --git a/engine262/.gitmodules b/engine262/.gitmodules new file mode 100644 index 0000000..11609fb --- /dev/null +++ b/engine262/.gitmodules @@ -0,0 +1,6 @@ +[submodule "test/JSONTestSuite"] + path = test/json/JSONTestSuite + url = https://github.com/nst/JSONTestSuite +[submodule "test/test262/test262"] + path = test/test262/test262 + url = https://github.com/tc39/test262 diff --git a/engine262/.npmignore b/engine262/.npmignore new file mode 100644 index 0000000..2173bf3 --- /dev/null +++ b/engine262/.npmignore @@ -0,0 +1,9 @@ +test +src +scripts +coverage +rollup.config.js +.eslintcache +.eslintrc.js +.eslintignore +.travis.yml diff --git a/engine262/.npmrc b/engine262/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/engine262/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/engine262/CODE_OF_CONDUCT.md b/engine262/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e22b282 --- /dev/null +++ b/engine262/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at devsnek@users.noreply.github.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/engine262/LICENSE b/engine262/LICENSE new file mode 100644 index 0000000..6cbeb88 --- /dev/null +++ b/engine262/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 engine262 Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/engine262/README.md b/engine262/README.md new file mode 100644 index 0000000..b8ec15d --- /dev/null +++ b/engine262/README.md @@ -0,0 +1,163 @@ +# engine262 + +An implementation of ECMA-262 in JavaScript + +Goals +- 100% Spec Compliance +- Introspection +- Ease of modification + +Non-Goals +- Speed at the expense of any of the goals + +This project is bound by a [Code of Conduct][COC]. + +Join us on [#engine262 on freenode][irc] ([web][irc-webchat]). + +## Why this exists + +While helping develop new features for JavaScript, I've found that one of the +most useful methods of finding what works and what doesn't is being able to +actually run code using the new feature. [Babel][] is fantastic for this, but +sometimes features just can't be nicely represented with it. Similarly, +implementing a feature in one of the engines is a large undertaking, involving +long compile times and annoying bugs with the optimizing compilers. + +engine262 is a tool to allow JavaScript developers to have a sandbox where new +features can be quickly prototyped and explored. As an example, adding +[do expressions][] to this engine is as simple as the following diff: + +```diff +--- a/src/evaluator.mjs ++++ b/src/evaluator.mjs +@@ -232,6 +232,8 @@ export function* Evaluate(node) { + case 'GeneratorBody': + case 'AsyncGeneratorBody': + return yield* Evaluate_AnyFunctionBody(node); ++ case 'DoExpression': ++ return yield* Evaluate_Block(node.Block); + default: + throw new OutOfRange('Evaluate', node); + } +--- a/src/parser/ExpressionParser.mjs ++++ b/src/parser/ExpressionParser.mjs +@@ -579,6 +579,12 @@ export class ExpressionParser extends FunctionParser { + return this.parseRegularExpressionLiteral(); + case Token.LPAREN: + return this.parseParenthesizedExpression(); ++ case Token.DO: { ++ const node = this.startNode(); ++ this.next(); ++ node.Block = this.parseBlock(); ++ return this.finishNode(node, 'DoExpression'); ++ } + default: + return this.unexpected(); + } +``` + +This simplicity applies to many other proposals, such as [optional chaining][], +[pattern matching][], [the pipeline operator][], and more. This engine has also +been used to find bugs in ECMA-262 and test262, the test suite for +conforming JavaScript implementations. + +## Requirements + +To run engine262 itself, a engine with support for recent ECMAScript features +is needed. Additionally, the CLI (`bin/engine262.js`) and test262 runner +(`test/test262.js`) require a recent version of Node.js. + +## Using engine262 + +Use it online: https://engine262.js.org + +You can install the latest engine262 build from [GitHub Packages][]. + +If you install it globally, you can use the CLI like so: + +`$ engine262` + +Or, you can install it locally and use the API: + +```js +'use strict'; + +const { + Agent, + setSurroundingAgent, + ManagedRealm, + Value, + + CreateDataProperty, + + inspect, +} = require('engine262'); + +const agent = new Agent({ + // onDebugger() {}, + // ensureCanCompileStrings() {}, + // hasSourceTextAvailable() {}, + // onNodeEvaluation() {}, + // features: [], +}); +setSurroundingAgent(agent); + +const realm = new ManagedRealm({ + // promiseRejectionTracker() {}, + // resolveImportedModule() {}, + // getImportMetaProperties() {}, + // finalizeImportMeta() {}, + // randomSeed() {}, +}); + +realm.scope(() => { + // Add print function from host + const print = new Value((args) => { + console.log(...args.map((tmp) => inspect(tmp))); + return Value.undefined; + }); + CreateDataProperty(realm.GlobalObject, new Value('print'), print); +}); + +realm.evaluateScript(` +'use strict'; + +async function* numbers() { + let i = 0; + while (true) { + const n = await Promise.resolve(i++); + yield n; + } +} + +(async () => { + for await (const item of numbers()) { + print(item); + } +})(); +`); + +// a stream of numbers fills your console. it fills you with determination. +``` + +## Related Projects + +Many people and organizations have attempted to write a JavaScript interpreter +in JavaScript much like engine262, with different goals. Some of them are +included here for reference, though engine262 is not based on any of them. + +- https://github.com/facebook/prepack +- https://github.com/mozilla/narcissus +- https://github.com/NeilFraser/JS-Interpreter +- https://github.com/metaes/metaes +- https://github.com/Siubaak/sval + +[Babel]: https://babeljs.io/ +[COC]: https://github.com/engine262/engine262/blob/master/CODE_OF_CONDUCT.md +[do expressions]: https://github.com/tc39/proposal-do-expressions +[irc]: ircs://chat.freenode.net:6697/engine262 +[irc-webchat]: https://webchat.freenode.net/?channels=engine262 +[optional chaining]: https://github.com/tc39/proposal-optional-chaining +[pattern matching]: https://github.com/tc39/proposal-pattern-matching +[the pipeline operator]: https://github.com/tc39/proposal-pipeline-operator +[GitHub Packages]: https://github.com/engine262/engine262/packages diff --git a/engine262/bin/engine262.js b/engine262/bin/engine262.js new file mode 100755 index 0000000..b20600a --- /dev/null +++ b/engine262/bin/engine262.js @@ -0,0 +1,217 @@ +#!/usr/bin/env node + +'use strict'; + +/* eslint-disable import/order */ + +try { + require('@snek/source-map-support/register'); +} catch { + // empty +} + +const repl = require('repl'); +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const packageJson = require('../package.json'); +const snekparse = require('./snekparse'); +const { + Agent, + setSurroundingAgent, + + FEATURES, + inspect, + + Value, + + CreateDataProperty, + OrdinaryObjectCreate, + Type, + + Completion, + AbruptCompletion, + Throw, +} = require('..'); +const { createRealm } = require('./test262_realm'); + +const execArgv = []; +let entry; +const programArgv = []; + +{ + let target = execArgv; + process.argv.slice(2).forEach((a) => { + if (a.startsWith('--')) { + target.push(a); + } else if (!entry) { + entry = a; + target = programArgv; + } else { + target.push(a); + } + }); +} + +const help = ` +engine262 v${require('../package.json').version} + +Usage: + + engine262 [options] + engine262 [options] [input file] + engine262 [input file] + +Options: + + -h, --help Show help (this screen) + -m, --module Evaluate contents of input-file as a module. + Must be followed by input-file + --features=... A comma separated list of features. If no features + are provided, the available features are listed. + +`; + +const argv = snekparse(execArgv); + +if (argv.h || argv.help) { + process.stdout.write(help); + process.exit(0); +} else if (argv.features === true) { + let nameLength = 0; + let flagLength = 0; + FEATURES.forEach((f) => { + if (f.name.length > nameLength) { + nameLength = f.name.length; + } + if (f.flag.length > flagLength) { + flagLength = f.flag.length; + } + }); + const log = (n, f, u) => { + process.stdout.write(`${n.padEnd(nameLength, ' ')} ${f.padEnd(flagLength, ' ')} ${u}\n`); + }; + log('name', 'flag', 'url'); + log('----', '----', '---'); + FEATURES.forEach((f) => { + log(f.name, f.flag, f.url); + }); + process.exit(0); +} + +let features; +if (argv.features === 'all') { + features = FEATURES.map((f) => f.flag); +} else if (argv.features) { + features = argv.features.split(','); +} else { + features = []; +} + +const agent = new Agent({ features }); +setSurroundingAgent(agent); + +const { realm, resolverCache } = createRealm({ printCompatMode: true }); +realm.scope(() => { + const console = OrdinaryObjectCreate(realm.Intrinsics['%Object.prototype%']); + CreateDataProperty(realm.GlobalObject, new Value('console'), console); + + const format = (args) => args.map((a, i) => { + if (i === 0 && Type(a) === 'String') { + return a.stringValue(); + } + return inspect(a); + }).join(' '); + + const log = new Value((args) => { + process.stdout.write(`${format(args)}\n`); + return Value.undefined; + }); + + CreateDataProperty(console, new Value('log'), log); + + const error = new Value((args) => { + process.stderr.write(`${format(args)}\n`); + return Value.undefined; + }); + + CreateDataProperty(console, new Value('error'), error); + + const debug = new Value((args) => { + process.stderr.write(`${util.format(...args)}\n`); + return Value.undefined; + }); + + CreateDataProperty(console, new Value('debug'), debug); +}); + +if (argv.inspector) { + const inspector = require('../inspector'); + inspector.attachRealm(realm); +} + +function oneShotEval(source, filename) { + realm.scope(() => { + let result; + if (argv.m || argv.module || filename.endsWith('.mjs')) { + result = realm.createSourceTextModule(filename, source); + if (!(result instanceof AbruptCompletion)) { + const module = result; + resolverCache.set(filename, result); + result = module.Link(); + if (!(result instanceof AbruptCompletion)) { + result = module.Evaluate(); + } + if (!(result instanceof AbruptCompletion)) { + if (result.PromiseState === 'rejected') { + result = Throw(result.PromiseResult); + } + } + } + } else { + result = realm.evaluateScript(source, { specifier: filename }); + } + if (result instanceof AbruptCompletion) { + const inspected = inspect(result); + process.stderr.write(`${inspected}\n`); + process.exit(1); + } + }); +} + +if (entry) { + const source = fs.readFileSync(entry, 'utf8'); + oneShotEval(source, path.resolve(entry)); +} else if (!process.stdin.isTTY) { + process.stdin.setEncoding('utf8'); + let source = ''; + process.stdin.on('data', (data) => { + source += data; + }); + process.stdin.once('end', () => { + oneShotEval(source, process.cwd()); + }); +} else { + process.stdout.write(`${packageJson.name} v${packageJson.version} +Please report bugs to ${packageJson.bugs.url} +`); + repl.start({ + prompt: '> ', + eval: (cmd, context, filename, callback) => { + try { + const result = realm.evaluateScript(cmd, { specifier: '(engine262)' }); + callback(null, result); + } catch (e) { + callback(e, null); + } + }, + preview: false, + completer: () => [], + writer: (o) => realm.scope(() => { + if (o instanceof Value || o instanceof Completion) { + return inspect(o); + } + return util.inspect(o); + }), + }); +} diff --git a/engine262/bin/snekparse.js b/engine262/bin/snekparse.js new file mode 100644 index 0000000..a5d22c7 --- /dev/null +++ b/engine262/bin/snekparse.js @@ -0,0 +1,76 @@ +'use strict'; + +function snekparse(args) { + if (typeof args === 'string') { + args = args.split(' '); + } + const argv = []; + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (/^--.+=/.test(arg)) { + const match = arg.match(/^--([^=]+)=([\s\S]*)$/); + argv[match[1]] = match[2]; + } else if (/^--no-.+/.test(arg)) { + argv[arg.match(/^--no-(.+)/)[1]] = false; + } else if (/^--.+/.test(arg)) { + const key = arg.match(/^--(.+)/)[1]; + const next = args[i + 1]; + if (typeof next !== 'undefined' && !/^-/.test(next)) { + argv[key] = next; + } else if (/^(true|false)$/.test(next)) { + argv[key] = next === 'true'; + } else { + argv[key] = true; + } + } else if (/^-[^-]+/.test(arg)) { + const letters = arg.slice(1, -1).split(''); + let broken = false; + for (const j of letters) { + const next = arg.slice(j + 2); + + if (next === '-') { + argv[letters[j]] = next; + continue; + } + + if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) { + argv[letters[j]] = next.split('=')[1]; + broken = true; + break; + } + + if (/[A-Za-z]/.test(letters[j]) + && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { + argv[letters[j]] = next; + broken = true; + break; + } + + if (letters[j + 1] && letters[j + 1].match(/\W/)) { + argv[letters[j]] = arg.slice(j + 2); + broken = true; + break; + } else { + argv[letters[j]] = true; + } + } + + const key = arg.slice(-1)[0]; + if (!broken && key !== '-') { + if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1])) { + argv[key] = args[i + 1]; + } else if (args[i + 1] && /true|false/.test(args[i + 1])) { + argv[key] = args[i + 1] === 'true'; + } else { + argv[key] = true; + } + } + } else { + argv.push(arg); + } + } + + return argv; +} + +module.exports = snekparse; diff --git a/engine262/bin/test262_realm.js b/engine262/bin/test262_realm.js new file mode 100644 index 0000000..bd9d856 --- /dev/null +++ b/engine262/bin/test262_realm.js @@ -0,0 +1,129 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const { + Value, + CreateDataProperty, + DetachArrayBuffer, + OrdinaryObjectCreate, + ToString, + Type, + Throw, + AbruptCompletion, + ManagedRealm, + inspect, + gc, +} = require('..'); + +const createRealm = ({ printCompatMode = false } = {}) => { + const resolverCache = new Map(); + const trackedPromises = new Set(); + + const realm = new ManagedRealm({ + promiseRejectionTracker(promise, operation) { + switch (operation) { + case 'reject': + trackedPromises.add(promise); + break; + case 'handle': + trackedPromises.delete(promise); + break; + /* istanbul ignore next */ + default: + throw new RangeError('promiseRejectionTracker', operation); + } + }, + resolveImportedModule(referencingScriptOrModule, specifier) { + try { + const base = path.dirname(referencingScriptOrModule.HostDefined.specifier); + const resolved = path.resolve(base, specifier); + if (resolverCache.has(resolved)) { + return resolverCache.get(resolved); + } + const source = fs.readFileSync(resolved, 'utf8'); + const m = realm.createSourceTextModule(resolved, source); + resolverCache.set(resolved, m); + return m; + } catch (e) { + return Throw(e.name, 'Raw', e.message); + } + }, + }); + + return realm.scope(() => { + const $262 = OrdinaryObjectCreate(realm.Intrinsics['%Object.prototype%']); + + let printHandle; + const setPrintHandle = (f) => { + printHandle = f; + }; + CreateDataProperty(realm.GlobalObject, new Value('print'), new Value((args) => { + /* istanbul ignore next */ + if (printHandle !== undefined) { + printHandle(...args); + } else { + if (printCompatMode) { + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + const s = ToString(arg); + if (s instanceof AbruptCompletion) { + return s; + } + process.stdout.write(s.stringValue()); + if (i !== args.length - 1) { + process.stdout.write(' '); + } + } + process.stdout.write('\n'); + return Value.undefined; + } else { + const formatted = args.map((a, i) => { + if (i === 0 && Type(a) === 'String') { + return a.stringValue(); + } + return inspect(a, realm); + }).join(' '); + console.log(formatted); // eslint-disable-line no-console + } + } + return Value.undefined; + })); + + [ + ['global', realm.GlobalObject], + ['createRealm', () => { + const info = createRealm(); + return info.$262; + }], + ['evalScript', ([sourceText]) => realm.evaluateScript(sourceText.stringValue())], + ['detachArrayBuffer', ([arrayBuffer]) => DetachArrayBuffer(arrayBuffer)], + ['gc', () => { + gc(); + return Value.undefined; + }], + ['spec', ([v]) => { + if (v.nativeFunction && v.nativeFunction.section) { + return new Value(v.nativeFunction.section); + } + return Value.undefined; + }], + ].forEach(([name, value]) => { + const v = value instanceof Value ? value : new Value(value); + CreateDataProperty($262, new Value(name), v); + }); + + CreateDataProperty(realm.GlobalObject, new Value('$262'), $262); + CreateDataProperty(realm.GlobalObject, new Value('$'), $262); + + return { + realm, + $262, + resolverCache, + trackedPromises, + setPrintHandle, + }; + }); +}; + +module.exports = { createRealm }; diff --git a/engine262/inspector/context.js b/engine262/inspector/context.js new file mode 100644 index 0000000..24c173b --- /dev/null +++ b/engine262/inspector/context.js @@ -0,0 +1,330 @@ +'use strict'; + +const engine262 = require('..'); + +const contexts = []; + +class InspectorContext { + constructor(realm) { + this.realm = realm; + this.idToObject = new Map(); + this.objectToId = new Map(); + this.objectCounter = 0; + this.previewStack = []; + } + + internObject(object, group = 'default') { + if (this.objectToId.has(object)) { + return this.objectToId.get(object); + } + const id = `${group}:${this.objectCounter}`; + this.objectCounter += 1; + this.idToObject.set(id, object); + this.objectToId.set(object, id); + return id; + } + + releaseObjectGroup(group) { + for (const [id, object] of this.idToObject.entries()) { + if (id.startsWith(group)) { + this.idToObject.delete(id); + this.objectToId.delete(object); + } + } + } + + getObject(objectId) { + return this.idToObject.get(objectId); + } + + toRemoteObject(object, options) { + const result = {}; + switch (engine262.Type(object)) { + case 'Object': + result.objectId = this.internObject(object, options.objectGroup); + if ('Call' in object) { + result.type = 'function'; + } else { + result.type = 'object'; + if ('PromiseState' in object) { + result.subtype = 'promise'; + } else if ('MapData' in object) { + result.subtype = 'map'; + } else if ('SetData' in object) { + result.subtype = 'set'; + } else if ('ErrorData' in object) { + result.subtype = 'error'; + } else if ('TypedArrayName' in object) { + result.subtype = 'typedarray'; + } else if ('DataView' in object) { + result.subtype = 'dataview'; + } else if ('ProxyTarget' in object) { + result.subtype = 'proxy'; + } else if ('DateValue' in object) { + result.subtype = 'date'; + } else if ('GeneratorState' in object) { + result.subtype = 'generator'; + } else if (engine262.IsArray(object) === engine262.Value.true) { + result.subtype = 'array'; + } + } + break; + case 'Null': + result.type = 'object'; + result.subtype = 'null'; + result.value = null; + break; + case 'Undefined': + result.type = 'undefined'; + break; + case 'String': + result.type = 'string'; + result.value = object.stringValue(); + break; + case 'Number': { + result.type = 'number'; + const v = object.numberValue(); + if (!Number.isFinite(v)) { + result.unserializableValue = v.toString(); + } else { + result.value = v; + } + break; + } + case 'Boolean': + result.type = 'boolean'; + result.value = object.booleanValue(); + break; + case 'BigInt': + result.type = 'bigint'; + result.unserializableValue = `${object.bigintValue().toString()}n`; + break; + case 'Symbol': + result.type = 'symbol'; + result.description = object.Description === engine262.Value.undefined + ? undefined + : object.Description.stringValue(); + break; + default: + throw new RangeError(); + } + if (options.generatePreview + && result.type === 'object' + && result.subtype !== 'null' + && !this.previewStack.includes(object)) { + this.previewStack.push(object); + const properties = this.getPropertyPreview(object, options); + let entries; + if ('MapData' in object) { + entries = object.MapData.map((d) => ({ + key: this.toRemoteObject(d.Key, options).preview, + value: this.toRemoteObject(d.Value, options).preview, + })); + } + this.previewStack.pop(); + result.preview = { + type: result.type, + subtype: result.subtype, + overflow: properties.length > 5, + properties: properties.slice(0, 5), + entries, + }; + } + return result; + } + + getProperties(object, options) { + const wrap = (v) => this.toRemoteObject(v, options); + + const properties = []; + const internalProperties = []; + + let p = object; + while (p !== engine262.Value.null) { + const keys = p.OwnPropertyKeys(); + if (keys instanceof engine262.AbruptCompletion) { + return keys; + } + + for (const key of keys) { + const desc = p.GetOwnProperty(key); + if (desc instanceof engine262.AbruptCompletion) { + return desc; + } + if (options.accessorPropertiesOnly && desc.Value) { + continue; + } + const descriptor = { + name: key.stringValue + ? key.stringValue() + : undefined, + value: desc.Value ? wrap(desc.Value) : undefined, + writable: desc.Writable === engine262.Value.true, + get: desc.Get ? wrap(desc.Get) : undefined, + set: desc.Set ? wrap(desc.Set) : undefined, + configurable: desc.Configurable === engine262.Value.true, + enumerable: desc.Enumerable === engine262.Value.true, + wasThrown: false, + isOwn: p === object, + symbol: key.stringValue ? undefined : wrap(key), + }; + properties.push(descriptor); + } + + if (options.ownProperties) { + break; + } + p = p.GetPrototypeOf(); + if (p instanceof engine262.AbruptCompletion) { + return p; + } + } + + if ('PromiseState' in object) { + internalProperties.push({ + name: '[[PromiseState]]', + value: { + type: 'string', + value: object.PromiseState, + }, + }); + internalProperties.push({ + name: '[[PromiseResult]]', + value: wrap(object.PromiseResult), + }); + } + + return { properties, internalProperties }; + } + + getPropertyPreview(object, options) { + const wrap = (v) => this.toRemoteObject(v, options); + + const keys = object.OwnPropertyKeys(); + if (keys instanceof engine262.AbruptCompletion) { + return keys; + } + + const properties = []; + for (const key of keys) { + const desc = object.GetOwnProperty(key); + if (desc instanceof engine262.AbruptCompletion) { + return desc; + } + const descriptor = { + name: key.stringValue + ? key.stringValue() + : `Symbol(${key.Description.stringValue ? key.Description.stringValue() : ''})`, + }; + if (desc.Value) { + desc.valuePreview = wrap(desc.Value).preview; + switch (engine262.Type(desc.Value)) { + case 'Object': + if ('Call' in desc.Value) { + descriptor.type = 'function'; + } else { + descriptor.type = 'object'; + if ('PromiseState' in desc.Value) { + descriptor.subtype = 'promise'; + } else if ('MapData' in desc.Value) { + descriptor.subtype = 'map'; + } else if ('SetData' in desc.Value) { + descriptor.subtype = 'set'; + } else if ('ErrorData' in desc.Value) { + descriptor.subtype = 'error'; + } else if ('TypedArrayName' in desc.Value) { + descriptor.subtype = 'typedarray'; + } else if ('DataView' in desc.Value) { + descriptor.subtype = 'dataview'; + } else if ('ProxyTarget' in desc.Value) { + descriptor.subtype = 'proxy'; + } else if ('DateValue' in desc.Value) { + descriptor.subtype = 'date'; + } else if ('GeneratorState' in desc.Value) { + descriptor.subtype = 'generator'; + } else if (engine262.IsArray(desc.Value) === engine262.Value.true) { + descriptor.subtype = 'array'; + } + } + break; + case 'Null': + descriptor.type = 'object'; + descriptor.subtype = 'null'; + descriptor.value = 'null'; + break; + case 'Undefined': + descriptor.type = 'undefined'; + descriptor.value = 'undefined'; + break; + case 'String': + descriptor.type = 'string'; + descriptor.value = desc.Value.stringValue(); + break; + case 'Number': { + descriptor.type = 'number'; + descriptor.value = desc.Value.numberValue().toString(); + break; + } + case 'Boolean': + descriptor.type = 'boolean'; + descriptor.value = desc.Value.booleanValue().toString(); + break; + case 'BigInt': + descriptor.type = 'bigint'; + descriptor.value = `${desc.Value.bigintValue().toString()}n`; + break; + case 'Symbol': { + descriptor.type = 'symbol'; + const description = desc.Value.Description === engine262.Value.undefined + ? '' + : desc.Value.Description.stringValue(); + descriptor.value = `Symbol(${description})`; + break; + } + default: + throw new RangeError(); + } + } else { + desc.type = 'accessor'; + } + properties.push(descriptor); + } + + if ('PromiseState' in object) { + properties.push({ + name: '[[PromiseState]]', + type: 'string', + value: object.PromiseState, + }); + } + + return properties; + } + + createEvaluationResult(completion, options) { + if (completion instanceof engine262.AbruptCompletion) { + return { + exceptionDetails: { + text: 'uh oh', + lineNumber: 0, + columnNumber: 0, + exception: this.toRemoteObject(completion.Value, options), + }, + }; + } else { + return { + result: this.toRemoteObject(completion.Value, options), + }; + } + } +} + +function attachRealm(realm) { + contexts.push(new InspectorContext(realm)); +} + +function getContext(id) { + return contexts[id] || contexts[0]; +} + +module.exports = { attachRealm, getContext }; diff --git a/engine262/inspector/index.js b/engine262/inspector/index.js new file mode 100644 index 0000000..58301cc --- /dev/null +++ b/engine262/inspector/index.js @@ -0,0 +1,6 @@ +'use strict'; + +require('./server'); +const { attachRealm } = require('./context'); + +module.exports = { attachRealm }; diff --git a/engine262/inspector/js_protocol.json b/engine262/inspector/js_protocol.json new file mode 100644 index 0000000..a200a5b --- /dev/null +++ b/engine262/inspector/js_protocol.json @@ -0,0 +1,3288 @@ +{ + "version": { + "major": "1", + "minor": "3" + }, + "domains": [ + { + "domain": "Console", + "description": "This domain is deprecated - use Runtime or Log instead.", + "deprecated": true, + "dependencies": [ + "Runtime" + ], + "types": [ + { + "id": "ConsoleMessage", + "description": "Console message.", + "type": "object", + "properties": [ + { + "name": "source", + "description": "Message source.", + "type": "string", + "enum": [ + "xml", + "javascript", + "network", + "console-api", + "storage", + "appcache", + "rendering", + "security", + "other", + "deprecation", + "worker" + ] + }, + { + "name": "level", + "description": "Message severity.", + "type": "string", + "enum": [ + "log", + "warning", + "error", + "debug", + "info" + ] + }, + { + "name": "text", + "description": "Message text.", + "type": "string" + }, + { + "name": "url", + "description": "URL of the message origin.", + "optional": true, + "type": "string" + }, + { + "name": "line", + "description": "Line number in the resource that generated this message (1-based).", + "optional": true, + "type": "integer" + }, + { + "name": "column", + "description": "Column number in the resource that generated this message (1-based).", + "optional": true, + "type": "integer" + } + ] + } + ], + "commands": [ + { + "name": "clearMessages", + "description": "Does nothing." + }, + { + "name": "disable", + "description": "Disables console domain, prevents further console messages from being reported to the client." + }, + { + "name": "enable", + "description": "Enables console domain, sends the messages collected so far to the client by means of the\n`messageAdded` notification." + } + ], + "events": [ + { + "name": "messageAdded", + "description": "Issued when new console message is added.", + "parameters": [ + { + "name": "message", + "description": "Console message that has been added.", + "$ref": "ConsoleMessage" + } + ] + } + ] + }, + { + "domain": "Debugger", + "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing\nbreakpoints, stepping through execution, exploring stack traces, etc.", + "dependencies": [ + "Runtime" + ], + "types": [ + { + "id": "BreakpointId", + "description": "Breakpoint identifier.", + "type": "string" + }, + { + "id": "CallFrameId", + "description": "Call frame identifier.", + "type": "string" + }, + { + "id": "Location", + "description": "Location in the source code.", + "type": "object", + "properties": [ + { + "name": "scriptId", + "description": "Script identifier as reported in the `Debugger.scriptParsed`.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "lineNumber", + "description": "Line number in the script (0-based).", + "type": "integer" + }, + { + "name": "columnNumber", + "description": "Column number in the script (0-based).", + "optional": true, + "type": "integer" + } + ] + }, + { + "id": "ScriptPosition", + "description": "Location in the source code.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "lineNumber", + "type": "integer" + }, + { + "name": "columnNumber", + "type": "integer" + } + ] + }, + { + "id": "CallFrame", + "description": "JavaScript call frame. Array of call frames form the call stack.", + "type": "object", + "properties": [ + { + "name": "callFrameId", + "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused.", + "$ref": "CallFrameId" + }, + { + "name": "functionName", + "description": "Name of the JavaScript function called on this call frame.", + "type": "string" + }, + { + "name": "functionLocation", + "description": "Location in the source code.", + "optional": true, + "$ref": "Location" + }, + { + "name": "location", + "description": "Location in the source code.", + "$ref": "Location" + }, + { + "name": "url", + "description": "JavaScript script name or url.", + "type": "string" + }, + { + "name": "scopeChain", + "description": "Scope chain for this call frame.", + "type": "array", + "items": { + "$ref": "Scope" + } + }, + { + "name": "this", + "description": "`this` object for this call frame.", + "$ref": "Runtime.RemoteObject" + }, + { + "name": "returnValue", + "description": "The value being returned, if the function is at return point.", + "optional": true, + "$ref": "Runtime.RemoteObject" + } + ] + }, + { + "id": "Scope", + "description": "Scope description.", + "type": "object", + "properties": [ + { + "name": "type", + "description": "Scope type.", + "type": "string", + "enum": [ + "global", + "local", + "with", + "closure", + "catch", + "block", + "script", + "eval", + "module" + ] + }, + { + "name": "object", + "description": "Object representing the scope. For `global` and `with` scopes it represents the actual\nobject; for the rest of the scopes, it is artificial transient object enumerating scope\nvariables as its properties.", + "$ref": "Runtime.RemoteObject" + }, + { + "name": "name", + "optional": true, + "type": "string" + }, + { + "name": "startLocation", + "description": "Location in the source code where scope starts", + "optional": true, + "$ref": "Location" + }, + { + "name": "endLocation", + "description": "Location in the source code where scope ends", + "optional": true, + "$ref": "Location" + } + ] + }, + { + "id": "SearchMatch", + "description": "Search match for resource.", + "type": "object", + "properties": [ + { + "name": "lineNumber", + "description": "Line number in resource content.", + "type": "number" + }, + { + "name": "lineContent", + "description": "Line with match content.", + "type": "string" + } + ] + }, + { + "id": "BreakLocation", + "type": "object", + "properties": [ + { + "name": "scriptId", + "description": "Script identifier as reported in the `Debugger.scriptParsed`.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "lineNumber", + "description": "Line number in the script (0-based).", + "type": "integer" + }, + { + "name": "columnNumber", + "description": "Column number in the script (0-based).", + "optional": true, + "type": "integer" + }, + { + "name": "type", + "optional": true, + "type": "string", + "enum": [ + "debuggerStatement", + "call", + "return" + ] + } + ] + } + ], + "commands": [ + { + "name": "continueToLocation", + "description": "Continues execution until specific location is reached.", + "parameters": [ + { + "name": "location", + "description": "Location to continue to.", + "$ref": "Location" + }, + { + "name": "targetCallFrames", + "optional": true, + "type": "string", + "enum": [ + "any", + "current" + ] + } + ] + }, + { + "name": "disable", + "description": "Disables debugger for given page." + }, + { + "name": "enable", + "description": "Enables debugger for the given page. Clients should not assume that the debugging has been\nenabled until the result for this command is received.", + "parameters": [ + { + "name": "maxScriptsCacheSize", + "description": "The maximum size in bytes of collected scripts (not referenced by other heap objects)\nthe debugger can hold. Puts no limit if paramter is omitted.", + "experimental": true, + "optional": true, + "type": "number" + } + ], + "returns": [ + { + "name": "debuggerId", + "description": "Unique identifier of the debugger.", + "experimental": true, + "$ref": "Runtime.UniqueDebuggerId" + } + ] + }, + { + "name": "evaluateOnCallFrame", + "description": "Evaluates expression on a given call frame.", + "parameters": [ + { + "name": "callFrameId", + "description": "Call frame identifier to evaluate on.", + "$ref": "CallFrameId" + }, + { + "name": "expression", + "description": "Expression to evaluate.", + "type": "string" + }, + { + "name": "objectGroup", + "description": "String object group name to put result into (allows rapid releasing resulting object handles\nusing `releaseObjectGroup`).", + "optional": true, + "type": "string" + }, + { + "name": "includeCommandLineAPI", + "description": "Specifies whether command line API should be available to the evaluated expression, defaults\nto false.", + "optional": true, + "type": "boolean" + }, + { + "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "throwOnSideEffect", + "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.", + "optional": true, + "type": "boolean" + }, + { + "name": "timeout", + "description": "Terminate execution after timing out (number of milliseconds).", + "experimental": true, + "optional": true, + "$ref": "Runtime.TimeDelta" + } + ], + "returns": [ + { + "name": "result", + "description": "Object wrapper for the evaluation result.", + "$ref": "Runtime.RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "Runtime.ExceptionDetails" + } + ] + }, + { + "name": "getPossibleBreakpoints", + "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be\nthe same.", + "parameters": [ + { + "name": "start", + "description": "Start of range to search possible breakpoint locations in.", + "$ref": "Location" + }, + { + "name": "end", + "description": "End of range to search possible breakpoint locations in (excluding). When not specified, end\nof scripts is used as end of range.", + "optional": true, + "$ref": "Location" + }, + { + "name": "restrictToFunction", + "description": "Only consider locations which are in the same (non-nested) function as start.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "locations", + "description": "List of the possible breakpoint locations.", + "type": "array", + "items": { + "$ref": "BreakLocation" + } + } + ] + }, + { + "name": "getScriptSource", + "description": "Returns source for the script with given id.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to get source for.", + "$ref": "Runtime.ScriptId" + } + ], + "returns": [ + { + "name": "scriptSource", + "description": "Script source (empty in case of Wasm bytecode).", + "type": "string" + }, + { + "name": "bytecode", + "description": "Wasm bytecode.", + "optional": true, + "type": "string" + } + ] + }, + { + "name": "getWasmBytecode", + "description": "This command is deprecated. Use getScriptSource instead.", + "deprecated": true, + "parameters": [ + { + "name": "scriptId", + "description": "Id of the Wasm script to get source for.", + "$ref": "Runtime.ScriptId" + } + ], + "returns": [ + { + "name": "bytecode", + "description": "Script source.", + "type": "string" + } + ] + }, + { + "name": "getStackTrace", + "description": "Returns stack trace with given `stackTraceId`.", + "experimental": true, + "parameters": [ + { + "name": "stackTraceId", + "$ref": "Runtime.StackTraceId" + } + ], + "returns": [ + { + "name": "stackTrace", + "$ref": "Runtime.StackTrace" + } + ] + }, + { + "name": "pause", + "description": "Stops on the next JavaScript statement." + }, + { + "name": "pauseOnAsyncCall", + "experimental": true, + "deprecated": true, + "parameters": [ + { + "name": "parentStackTraceId", + "description": "Debugger will pause when async call with given stack trace is started.", + "$ref": "Runtime.StackTraceId" + } + ] + }, + { + "name": "removeBreakpoint", + "description": "Removes JavaScript breakpoint.", + "parameters": [ + { + "name": "breakpointId", + "$ref": "BreakpointId" + } + ] + }, + { + "name": "restartFrame", + "description": "Restarts particular call frame from the beginning.", + "parameters": [ + { + "name": "callFrameId", + "description": "Call frame identifier to evaluate on.", + "$ref": "CallFrameId" + } + ], + "returns": [ + { + "name": "callFrames", + "description": "New stack trace.", + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "asyncStackTrace", + "description": "Async stack trace, if any.", + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "asyncStackTraceId", + "description": "Async stack trace, if any.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + } + ] + }, + { + "name": "resume", + "description": "Resumes JavaScript execution." + }, + { + "name": "searchInContent", + "description": "Searches for given string in script content.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to search in.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "query", + "description": "String to search for.", + "type": "string" + }, + { + "name": "caseSensitive", + "description": "If true, search is case sensitive.", + "optional": true, + "type": "boolean" + }, + { + "name": "isRegex", + "description": "If true, treats string parameter as regex.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "List of search matches.", + "type": "array", + "items": { + "$ref": "SearchMatch" + } + } + ] + }, + { + "name": "setAsyncCallStackDepth", + "description": "Enables or disables async call stacks tracking.", + "parameters": [ + { + "name": "maxDepth", + "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", + "type": "integer" + } + ] + }, + { + "name": "setBlackboxPatterns", + "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in\nscripts with url matching one of the patterns. VM will try to leave blackboxed script by\nperforming 'step in' several times, finally resorting to 'step out' if unsuccessful.", + "experimental": true, + "parameters": [ + { + "name": "patterns", + "description": "Array of regexps that will be used to check script url for blackbox state.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + { + "name": "setBlackboxedRanges", + "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted\nscripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\nPositions array contains positions where blackbox state is changed. First interval isn't\nblackboxed. Array should be sorted.", + "experimental": true, + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "positions", + "type": "array", + "items": { + "$ref": "ScriptPosition" + } + } + ] + }, + { + "name": "setBreakpoint", + "description": "Sets JavaScript breakpoint at a given location.", + "parameters": [ + { + "name": "location", + "description": "Location to set breakpoint in.", + "$ref": "Location" + }, + { + "name": "condition", + "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + }, + { + "name": "actualLocation", + "description": "Location this breakpoint resolved into.", + "$ref": "Location" + } + ] + }, + { + "name": "setInstrumentationBreakpoint", + "description": "Sets instrumentation breakpoint.", + "parameters": [ + { + "name": "instrumentation", + "description": "Instrumentation name.", + "type": "string", + "enum": [ + "beforeScriptExecution", + "beforeScriptWithSourceMapExecution" + ] + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + } + ] + }, + { + "name": "setBreakpointByUrl", + "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this\ncommand is issued, all existing parsed scripts will have breakpoints resolved and returned in\n`locations` property. Further matching script parsing will result in subsequent\n`breakpointResolved` events issued. This logical breakpoint will survive page reloads.", + "parameters": [ + { + "name": "lineNumber", + "description": "Line number to set breakpoint at.", + "type": "integer" + }, + { + "name": "url", + "description": "URL of the resources to set breakpoint on.", + "optional": true, + "type": "string" + }, + { + "name": "urlRegex", + "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or\n`urlRegex` must be specified.", + "optional": true, + "type": "string" + }, + { + "name": "scriptHash", + "description": "Script hash of the resources to set breakpoint on.", + "optional": true, + "type": "string" + }, + { + "name": "columnNumber", + "description": "Offset in the line to set breakpoint at.", + "optional": true, + "type": "integer" + }, + { + "name": "condition", + "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + }, + { + "name": "locations", + "description": "List of the locations this breakpoint resolved into upon addition.", + "type": "array", + "items": { + "$ref": "Location" + } + } + ] + }, + { + "name": "setBreakpointOnFunctionCall", + "description": "Sets JavaScript breakpoint before each call to the given function.\nIf another function was created from the same source as a given one,\ncalling it will also trigger the breakpoint.", + "experimental": true, + "parameters": [ + { + "name": "objectId", + "description": "Function object id.", + "$ref": "Runtime.RemoteObjectId" + }, + { + "name": "condition", + "description": "Expression to use as a breakpoint condition. When specified, debugger will\nstop on the breakpoint if this expression evaluates to true.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + } + ] + }, + { + "name": "setBreakpointsActive", + "description": "Activates / deactivates all breakpoints on the page.", + "parameters": [ + { + "name": "active", + "description": "New value for breakpoints active state.", + "type": "boolean" + } + ] + }, + { + "name": "setPauseOnExceptions", + "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or\nno exceptions. Initial pause on exceptions state is `none`.", + "parameters": [ + { + "name": "state", + "description": "Pause on exceptions mode.", + "type": "string", + "enum": [ + "none", + "uncaught", + "all" + ] + } + ] + }, + { + "name": "setReturnValue", + "description": "Changes return value in top frame. Available only at return break position.", + "experimental": true, + "parameters": [ + { + "name": "newValue", + "description": "New return value.", + "$ref": "Runtime.CallArgument" + } + ] + }, + { + "name": "setScriptSource", + "description": "Edits JavaScript source live.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to edit.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "scriptSource", + "description": "New content of the script.", + "type": "string" + }, + { + "name": "dryRun", + "description": "If true the change will not actually be applied. Dry run may be used to get result\ndescription without actually modifying the code.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "callFrames", + "description": "New stack trace in case editing has happened while VM was stopped.", + "optional": true, + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "stackChanged", + "description": "Whether current call stack was modified after applying the changes.", + "optional": true, + "type": "boolean" + }, + { + "name": "asyncStackTrace", + "description": "Async stack trace, if any.", + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "asyncStackTraceId", + "description": "Async stack trace, if any.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + }, + { + "name": "exceptionDetails", + "description": "Exception details if any.", + "optional": true, + "$ref": "Runtime.ExceptionDetails" + } + ] + }, + { + "name": "setSkipAllPauses", + "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).", + "parameters": [ + { + "name": "skip", + "description": "New value for skip pauses state.", + "type": "boolean" + } + ] + }, + { + "name": "setVariableValue", + "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be\nmutated manually.", + "parameters": [ + { + "name": "scopeNumber", + "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch'\nscope types are allowed. Other scopes could be manipulated manually.", + "type": "integer" + }, + { + "name": "variableName", + "description": "Variable name.", + "type": "string" + }, + { + "name": "newValue", + "description": "New variable value.", + "$ref": "Runtime.CallArgument" + }, + { + "name": "callFrameId", + "description": "Id of callframe that holds variable.", + "$ref": "CallFrameId" + } + ] + }, + { + "name": "stepInto", + "description": "Steps into the function call.", + "parameters": [ + { + "name": "breakOnAsyncCall", + "description": "Debugger will pause on the execution of the first async task which was scheduled\nbefore next pause.", + "experimental": true, + "optional": true, + "type": "boolean" + } + ] + }, + { + "name": "stepOut", + "description": "Steps out of the function call." + }, + { + "name": "stepOver", + "description": "Steps over the statement." + } + ], + "events": [ + { + "name": "breakpointResolved", + "description": "Fired when breakpoint is resolved to an actual script and location.", + "parameters": [ + { + "name": "breakpointId", + "description": "Breakpoint unique identifier.", + "$ref": "BreakpointId" + }, + { + "name": "location", + "description": "Actual breakpoint location.", + "$ref": "Location" + } + ] + }, + { + "name": "paused", + "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria.", + "parameters": [ + { + "name": "callFrames", + "description": "Call stack the virtual machine stopped on.", + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "reason", + "description": "Pause reason.", + "type": "string", + "enum": [ + "ambiguous", + "assert", + "debugCommand", + "DOM", + "EventListener", + "exception", + "instrumentation", + "OOM", + "other", + "promiseRejection", + "XHR" + ] + }, + { + "name": "data", + "description": "Object containing break-specific auxiliary properties.", + "optional": true, + "type": "object" + }, + { + "name": "hitBreakpoints", + "description": "Hit breakpoints IDs", + "optional": true, + "type": "array", + "items": { + "type": "string" + } + }, + { + "name": "asyncStackTrace", + "description": "Async stack trace, if any.", + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "asyncStackTraceId", + "description": "Async stack trace, if any.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + }, + { + "name": "asyncCallStackTraceId", + "description": "Never present, will be removed.", + "experimental": true, + "deprecated": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + } + ] + }, + { + "name": "resumed", + "description": "Fired when the virtual machine resumed execution." + }, + { + "name": "scriptFailedToParse", + "description": "Fired when virtual machine fails to parse the script.", + "parameters": [ + { + "name": "scriptId", + "description": "Identifier of the script parsed.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "url", + "description": "URL or name of the script parsed (if any).", + "type": "string" + }, + { + "name": "startLine", + "description": "Line offset of the script within the resource with given URL (for script tags).", + "type": "integer" + }, + { + "name": "startColumn", + "description": "Column offset of the script within the resource with given URL.", + "type": "integer" + }, + { + "name": "endLine", + "description": "Last line of the script.", + "type": "integer" + }, + { + "name": "endColumn", + "description": "Length of the last line of the script.", + "type": "integer" + }, + { + "name": "executionContextId", + "description": "Specifies script creation context.", + "$ref": "Runtime.ExecutionContextId" + }, + { + "name": "hash", + "description": "Content hash of the script.", + "type": "string" + }, + { + "name": "executionContextAuxData", + "description": "Embedder-specific auxiliary data.", + "optional": true, + "type": "object" + }, + { + "name": "sourceMapURL", + "description": "URL of source map associated with script (if any).", + "optional": true, + "type": "string" + }, + { + "name": "hasSourceURL", + "description": "True, if this script has sourceURL.", + "optional": true, + "type": "boolean" + }, + { + "name": "isModule", + "description": "True, if this script is ES6 module.", + "optional": true, + "type": "boolean" + }, + { + "name": "length", + "description": "This script length.", + "optional": true, + "type": "integer" + }, + { + "name": "stackTrace", + "description": "JavaScript top stack frame of where the script parsed event was triggered if available.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTrace" + } + ] + }, + { + "name": "scriptParsed", + "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected\nscripts upon enabling debugger.", + "parameters": [ + { + "name": "scriptId", + "description": "Identifier of the script parsed.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "url", + "description": "URL or name of the script parsed (if any).", + "type": "string" + }, + { + "name": "startLine", + "description": "Line offset of the script within the resource with given URL (for script tags).", + "type": "integer" + }, + { + "name": "startColumn", + "description": "Column offset of the script within the resource with given URL.", + "type": "integer" + }, + { + "name": "endLine", + "description": "Last line of the script.", + "type": "integer" + }, + { + "name": "endColumn", + "description": "Length of the last line of the script.", + "type": "integer" + }, + { + "name": "executionContextId", + "description": "Specifies script creation context.", + "$ref": "Runtime.ExecutionContextId" + }, + { + "name": "hash", + "description": "Content hash of the script.", + "type": "string" + }, + { + "name": "executionContextAuxData", + "description": "Embedder-specific auxiliary data.", + "optional": true, + "type": "object" + }, + { + "name": "isLiveEdit", + "description": "True, if this script is generated as a result of the live edit operation.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "sourceMapURL", + "description": "URL of source map associated with script (if any).", + "optional": true, + "type": "string" + }, + { + "name": "hasSourceURL", + "description": "True, if this script has sourceURL.", + "optional": true, + "type": "boolean" + }, + { + "name": "isModule", + "description": "True, if this script is ES6 module.", + "optional": true, + "type": "boolean" + }, + { + "name": "length", + "description": "This script length.", + "optional": true, + "type": "integer" + }, + { + "name": "stackTrace", + "description": "JavaScript top stack frame of where the script parsed event was triggered if available.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTrace" + } + ] + } + ] + }, + { + "domain": "HeapProfiler", + "experimental": true, + "dependencies": [ + "Runtime" + ], + "types": [ + { + "id": "HeapSnapshotObjectId", + "description": "Heap snapshot object id.", + "type": "string" + }, + { + "id": "SamplingHeapProfileNode", + "description": "Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes.", + "type": "object", + "properties": [ + { + "name": "callFrame", + "description": "Function location.", + "$ref": "Runtime.CallFrame" + }, + { + "name": "selfSize", + "description": "Allocations size in bytes for the node excluding children.", + "type": "number" + }, + { + "name": "id", + "description": "Node id. Ids are unique across all profiles collected between startSampling and stopSampling.", + "type": "integer" + }, + { + "name": "children", + "description": "Child nodes.", + "type": "array", + "items": { + "$ref": "SamplingHeapProfileNode" + } + } + ] + }, + { + "id": "SamplingHeapProfileSample", + "description": "A single sample from a sampling profile.", + "type": "object", + "properties": [ + { + "name": "size", + "description": "Allocation size in bytes attributed to the sample.", + "type": "number" + }, + { + "name": "nodeId", + "description": "Id of the corresponding profile tree node.", + "type": "integer" + }, + { + "name": "ordinal", + "description": "Time-ordered sample ordinal number. It is unique across all profiles retrieved\nbetween startSampling and stopSampling.", + "type": "number" + } + ] + }, + { + "id": "SamplingHeapProfile", + "description": "Sampling profile.", + "type": "object", + "properties": [ + { + "name": "head", + "$ref": "SamplingHeapProfileNode" + }, + { + "name": "samples", + "type": "array", + "items": { + "$ref": "SamplingHeapProfileSample" + } + } + ] + } + ], + "commands": [ + { + "name": "addInspectedHeapObject", + "description": "Enables console to refer to the node with given id via $x (see Command Line API for more details\n$x functions).", + "parameters": [ + { + "name": "heapObjectId", + "description": "Heap snapshot object id to be accessible by means of $x command line API.", + "$ref": "HeapSnapshotObjectId" + } + ] + }, + { + "name": "collectGarbage" + }, + { + "name": "disable" + }, + { + "name": "enable" + }, + { + "name": "getHeapObjectId", + "parameters": [ + { + "name": "objectId", + "description": "Identifier of the object to get heap object id for.", + "$ref": "Runtime.RemoteObjectId" + } + ], + "returns": [ + { + "name": "heapSnapshotObjectId", + "description": "Id of the heap snapshot object corresponding to the passed remote object id.", + "$ref": "HeapSnapshotObjectId" + } + ] + }, + { + "name": "getObjectByHeapObjectId", + "parameters": [ + { + "name": "objectId", + "$ref": "HeapSnapshotObjectId" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "result", + "description": "Evaluation result.", + "$ref": "Runtime.RemoteObject" + } + ] + }, + { + "name": "getSamplingProfile", + "returns": [ + { + "name": "profile", + "description": "Return the sampling profile being collected.", + "$ref": "SamplingHeapProfile" + } + ] + }, + { + "name": "startSampling", + "parameters": [ + { + "name": "samplingInterval", + "description": "Average sample interval in bytes. Poisson distribution is used for the intervals. The\ndefault value is 32768 bytes.", + "optional": true, + "type": "number" + } + ] + }, + { + "name": "startTrackingHeapObjects", + "parameters": [ + { + "name": "trackAllocations", + "optional": true, + "type": "boolean" + } + ] + }, + { + "name": "stopSampling", + "returns": [ + { + "name": "profile", + "description": "Recorded sampling heap profile.", + "$ref": "SamplingHeapProfile" + } + ] + }, + { + "name": "stopTrackingHeapObjects", + "parameters": [ + { + "name": "reportProgress", + "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken\nwhen the tracking is stopped.", + "optional": true, + "type": "boolean" + } + ] + }, + { + "name": "takeHeapSnapshot", + "parameters": [ + { + "name": "reportProgress", + "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken.", + "optional": true, + "type": "boolean" + } + ] + } + ], + "events": [ + { + "name": "addHeapSnapshotChunk", + "parameters": [ + { + "name": "chunk", + "type": "string" + } + ] + }, + { + "name": "heapStatsUpdate", + "description": "If heap objects tracking has been started then backend may send update for one or more fragments", + "parameters": [ + { + "name": "statsUpdate", + "description": "An array of triplets. Each triplet describes a fragment. The first integer is the fragment\nindex, the second integer is a total count of objects for the fragment, the third integer is\na total size of the objects for the fragment.", + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + { + "name": "lastSeenObjectId", + "description": "If heap objects tracking has been started then backend regularly sends a current value for last\nseen object id and corresponding timestamp. If the were changes in the heap since last event\nthen one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event.", + "parameters": [ + { + "name": "lastSeenObjectId", + "type": "integer" + }, + { + "name": "timestamp", + "type": "number" + } + ] + }, + { + "name": "reportHeapSnapshotProgress", + "parameters": [ + { + "name": "done", + "type": "integer" + }, + { + "name": "total", + "type": "integer" + }, + { + "name": "finished", + "optional": true, + "type": "boolean" + } + ] + }, + { + "name": "resetProfiles" + } + ] + }, + { + "domain": "Profiler", + "dependencies": [ + "Runtime", + "Debugger" + ], + "types": [ + { + "id": "ProfileNode", + "description": "Profile node. Holds callsite information, execution statistics and child nodes.", + "type": "object", + "properties": [ + { + "name": "id", + "description": "Unique id of the node.", + "type": "integer" + }, + { + "name": "callFrame", + "description": "Function location.", + "$ref": "Runtime.CallFrame" + }, + { + "name": "hitCount", + "description": "Number of samples where this node was on top of the call stack.", + "optional": true, + "type": "integer" + }, + { + "name": "children", + "description": "Child node ids.", + "optional": true, + "type": "array", + "items": { + "type": "integer" + } + }, + { + "name": "deoptReason", + "description": "The reason of being not optimized. The function may be deoptimized or marked as don't\noptimize.", + "optional": true, + "type": "string" + }, + { + "name": "positionTicks", + "description": "An array of source position ticks.", + "optional": true, + "type": "array", + "items": { + "$ref": "PositionTickInfo" + } + } + ] + }, + { + "id": "Profile", + "description": "Profile.", + "type": "object", + "properties": [ + { + "name": "nodes", + "description": "The list of profile nodes. First item is the root node.", + "type": "array", + "items": { + "$ref": "ProfileNode" + } + }, + { + "name": "startTime", + "description": "Profiling start timestamp in microseconds.", + "type": "number" + }, + { + "name": "endTime", + "description": "Profiling end timestamp in microseconds.", + "type": "number" + }, + { + "name": "samples", + "description": "Ids of samples top nodes.", + "optional": true, + "type": "array", + "items": { + "type": "integer" + } + }, + { + "name": "timeDeltas", + "description": "Time intervals between adjacent samples in microseconds. The first delta is relative to the\nprofile startTime.", + "optional": true, + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + { + "id": "PositionTickInfo", + "description": "Specifies a number of samples attributed to a certain source position.", + "type": "object", + "properties": [ + { + "name": "line", + "description": "Source line number (1-based).", + "type": "integer" + }, + { + "name": "ticks", + "description": "Number of samples attributed to the source line.", + "type": "integer" + } + ] + }, + { + "id": "CoverageRange", + "description": "Coverage data for a source range.", + "type": "object", + "properties": [ + { + "name": "startOffset", + "description": "JavaScript script source offset for the range start.", + "type": "integer" + }, + { + "name": "endOffset", + "description": "JavaScript script source offset for the range end.", + "type": "integer" + }, + { + "name": "count", + "description": "Collected execution count of the source range.", + "type": "integer" + } + ] + }, + { + "id": "FunctionCoverage", + "description": "Coverage data for a JavaScript function.", + "type": "object", + "properties": [ + { + "name": "functionName", + "description": "JavaScript function name.", + "type": "string" + }, + { + "name": "ranges", + "description": "Source ranges inside the function with coverage data.", + "type": "array", + "items": { + "$ref": "CoverageRange" + } + }, + { + "name": "isBlockCoverage", + "description": "Whether coverage data for this function has block granularity.", + "type": "boolean" + } + ] + }, + { + "id": "ScriptCoverage", + "description": "Coverage data for a JavaScript script.", + "type": "object", + "properties": [ + { + "name": "scriptId", + "description": "JavaScript script id.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "url", + "description": "JavaScript script name or url.", + "type": "string" + }, + { + "name": "functions", + "description": "Functions contained in the script that has coverage data.", + "type": "array", + "items": { + "$ref": "FunctionCoverage" + } + } + ] + }, + { + "id": "TypeObject", + "description": "Describes a type collected during runtime.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "name", + "description": "Name of a type collected with type profiling.", + "type": "string" + } + ] + }, + { + "id": "TypeProfileEntry", + "description": "Source offset and types for a parameter or return value.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "offset", + "description": "Source offset of the parameter or end of function for return values.", + "type": "integer" + }, + { + "name": "types", + "description": "The types for this parameter or return value.", + "type": "array", + "items": { + "$ref": "TypeObject" + } + } + ] + }, + { + "id": "ScriptTypeProfile", + "description": "Type profile data collected during runtime for a JavaScript script.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "scriptId", + "description": "JavaScript script id.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "url", + "description": "JavaScript script name or url.", + "type": "string" + }, + { + "name": "entries", + "description": "Type profile entries for parameters and return values of the functions in the script.", + "type": "array", + "items": { + "$ref": "TypeProfileEntry" + } + } + ] + }, + { + "id": "CounterInfo", + "description": "Collected counter information.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "name", + "description": "Counter name.", + "type": "string" + }, + { + "name": "value", + "description": "Counter value.", + "type": "integer" + } + ] + } + ], + "commands": [ + { + "name": "disable" + }, + { + "name": "enable" + }, + { + "name": "getBestEffortCoverage", + "description": "Collect coverage data for the current isolate. The coverage data may be incomplete due to\ngarbage collection.", + "returns": [ + { + "name": "result", + "description": "Coverage data for the current isolate.", + "type": "array", + "items": { + "$ref": "ScriptCoverage" + } + } + ] + }, + { + "name": "setSamplingInterval", + "description": "Changes CPU profiler sampling interval. Must be called before CPU profiles recording started.", + "parameters": [ + { + "name": "interval", + "description": "New sampling interval in microseconds.", + "type": "integer" + } + ] + }, + { + "name": "start" + }, + { + "name": "startPreciseCoverage", + "description": "Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code\ncoverage may be incomplete. Enabling prevents running optimized code and resets execution\ncounters.", + "parameters": [ + { + "name": "callCount", + "description": "Collect accurate call counts beyond simple 'covered' or 'not covered'.", + "optional": true, + "type": "boolean" + }, + { + "name": "detailed", + "description": "Collect block-based coverage.", + "optional": true, + "type": "boolean" + } + ] + }, + { + "name": "startTypeProfile", + "description": "Enable type profile.", + "experimental": true + }, + { + "name": "stop", + "returns": [ + { + "name": "profile", + "description": "Recorded profile.", + "$ref": "Profile" + } + ] + }, + { + "name": "stopPreciseCoverage", + "description": "Disable precise code coverage. Disabling releases unnecessary execution count records and allows\nexecuting optimized code." + }, + { + "name": "stopTypeProfile", + "description": "Disable type profile. Disabling releases type profile data collected so far.", + "experimental": true + }, + { + "name": "takePreciseCoverage", + "description": "Collect coverage data for the current isolate, and resets execution counters. Precise code\ncoverage needs to have started.", + "returns": [ + { + "name": "result", + "description": "Coverage data for the current isolate.", + "type": "array", + "items": { + "$ref": "ScriptCoverage" + } + } + ] + }, + { + "name": "takeTypeProfile", + "description": "Collect type profile.", + "experimental": true, + "returns": [ + { + "name": "result", + "description": "Type profile for all scripts since startTypeProfile() was turned on.", + "type": "array", + "items": { + "$ref": "ScriptTypeProfile" + } + } + ] + }, + { + "name": "enableRuntimeCallStats", + "description": "Enable run time call stats collection.", + "experimental": true + }, + { + "name": "disableRuntimeCallStats", + "description": "Disable run time call stats collection.", + "experimental": true + }, + { + "name": "getRuntimeCallStats", + "description": "Retrieve run time call stats.", + "experimental": true, + "returns": [ + { + "name": "result", + "description": "Collected counter information.", + "type": "array", + "items": { + "$ref": "CounterInfo" + } + } + ] + } + ], + "events": [ + { + "name": "consoleProfileFinished", + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "location", + "description": "Location of console.profileEnd().", + "$ref": "Debugger.Location" + }, + { + "name": "profile", + "$ref": "Profile" + }, + { + "name": "title", + "description": "Profile title passed as an argument to console.profile().", + "optional": true, + "type": "string" + } + ] + }, + { + "name": "consoleProfileStarted", + "description": "Sent when new profile recording is started using console.profile() call.", + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "location", + "description": "Location of console.profile().", + "$ref": "Debugger.Location" + }, + { + "name": "title", + "description": "Profile title passed as an argument to console.profile().", + "optional": true, + "type": "string" + } + ] + } + ] + }, + { + "domain": "Runtime", + "description": "Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects.\nEvaluation results are returned as mirror object that expose object type, string representation\nand unique identifier that can be used for further object reference. Original objects are\nmaintained in memory unless they are either explicitly released or are released along with the\nother objects in their object group.", + "types": [ + { + "id": "ScriptId", + "description": "Unique script identifier.", + "type": "string" + }, + { + "id": "RemoteObjectId", + "description": "Unique object identifier.", + "type": "string" + }, + { + "id": "UnserializableValue", + "description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals.", + "type": "string" + }, + { + "id": "RemoteObject", + "description": "Mirror object referencing original JavaScript object.", + "type": "object", + "properties": [ + { + "name": "type", + "description": "Object type.", + "type": "string", + "enum": [ + "object", + "function", + "undefined", + "string", + "number", + "boolean", + "symbol", + "bigint" + ] + }, + { + "name": "subtype", + "description": "Object subtype hint. Specified for `object` type values only.", + "optional": true, + "type": "string", + "enum": [ + "array", + "null", + "node", + "regexp", + "date", + "map", + "set", + "weakmap", + "weakset", + "iterator", + "generator", + "error", + "proxy", + "promise", + "typedarray", + "arraybuffer", + "dataview" + ] + }, + { + "name": "className", + "description": "Object class (constructor) name. Specified for `object` type values only.", + "optional": true, + "type": "string" + }, + { + "name": "value", + "description": "Remote object value in case of primitive values or JSON values (if it was requested).", + "optional": true, + "type": "any" + }, + { + "name": "unserializableValue", + "description": "Primitive value which can not be JSON-stringified does not have `value`, but gets this\nproperty.", + "optional": true, + "$ref": "UnserializableValue" + }, + { + "name": "description", + "description": "String representation of the object.", + "optional": true, + "type": "string" + }, + { + "name": "objectId", + "description": "Unique object identifier (for non-primitive values).", + "optional": true, + "$ref": "RemoteObjectId" + }, + { + "name": "preview", + "description": "Preview containing abbreviated property values. Specified for `object` type values only.", + "experimental": true, + "optional": true, + "$ref": "ObjectPreview" + }, + { + "name": "customPreview", + "experimental": true, + "optional": true, + "$ref": "CustomPreview" + } + ] + }, + { + "id": "CustomPreview", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "header", + "description": "The JSON-stringified result of formatter.header(object, config) call.\nIt contains json ML array that represents RemoteObject.", + "type": "string" + }, + { + "name": "bodyGetterId", + "description": "If formatter returns true as a result of formatter.hasBody call then bodyGetterId will\ncontain RemoteObjectId for the function that returns result of formatter.body(object, config) call.\nThe result value is json ML array.", + "optional": true, + "$ref": "RemoteObjectId" + } + ] + }, + { + "id": "ObjectPreview", + "description": "Object containing abbreviated remote object value.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "type", + "description": "Object type.", + "type": "string", + "enum": [ + "object", + "function", + "undefined", + "string", + "number", + "boolean", + "symbol", + "bigint" + ] + }, + { + "name": "subtype", + "description": "Object subtype hint. Specified for `object` type values only.", + "optional": true, + "type": "string", + "enum": [ + "array", + "null", + "node", + "regexp", + "date", + "map", + "set", + "weakmap", + "weakset", + "iterator", + "generator", + "error" + ] + }, + { + "name": "description", + "description": "String representation of the object.", + "optional": true, + "type": "string" + }, + { + "name": "overflow", + "description": "True iff some of the properties or entries of the original object did not fit.", + "type": "boolean" + }, + { + "name": "properties", + "description": "List of the properties.", + "type": "array", + "items": { + "$ref": "PropertyPreview" + } + }, + { + "name": "entries", + "description": "List of the entries. Specified for `map` and `set` subtype values only.", + "optional": true, + "type": "array", + "items": { + "$ref": "EntryPreview" + } + } + ] + }, + { + "id": "PropertyPreview", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "name", + "description": "Property name.", + "type": "string" + }, + { + "name": "type", + "description": "Object type. Accessor means that the property itself is an accessor property.", + "type": "string", + "enum": [ + "object", + "function", + "undefined", + "string", + "number", + "boolean", + "symbol", + "accessor", + "bigint" + ] + }, + { + "name": "value", + "description": "User-friendly property value string.", + "optional": true, + "type": "string" + }, + { + "name": "valuePreview", + "description": "Nested value preview.", + "optional": true, + "$ref": "ObjectPreview" + }, + { + "name": "subtype", + "description": "Object subtype hint. Specified for `object` type values only.", + "optional": true, + "type": "string", + "enum": [ + "array", + "null", + "node", + "regexp", + "date", + "map", + "set", + "weakmap", + "weakset", + "iterator", + "generator", + "error" + ] + } + ] + }, + { + "id": "EntryPreview", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "key", + "description": "Preview of the key. Specified for map-like collection entries.", + "optional": true, + "$ref": "ObjectPreview" + }, + { + "name": "value", + "description": "Preview of the value.", + "$ref": "ObjectPreview" + } + ] + }, + { + "id": "PropertyDescriptor", + "description": "Object property descriptor.", + "type": "object", + "properties": [ + { + "name": "name", + "description": "Property name or symbol description.", + "type": "string" + }, + { + "name": "value", + "description": "The value associated with the property.", + "optional": true, + "$ref": "RemoteObject" + }, + { + "name": "writable", + "description": "True if the value associated with the property may be changed (data descriptors only).", + "optional": true, + "type": "boolean" + }, + { + "name": "get", + "description": "A function which serves as a getter for the property, or `undefined` if there is no getter\n(accessor descriptors only).", + "optional": true, + "$ref": "RemoteObject" + }, + { + "name": "set", + "description": "A function which serves as a setter for the property, or `undefined` if there is no setter\n(accessor descriptors only).", + "optional": true, + "$ref": "RemoteObject" + }, + { + "name": "configurable", + "description": "True if the type of this property descriptor may be changed and if the property may be\ndeleted from the corresponding object.", + "type": "boolean" + }, + { + "name": "enumerable", + "description": "True if this property shows up during enumeration of the properties on the corresponding\nobject.", + "type": "boolean" + }, + { + "name": "wasThrown", + "description": "True if the result was thrown during the evaluation.", + "optional": true, + "type": "boolean" + }, + { + "name": "isOwn", + "description": "True if the property is owned for the object.", + "optional": true, + "type": "boolean" + }, + { + "name": "symbol", + "description": "Property symbol object, if the property is of the `symbol` type.", + "optional": true, + "$ref": "RemoteObject" + } + ] + }, + { + "id": "InternalPropertyDescriptor", + "description": "Object internal property descriptor. This property isn't normally visible in JavaScript code.", + "type": "object", + "properties": [ + { + "name": "name", + "description": "Conventional property name.", + "type": "string" + }, + { + "name": "value", + "description": "The value associated with the property.", + "optional": true, + "$ref": "RemoteObject" + } + ] + }, + { + "id": "PrivatePropertyDescriptor", + "description": "Object private field descriptor.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "name", + "description": "Private property name.", + "type": "string" + }, + { + "name": "value", + "description": "The value associated with the private property.", + "$ref": "RemoteObject" + } + ] + }, + { + "id": "CallArgument", + "description": "Represents function call argument. Either remote object id `objectId`, primitive `value`,\nunserializable primitive value or neither of (for undefined) them should be specified.", + "type": "object", + "properties": [ + { + "name": "value", + "description": "Primitive value or serializable javascript object.", + "optional": true, + "type": "any" + }, + { + "name": "unserializableValue", + "description": "Primitive value which can not be JSON-stringified.", + "optional": true, + "$ref": "UnserializableValue" + }, + { + "name": "objectId", + "description": "Remote object handle.", + "optional": true, + "$ref": "RemoteObjectId" + } + ] + }, + { + "id": "ExecutionContextId", + "description": "Id of an execution context.", + "type": "integer" + }, + { + "id": "ExecutionContextDescription", + "description": "Description of an isolated world.", + "type": "object", + "properties": [ + { + "name": "id", + "description": "Unique id of the execution context. It can be used to specify in which execution context\nscript evaluation should be performed.", + "$ref": "ExecutionContextId" + }, + { + "name": "origin", + "description": "Execution context origin.", + "type": "string" + }, + { + "name": "name", + "description": "Human readable name describing given context.", + "type": "string" + }, + { + "name": "auxData", + "description": "Embedder-specific auxiliary data.", + "optional": true, + "type": "object" + } + ] + }, + { + "id": "ExceptionDetails", + "description": "Detailed information about exception (or error) that was thrown during script compilation or\nexecution.", + "type": "object", + "properties": [ + { + "name": "exceptionId", + "description": "Exception id.", + "type": "integer" + }, + { + "name": "text", + "description": "Exception text, which should be used together with exception object when available.", + "type": "string" + }, + { + "name": "lineNumber", + "description": "Line number of the exception location (0-based).", + "type": "integer" + }, + { + "name": "columnNumber", + "description": "Column number of the exception location (0-based).", + "type": "integer" + }, + { + "name": "scriptId", + "description": "Script ID of the exception location.", + "optional": true, + "$ref": "ScriptId" + }, + { + "name": "url", + "description": "URL of the exception location, to be used when the script was not reported.", + "optional": true, + "type": "string" + }, + { + "name": "stackTrace", + "description": "JavaScript stack trace if available.", + "optional": true, + "$ref": "StackTrace" + }, + { + "name": "exception", + "description": "Exception object if available.", + "optional": true, + "$ref": "RemoteObject" + }, + { + "name": "executionContextId", + "description": "Identifier of the context where exception happened.", + "optional": true, + "$ref": "ExecutionContextId" + } + ] + }, + { + "id": "Timestamp", + "description": "Number of milliseconds since epoch.", + "type": "number" + }, + { + "id": "TimeDelta", + "description": "Number of milliseconds.", + "type": "number" + }, + { + "id": "CallFrame", + "description": "Stack entry for runtime errors and assertions.", + "type": "object", + "properties": [ + { + "name": "functionName", + "description": "JavaScript function name.", + "type": "string" + }, + { + "name": "scriptId", + "description": "JavaScript script id.", + "$ref": "ScriptId" + }, + { + "name": "url", + "description": "JavaScript script name or url.", + "type": "string" + }, + { + "name": "lineNumber", + "description": "JavaScript script line number (0-based).", + "type": "integer" + }, + { + "name": "columnNumber", + "description": "JavaScript script column number (0-based).", + "type": "integer" + } + ] + }, + { + "id": "StackTrace", + "description": "Call frames for assertions or error messages.", + "type": "object", + "properties": [ + { + "name": "description", + "description": "String label of this stack trace. For async traces this may be a name of the function that\ninitiated the async call.", + "optional": true, + "type": "string" + }, + { + "name": "callFrames", + "description": "JavaScript function name.", + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "parent", + "description": "Asynchronous JavaScript stack trace that preceded this stack, if available.", + "optional": true, + "$ref": "StackTrace" + }, + { + "name": "parentId", + "description": "Asynchronous JavaScript stack trace that preceded this stack, if available.", + "experimental": true, + "optional": true, + "$ref": "StackTraceId" + } + ] + }, + { + "id": "UniqueDebuggerId", + "description": "Unique identifier of current debugger.", + "experimental": true, + "type": "string" + }, + { + "id": "StackTraceId", + "description": "If `debuggerId` is set stack trace comes from another debugger and can be resolved there. This\nallows to track cross-debugger calls. See `Runtime.StackTrace` and `Debugger.paused` for usages.", + "experimental": true, + "type": "object", + "properties": [ + { + "name": "id", + "type": "string" + }, + { + "name": "debuggerId", + "optional": true, + "$ref": "UniqueDebuggerId" + } + ] + } + ], + "commands": [ + { + "name": "awaitPromise", + "description": "Add handler to promise with given promise object id.", + "parameters": [ + { + "name": "promiseObjectId", + "description": "Identifier of the promise.", + "$ref": "RemoteObjectId" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Promise result. Will contain rejected value if promise was rejected.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details if stack strace is available.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "callFunctionOn", + "description": "Calls function with given declaration on the given object. Object group of the result is\ninherited from the target object.", + "parameters": [ + { + "name": "functionDeclaration", + "description": "Declaration of the function to call.", + "type": "string" + }, + { + "name": "objectId", + "description": "Identifier of the object to call function on. Either objectId or executionContextId should\nbe specified.", + "optional": true, + "$ref": "RemoteObjectId" + }, + { + "name": "arguments", + "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target\nobject.", + "optional": true, + "type": "array", + "items": { + "$ref": "CallArgument" + } + }, + { + "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object which should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "userGesture", + "description": "Whether execution should be treated as initiated by user in the UI.", + "optional": true, + "type": "boolean" + }, + { + "name": "awaitPromise", + "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", + "optional": true, + "type": "boolean" + }, + { + "name": "executionContextId", + "description": "Specifies execution context which global object will be used to call function on. Either\nexecutionContextId or objectId should be specified.", + "optional": true, + "$ref": "ExecutionContextId" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not\nspecified and objectId is, objectGroup will be inherited from object.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "result", + "description": "Call result.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "compileScript", + "description": "Compiles expression.", + "parameters": [ + { + "name": "expression", + "description": "Expression to compile.", + "type": "string" + }, + { + "name": "sourceURL", + "description": "Source url to be set for the script.", + "type": "string" + }, + { + "name": "persistScript", + "description": "Specifies whether the compiled script should be persisted.", + "type": "boolean" + }, + { + "name": "executionContextId", + "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", + "optional": true, + "$ref": "ExecutionContextId" + } + ], + "returns": [ + { + "name": "scriptId", + "description": "Id of the script.", + "optional": true, + "$ref": "ScriptId" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "disable", + "description": "Disables reporting of execution contexts creation." + }, + { + "name": "discardConsoleEntries", + "description": "Discards collected exceptions and console API calls." + }, + { + "name": "enable", + "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext." + }, + { + "name": "evaluate", + "description": "Evaluates expression on global object.", + "parameters": [ + { + "name": "expression", + "description": "Expression to evaluate.", + "type": "string" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects.", + "optional": true, + "type": "string" + }, + { + "name": "includeCommandLineAPI", + "description": "Determines whether Command Line API should be available during the evaluation.", + "optional": true, + "type": "boolean" + }, + { + "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { + "name": "contextId", + "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", + "optional": true, + "$ref": "ExecutionContextId" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "userGesture", + "description": "Whether execution should be treated as initiated by user in the UI.", + "optional": true, + "type": "boolean" + }, + { + "name": "awaitPromise", + "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", + "optional": true, + "type": "boolean" + }, + { + "name": "throwOnSideEffect", + "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.\nThis implies `disableBreaks` below.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "timeout", + "description": "Terminate execution after timing out (number of milliseconds).", + "experimental": true, + "optional": true, + "$ref": "TimeDelta" + }, + { + "name": "disableBreaks", + "description": "Disable breakpoints during execution.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "replMode", + "description": "Reserved flag for future REPL mode support. Setting this flag has currently no effect.", + "experimental": true, + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Evaluation result.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "getIsolateId", + "description": "Returns the isolate id.", + "experimental": true, + "returns": [ + { + "name": "id", + "description": "The isolate id.", + "type": "string" + } + ] + }, + { + "name": "getHeapUsage", + "description": "Returns the JavaScript heap usage.\nIt is the total usage of the corresponding isolate not scoped to a particular Runtime.", + "experimental": true, + "returns": [ + { + "name": "usedSize", + "description": "Used heap size in bytes.", + "type": "number" + }, + { + "name": "totalSize", + "description": "Allocated heap size in bytes.", + "type": "number" + } + ] + }, + { + "name": "getProperties", + "description": "Returns properties of a given object. Object group of the result is inherited from the target\nobject.", + "parameters": [ + { + "name": "objectId", + "description": "Identifier of the object to return properties for.", + "$ref": "RemoteObjectId" + }, + { + "name": "ownProperties", + "description": "If true, returns properties belonging only to the element itself, not to its prototype\nchain.", + "optional": true, + "type": "boolean" + }, + { + "name": "accessorPropertiesOnly", + "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not\nreturned either.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the results.", + "experimental": true, + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Object properties.", + "type": "array", + "items": { + "$ref": "PropertyDescriptor" + } + }, + { + "name": "internalProperties", + "description": "Internal object properties (only of the element itself).", + "optional": true, + "type": "array", + "items": { + "$ref": "InternalPropertyDescriptor" + } + }, + { + "name": "privateProperties", + "description": "Object private properties.", + "experimental": true, + "optional": true, + "type": "array", + "items": { + "$ref": "PrivatePropertyDescriptor" + } + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "globalLexicalScopeNames", + "description": "Returns all let, const and class variables from global scope.", + "parameters": [ + { + "name": "executionContextId", + "description": "Specifies in which execution context to lookup global scope variables.", + "optional": true, + "$ref": "ExecutionContextId" + } + ], + "returns": [ + { + "name": "names", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + { + "name": "queryObjects", + "parameters": [ + { + "name": "prototypeObjectId", + "description": "Identifier of the prototype to return objects for.", + "$ref": "RemoteObjectId" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release the results.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "objects", + "description": "Array with objects.", + "$ref": "RemoteObject" + } + ] + }, + { + "name": "releaseObject", + "description": "Releases remote object with given id.", + "parameters": [ + { + "name": "objectId", + "description": "Identifier of the object to release.", + "$ref": "RemoteObjectId" + } + ] + }, + { + "name": "releaseObjectGroup", + "description": "Releases all remote objects that belong to a given group.", + "parameters": [ + { + "name": "objectGroup", + "description": "Symbolic object group name.", + "type": "string" + } + ] + }, + { + "name": "runIfWaitingForDebugger", + "description": "Tells inspected instance to run if it was waiting for debugger to attach." + }, + { + "name": "runScript", + "description": "Runs script with given id in a given context.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to run.", + "$ref": "ScriptId" + }, + { + "name": "executionContextId", + "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", + "optional": true, + "$ref": "ExecutionContextId" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects.", + "optional": true, + "type": "string" + }, + { + "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { + "name": "includeCommandLineAPI", + "description": "Determines whether Command Line API should be available during the evaluation.", + "optional": true, + "type": "boolean" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object which should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "optional": true, + "type": "boolean" + }, + { + "name": "awaitPromise", + "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Run result.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "setAsyncCallStackDepth", + "description": "Enables or disables async call stacks tracking.", + "redirect": "Debugger", + "parameters": [ + { + "name": "maxDepth", + "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", + "type": "integer" + } + ] + }, + { + "name": "setCustomObjectFormatterEnabled", + "experimental": true, + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ] + }, + { + "name": "setMaxCallStackSizeToCapture", + "experimental": true, + "parameters": [ + { + "name": "size", + "type": "integer" + } + ] + }, + { + "name": "terminateExecution", + "description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.", + "experimental": true + }, + { + "name": "addBinding", + "description": "If executionContextId is empty, adds binding with the given name on the\nglobal objects of all inspected contexts, including those created later,\nbindings survive reloads.\nIf executionContextId is specified, adds binding only on global object of\ngiven execution context.\nBinding function takes exactly one argument, this argument should be string,\nin case of any other input, function throws an exception.\nEach binding function call produces Runtime.bindingCalled notification.", + "experimental": true, + "parameters": [ + { + "name": "name", + "type": "string" + }, + { + "name": "executionContextId", + "optional": true, + "$ref": "ExecutionContextId" + } + ] + }, + { + "name": "removeBinding", + "description": "This method does not remove binding function from global object but\nunsubscribes current runtime agent from Runtime.bindingCalled notifications.", + "experimental": true, + "parameters": [ + { + "name": "name", + "type": "string" + } + ] + } + ], + "events": [ + { + "name": "bindingCalled", + "description": "Notification is issued every time when binding is called.", + "experimental": true, + "parameters": [ + { + "name": "name", + "type": "string" + }, + { + "name": "payload", + "type": "string" + }, + { + "name": "executionContextId", + "description": "Identifier of the context where the call was made.", + "$ref": "ExecutionContextId" + } + ] + }, + { + "name": "consoleAPICalled", + "description": "Issued when console API was called.", + "parameters": [ + { + "name": "type", + "description": "Type of the call.", + "type": "string", + "enum": [ + "log", + "debug", + "info", + "error", + "warning", + "dir", + "dirxml", + "table", + "trace", + "clear", + "startGroup", + "startGroupCollapsed", + "endGroup", + "assert", + "profile", + "profileEnd", + "count", + "timeEnd" + ] + }, + { + "name": "args", + "description": "Call arguments.", + "type": "array", + "items": { + "$ref": "RemoteObject" + } + }, + { + "name": "executionContextId", + "description": "Identifier of the context where the call was made.", + "$ref": "ExecutionContextId" + }, + { + "name": "timestamp", + "description": "Call timestamp.", + "$ref": "Timestamp" + }, + { + "name": "stackTrace", + "description": "Stack trace captured when the call was made. The async stack chain is automatically reported for\nthe following call types: `assert`, `error`, `trace`, `warning`. For other types the async call\nchain can be retrieved using `Debugger.getStackTrace` and `stackTrace.parentId` field.", + "optional": true, + "$ref": "StackTrace" + }, + { + "name": "context", + "description": "Console context descriptor for calls on non-default console context (not console.*):\n'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call\non named context.", + "experimental": true, + "optional": true, + "type": "string" + } + ] + }, + { + "name": "exceptionRevoked", + "description": "Issued when unhandled exception was revoked.", + "parameters": [ + { + "name": "reason", + "description": "Reason describing why exception was revoked.", + "type": "string" + }, + { + "name": "exceptionId", + "description": "The id of revoked exception, as reported in `exceptionThrown`.", + "type": "integer" + } + ] + }, + { + "name": "exceptionThrown", + "description": "Issued when exception was thrown and unhandled.", + "parameters": [ + { + "name": "timestamp", + "description": "Timestamp of the exception.", + "$ref": "Timestamp" + }, + { + "name": "exceptionDetails", + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "executionContextCreated", + "description": "Issued when new execution context is created.", + "parameters": [ + { + "name": "context", + "description": "A newly created execution context.", + "$ref": "ExecutionContextDescription" + } + ] + }, + { + "name": "executionContextDestroyed", + "description": "Issued when execution context is destroyed.", + "parameters": [ + { + "name": "executionContextId", + "description": "Id of the destroyed context", + "$ref": "ExecutionContextId" + } + ] + }, + { + "name": "executionContextsCleared", + "description": "Issued when all executionContexts were cleared in browser" + }, + { + "name": "inspectRequested", + "description": "Issued when object should be inspected (for example, as a result of inspect() command line API\ncall).", + "parameters": [ + { + "name": "object", + "$ref": "RemoteObject" + }, + { + "name": "hints", + "type": "object" + } + ] + } + ] + }, + { + "domain": "Schema", + "description": "This domain is deprecated.", + "deprecated": true, + "types": [ + { + "id": "Domain", + "description": "Description of the protocol domain.", + "type": "object", + "properties": [ + { + "name": "name", + "description": "Domain name.", + "type": "string" + }, + { + "name": "version", + "description": "Domain version.", + "type": "string" + } + ] + } + ], + "commands": [ + { + "name": "getDomains", + "description": "Returns supported domains.", + "returns": [ + { + "name": "domains", + "description": "List of supported domains.", + "type": "array", + "items": { + "$ref": "Domain" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/engine262/inspector/methods.js b/engine262/inspector/methods.js new file mode 100644 index 0000000..7299c9f --- /dev/null +++ b/engine262/inspector/methods.js @@ -0,0 +1,105 @@ +'use strict'; + +const engine262 = require('..'); +const { getContext } = require('./context'); + +module.exports = { + Debugger: { + enable() { + return { debuggerId: 'debugger.0' }; + }, + setAsyncCallStackDepth() {}, + setBlackboxPatterns() {}, + setPauseOnExceptions() {}, + }, + Profiler: { + enable() {}, + }, + Runtime: { + enable() {}, + compileScript() { + return { scriptId: 'script.0' }; + }, + callFunctionOn(options) { + const context = getContext(options.executionContextId); + const { Value: F } = context.realm.evaluateScript(`(${options.functionDeclaration})`); + const thisValue = options.objectId + ? context.getObject(options.objectId) + : engine262.Value.undefined; + const args = options.arguments.map((a) => { + if ('value' in a) { + return new engine262.Value(context.realm, a.value); + } + if ('objectId' in a) { + return context.getObject(a.objectId); + } + if ('unserializableValue' in a) { + throw new RangeError(); + } + return engine262.Value.undefined; + }); + const r = engine262.Call(F, thisValue, args); + return context.createEvaluationResult(r, options); + }, + evaluate(options) { + if (options.throwOnSideEffect || options.awaitPromise) { + return { + exceptionDetails: { + text: 'unsupported', + lineNumber: 0, + columnNumber: 0, + }, + }; + } + + const context = getContext(options.contextId); + const r = context.realm.evaluateScript(options.expression); + return context.createEvaluationResult(r, options); + }, + getHeapUsage() { + return { usedSize: 0, totalSize: 0 }; + }, + getIsolateId() { + return { id: 'isolate.0' }; + }, + getProperties(options) { + const context = getContext(); + const object = context.getObject(options.objectId); + + const properties = context.getProperties(object, options); + if (properties instanceof engine262.AbruptCompletion) { + return context.createEvaluationResult(properties, options); + } + + return { + result: properties.properties, + internalProperties: properties.internalProperties, + }; + }, + globalLexicalScopeNames({ executionContextId }) { + const context = getContext(executionContextId); + const envRec = context.realm.realm.GlobalEnv.EnvironmentRecord; + const names = Map.prototype.keys.call(envRec.DeclarativeRecord.bindings); + return { + names: [...names], + }; + }, + releaseObjectGroup({ objectGroup }) { + getContext().releaseObjectGroup(objectGroup); + }, + runIfWaitingForDebugger(params, ctx) { + ctx.sendEvent('Runtime.executionContextCreated', { + context: { + id: '0', + origin: 'file://', + name: 'context.0', + auxData: {}, + }, + }); + }, + }, + HeapProfiler: { + enable() {}, + collectGarbage() {}, + }, +}; diff --git a/engine262/inspector/server.js b/engine262/inspector/server.js new file mode 100644 index 0000000..ecbf511 --- /dev/null +++ b/engine262/inspector/server.js @@ -0,0 +1,87 @@ +'use strict'; + +const http = require('http'); +const WebSocket = require('ws'); // eslint-disable-line import/no-extraneous-dependencies +const packageJson = require('../package.json'); +const protocol = require('./js_protocol.json'); +const methods = require('./methods'); + +const server = http.createServer((req, res) => { + if (req.method !== 'GET') { + res.writeHead(405); + res.end(); + return; + } + + const json = (obj) => { + const s = JSON.stringify(obj); + res.writeHead(200, { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(s), + }); + res.end(s); + }; + + switch (req.url) { + case '/json': + case '/json/list': + json([{ + description: `${packageJson.name} instance`, + devtoolsFrontendUrl: 'chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9229/', + devtoolsFrontendUrlCompat: 'chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9229/', + faviconUrl: 'https://avatars0.githubusercontent.com/u/51185628', + id: 'inspector.0', + title: 'engine262', + type: 'node', + url: `file://${process.cwd()}`, + webSocketDebuggerUrl: 'ws://localhost:9229/', + }]); + break; + case '/json/version': + json({ + 'Browser': `${packageJson.name}/v${packageJson.version}`, + 'Protocol-Version': `${protocol.version.major}.${protocol.version.minor}`, + }); + break; + case '/json/protocol': + json(protocol); + break; + default: + res.writeHead(404); + res.end(); + break; + } +}); + +const wss = new WebSocket.Server({ server }); + +wss.on('connection', (ws) => { + const send = (obj) => { + const s = JSON.stringify(obj); + // console.log('<-', s); + ws.send(s); + }; + + ws._socket.unref(); + + const context = { + sendEvent(event, params) { + send({ method: event, params }); + }, + }; + + ws.on('message', (data) => { + // console.log('->', data); + const { id, method, params } = JSON.parse(data); + const [k, v] = method.split('.'); + Promise.resolve(methods[k][v](params, context)) + .then((result = {}) => { + send({ id, result }); + }); + }); +}); + +server.listen(9229, '127.0.0.1', () => { + console.log('Debugger listening at localhost:9229'); // eslint-disable-line no-console +}); +server.unref(); diff --git a/engine262/package.json b/engine262/package.json new file mode 100644 index 0000000..ebbab4a --- /dev/null +++ b/engine262/package.json @@ -0,0 +1,54 @@ +{ + "name": "engine262", + "version": "0.0.1", + "description": "Implementation of ECMA-262 in JavaScript", + "author": "engine262 Contributors", + "license": "MIT", + "homepage": "https://github.com/engine262/engine262#readme", + "bugs": { + "url": "https://github.com/engine262/engine262/issues" + }, + "main": "dist/engine262", + "scripts": { + "lint": "eslint rollup.config.js test/ src/ bin/ inspector/ scripts/ --cache --ext=js,mjs", + "build": "npm run build:engine", + "build:engine": "rollup -c", + "test": "bash test/test_root.sh", + "test:test262": "node test/test262/test262.js", + "test:supplemental": "node test/supplemental.js", + "test:json": "node test/json/json.js", + "coverage": "nyc --reporter=lcov npm run test", + "prepublishOnly": "node scripts/tag_version_with_git_hash.js", + "postpublish": "git reset --hard HEAD" + }, + "bin": { + "engine262": "bin/engine262.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/engine262/engine262.git" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com/@engine262" + }, + "dependencies": {}, + "devDependencies": { + "@babel/core": "^7.11.1", + "@babel/eslint-parser": "^7.11.3", + "@rollup/plugin-babel": "^5.1.0", + "@rollup/plugin-commonjs": "^14.0.0", + "@rollup/plugin-node-resolve": "^8.4.0", + "@snek/source-map-support": "^1.0.4", + "acorn": "^7.4.0", + "eslint": "^7.2.0", + "eslint-config-airbnb-base": "^14.2.0", + "eslint-plugin-import": "^2.22.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "nyc": "^15.1.0", + "rollup": "^2.23.1", + "test262-stream": "^1.3.0", + "unicode-13.0.0": "^0.8.0", + "ws": "^7.2.3" + } +} diff --git a/engine262/rollup.config.js b/engine262/rollup.config.js new file mode 100644 index 0000000..de83e3b --- /dev/null +++ b/engine262/rollup.config.js @@ -0,0 +1,55 @@ +'use strict'; + +const fs = require('fs'); +const { execSync } = require('child_process'); +const { babel } = require('@rollup/plugin-babel'); +const commonjs = require('@rollup/plugin-commonjs'); +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const { name, version } = require('./package.json'); + +const hash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + +const banner = `/*! + * engine262 ${version} ${hash} + * + * ${fs.readFileSync('./LICENSE', 'utf8').trim().split('\n').join('\n * ')} + */ +`; + +module.exports = () => ({ + input: './src/api.mjs', + plugins: [ + commonjs(), + nodeResolve(), + babel({ + babelHelpers: 'bundled', + exclude: 'node_modules/**', + plugins: [ + './scripts/transform.js', + ], + }), + ], + output: [ + { + file: 'dist/engine262.js', + format: 'umd', + sourcemap: true, + name, + banner, + }, + { + file: 'dist/engine262.mjs', + format: 'es', + sourcemap: true, + banner, + }, + ], + onwarn(warning, warn) { + if (warning.code === 'CIRCULAR_DEPENDENCY') { + // Squelch. + return; + } + process.exitCode = 1; + warn(warning); + }, +}); diff --git a/engine262/scripts/tag_version_with_git_hash.js b/engine262/scripts/tag_version_with_git_hash.js new file mode 100644 index 0000000..f974fa3 --- /dev/null +++ b/engine262/scripts/tag_version_with_git_hash.js @@ -0,0 +1,20 @@ +'use strict'; + +const { execSync } = require('child_process'); +const fs = require('fs'); + +const pjsonPath = require.resolve('../package.json'); + +const pjson = JSON.parse(fs.readFileSync(pjsonPath, 'utf8')); + +process.stdout.write('Checking package.json for git revision...\n'); + +if (!pjson.version.includes('-')) { + process.stdout.write('Inserting git revision into package.json...\n'); + + const hash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + pjson.version = `${pjson.version}-${hash}`; + fs.writeFileSync(pjsonPath, `${JSON.stringify(pjson, null, 2)}\n`); +} + +process.stdout.write('Done!\n'); diff --git a/engine262/scripts/transform.js b/engine262/scripts/transform.js new file mode 100644 index 0000000..8f25206 --- /dev/null +++ b/engine262/scripts/transform.js @@ -0,0 +1,260 @@ +'use strict'; + +const { relative, resolve } = require('path'); + +const COMPLETION_PATH = resolve('./src/completion.mjs'); +const ABSTRACT_OPS_PATH = resolve('./src/abstract-ops/all.mjs'); +const VALUE_PATH = resolve('./src/value.mjs'); + +function fileToImport(file, refPath) { + return relative(file.opts.filename, refPath) + .replace(/\\/g, '/') // Support building on Windows + .replace('../', './'); +} + +function findParentStatementPath(path) { + while (path && !path.isStatement()) { + path = path.parentPath; + } + return path; +} + +function getEnclosingConditionalExpression(path) { + while (path && !path.isStatement()) { + if (path.isConditionalExpression()) { + return path; + } + path = path.parentPath; + } + return null; +} + +module.exports = ({ types: t, template }) => { + function createImportCompletion(file) { + const r = fileToImport(file, COMPLETION_PATH); + return template.ast(` + import { Completion } from "${r}"; + `); + } + + function createImportAbruptCompletion(file) { + const r = fileToImport(file, COMPLETION_PATH); + return template.ast(` + import { AbruptCompletion } from "${r}"; + `); + } + + function createImportAssert(file) { + const r = fileToImport(file, ABSTRACT_OPS_PATH); + return template.ast(` + import { Assert } from "${r}"; + `); + } + + function createImportCall(file) { + const r = fileToImport(file, ABSTRACT_OPS_PATH); + return template.ast(` + import { Call } from "${r}"; + `); + } + + function createImportValue(file) { + const r = fileToImport(file, VALUE_PATH); + return template.ast(` + import { Value } from "${r}"; + `); + } + + function addSectionFromComments(path) { + if (path.node.leadingComments) { + for (const c of path.node.leadingComments) { + const lines = c.value.split('\n'); + for (const line of lines) { + if (/#sec/.test(line)) { + const section = line.split(' ').find((l) => l.includes('#sec')); + const url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`; + path.insertAfter(template.ast(`${path.node.id ? path.node.id.name : path.node.declarations[0].id.name}.section = '${url}';`)); + return; + } + } + } + } + } + + const MACROS = { + Q: { + template: template(` + let ID = ARGUMENT; + /* istanbul ignore if */ + if (ID instanceof AbruptCompletion) { + return ID; + } + /* istanbul ignore if */ + if (ID instanceof Completion) { + ID = ID.Value; + } + `, { preserveComments: true }), + imports: ['AbruptCompletion', 'Completion'], + }, + X: { + template: template(` + let ID = ARGUMENT; + Assert(!(ID instanceof AbruptCompletion), SOURCE + ' returned an abrupt completion'); + /* istanbul ignore if */ + if (ID instanceof Completion) { + ID = ID.Value; + } + `, { preserveComments: true }), + imports: ['Assert', 'Completion', 'AbruptCompletion'], + }, + IfAbruptRejectPromise: { + template: template(` + /* istanbul ignore if */ + if (ID instanceof AbruptCompletion) { + const hygenicTemp2 = Call(CAPABILITY.Reject, Value.undefined, [ID.Value]); + if (hygenicTemp2 instanceof AbruptCompletion) { + return hygenicTemp2; + } + return CAPABILITY.Promise; + } + /* istanbul ignore if */ + if (ID instanceof Completion) { + ID = ID.Value; + } + `, { preserveComments: true }), + imports: ['Call', 'Value', 'AbruptCompletion', 'Completion'], + }, + }; + MACROS.ReturnIfAbrupt = MACROS.Q; + const MACRO_NAMES = Object.keys(MACROS); + + return { + visitor: { + Program: { + enter(path, state) { + state.needed = {}; + }, + exit(path, state) { + if (state.needed.Completion && !state.file.opts.filename.endsWith('completion.mjs')) { + path.node.body.unshift(createImportCompletion(state.file)); + } + if (state.needed.AbruptCompletion && !state.file.opts.filename.endsWith('completion.mjs')) { + path.node.body.unshift(createImportAbruptCompletion(state.file)); + } + if (state.needed.Assert) { + path.node.body.unshift(createImportAssert(state.file)); + } + if (state.needed.Call) { + path.node.body.unshift(createImportCall(state.file)); + } + if (state.needed.Value) { + path.node.body.unshift(createImportValue(state.file)); + } + }, + }, + CallExpression(path, state) { + if (!t.isIdentifier(path.node.callee)) { + return; + } + + const macroName = path.node.callee.name; + if (MACRO_NAMES.includes(macroName)) { + const enclosingConditional = getEnclosingConditionalExpression(path); + if (enclosingConditional !== null) { + if (enclosingConditional.parentPath.isVariableDeclarator()) { + const declaration = enclosingConditional.parentPath.parentPath; + const id = enclosingConditional.parentPath.get('id'); + declaration.replaceWithMultiple(template.ast(` + let ${id}; + if (${enclosingConditional.get('test')}) { + ${id} = ${enclosingConditional.get('consequent')} + } else { + ${id} = ${enclosingConditional.get('alternate')} + } + `)); + return; + } else { + throw path.buildCodeFrameError('Macros may not be used within conditional expressions'); + } + } + + const macro = MACROS[macroName]; + const [argument] = path.node.arguments; + + if (macro === MACROS.Q && (t.isReturnStatement(path.parentPath) || path.parentPath.isArrowFunctionExpression())) { + path.replaceWith(path.node.arguments[0]); + return; + } + + if (path.parentPath.isArrowFunctionExpression()) { + throw path.buildCodeFrameError('Macros may not be the sole expression of an arrow function'); + } + + const statementPath = findParentStatementPath(path); + + macro.imports.forEach((i) => { + state.needed[i] = path.scope.getBinding(i) === undefined; + }); + + if (macro === MACROS.Q && t.isIdentifier(argument)) { + const binding = path.scope.getBinding(argument.name); + binding.path.parent.kind = 'let'; + statementPath.insertBefore(template(` + /* istanbul ignore if */ + if (ID instanceof AbruptCompletion) { + return ID; + } + /* istanbul ignore if */ + if (ID instanceof Completion) { + ID = ID.Value; + } + `, { preserveComments: true })({ ID: argument })); + path.replaceWith(argument); + } else { + if (macro === MACROS.IfAbruptRejectPromise) { + const [, capability] = path.node.arguments; + if (!t.isIdentifier(argument)) { + throw path.get('arguments.0').buildCodeFrameError('First argument to IfAbruptRejectPromise should be an identifier'); + } + if (!t.isIdentifier(capability)) { + throw path.get('arguments.1').buildCodeFrameError('Second argument to IfAbruptRejectPromise should be an identifier'); + } + const binding = path.scope.getBinding(argument.name); + binding.path.parent.kind = 'let'; + statementPath.insertBefore(macro.template({ ID: argument, CAPABILITY: capability })); + path.remove(); + } else { + const id = statementPath.scope.generateUidIdentifier(); + const replacement = { + ARGUMENT: argument, + ID: id, + }; + if (macro === MACROS.X) { + replacement.SOURCE = t.stringLiteral(path.get('arguments.0').getSource()); + } + statementPath.insertBefore(macro.template(replacement)); + path.replaceWith(id); + } + } + } else if (macroName === 'Assert') { + path.node.arguments.push(t.stringLiteral(path.get('arguments.0').getSource())); + } + }, + SwitchCase(path) { + const n = path.node.consequent[0]; + if (t.isThrowStatement(n) && t.isNewExpression(n.argument) && n.argument.callee.name === 'OutOfRange') { + path.node.leadingComments = path.node.leadingComments || []; + path.node.leadingComments.push({ type: 'CommentBlock', value: 'istanbul ignore next' }); + } + }, + FunctionDeclaration(path) { + addSectionFromComments(path); + }, + VariableDeclaration(path) { + if (path.get('declarations.0.init').isArrowFunctionExpression()) { + addSectionFromComments(path); + } + }, + }, + }; +}; diff --git a/engine262/src/abstract-ops/all.mjs b/engine262/src/abstract-ops/all.mjs new file mode 100644 index 0000000..3da9f1e --- /dev/null +++ b/engine262/src/abstract-ops/all.mjs @@ -0,0 +1,32 @@ +export * from './arguments-operations.mjs'; +export * from './array-objects.mjs'; +export * from './arraybuffer-objects.mjs'; +export * from './async-function-operations.mjs'; +export * from './async-generator-objects.mjs'; +export * from './data-types-and-values.mjs'; +export * from './dataview-objects.mjs'; +export * from './date-objects.mjs'; +export * from './execution-contexts.mjs'; +export * from './function-operations.mjs'; +export * from './generator-operations.mjs'; +export * from './global-object.mjs'; +export * from './immutable-prototype-objects.mjs'; +export * from './integer-indexed-objects.mjs'; +export * from './iterator-operations.mjs'; +export * from './module-namespace-exotic-objects.mjs'; +export * from './module-records.mjs'; +export * from './notational-conventions.mjs'; +export * from './object-operations.mjs'; +export * from './objects.mjs'; +export * from './promise-operations.mjs'; +export * from './proxy-objects.mjs'; +export * from './realms.mjs'; +export * from './reference-operations.mjs'; +export * from './regexp-objects.mjs'; +export * from './spec-types.mjs'; +export * from './string-objects.mjs'; +export * from './symbol-objects.mjs'; +export * from './testing-comparison.mjs'; +export * from './type-conversion.mjs'; +export * from './typedarray-objects.mjs'; +export * from './weak-operations.mjs'; diff --git a/engine262/src/abstract-ops/arguments-operations.mjs b/engine262/src/abstract-ops/arguments-operations.mjs new file mode 100644 index 0000000..b97d6bc --- /dev/null +++ b/engine262/src/abstract-ops/arguments-operations.mjs @@ -0,0 +1,248 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Descriptor, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { BoundNames } from '../static-semantics/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { ValueSet } from '../helpers.mjs'; +import { + Assert, + CreateBuiltinFunction, + CreateDataProperty, + DefinePropertyOrThrow, + SetFunctionLength, + ToString, + SameValue, + MakeBasicObject, + OrdinaryObjectCreate, + OrdinaryGetOwnProperty, + OrdinaryDefineOwnProperty, + OrdinaryGet, + OrdinarySet, + OrdinaryDelete, + Get, + Set, + HasOwnProperty, + IsAccessorDescriptor, + IsDataDescriptor, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 9.4.4 #sec-arguments-exotic-objects + + +function ArgumentsGetOwnProperty(P) { + const args = this; + const desc = OrdinaryGetOwnProperty(args, P); + if (desc === Value.undefined) { + return desc; + } + const map = args.ParameterMap; + const isMapped = X(HasOwnProperty(map, P)); + if (isMapped === Value.true) { + desc.Value = Get(map, P); + } + return desc; +} + +function ArgumentsDefineOwnProperty(P, Desc) { + const args = this; + const map = args.ParameterMap; + const isMapped = X(HasOwnProperty(map, P)); + let newArgDesc = Desc; + if (isMapped === Value.true && IsDataDescriptor(Desc) === true) { + if (Desc.Value === undefined && Desc.Writable !== undefined && Desc.Writable === Value.false) { + newArgDesc = Descriptor({ ...Desc }); + newArgDesc.Value = X(Get(map, P)); + } + } + const allowed = Q(OrdinaryDefineOwnProperty(args, P, newArgDesc)); + if (allowed === Value.false) { + return Value.false; + } + if (isMapped === Value.true) { + if (IsAccessorDescriptor(Desc) === true) { + map.Delete(P); + } else { + if (Desc.Value !== undefined) { + const setStatus = Set(map, P, Desc.Value, Value.false); + Assert(setStatus === Value.true); + } + if (Desc.Writable !== undefined && Desc.Writable === Value.false) { + map.Delete(P); + } + } + } + return Value.true; +} + +function ArgumentsGet(P, Receiver) { + const args = this; + const map = args.ParameterMap; + const isMapped = X(HasOwnProperty(map, P)); + if (isMapped === Value.false) { + return Q(OrdinaryGet(args, P, Receiver)); + } else { + return Get(map, P); + } +} + +function ArgumentsSet(P, V, Receiver) { + const args = this; + let isMapped; + let map; + if (SameValue(args, Receiver) === Value.false) { + isMapped = false; + } else { + map = args.ParameterMap; + isMapped = X(HasOwnProperty(map, P)) === Value.true; + } + if (isMapped) { + const setStatus = Set(map, P, V, Value.false); + Assert(setStatus === Value.true); + } + return Q(OrdinarySet(args, P, V, Receiver)); +} + +function ArgumentsDelete(P) { + const args = this; + const map = args.ParameterMap; + const isMapped = X(HasOwnProperty(map, P)); + const result = Q(OrdinaryDelete(args, P)); + if (result === Value.true && isMapped === Value.true) { + map.Delete(P); + } + return result; +} + +// 9.4.4.6 #sec-createunmappedargumentsobject +export function CreateUnmappedArgumentsObject(argumentsList) { + const len = argumentsList.length; + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'), ['ParameterMap']); + obj.ParameterMap = Value.undefined; + DefinePropertyOrThrow(obj, new Value('length'), Descriptor({ + Value: new Value(len), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + })); + let index = 0; + while (index < len) { + const val = argumentsList[index]; + const idxStr = X(ToString(new Value(index))); + X(CreateDataProperty(obj, idxStr, val)); + index += 1; + } + X(DefinePropertyOrThrow(obj, wellKnownSymbols.iterator, Descriptor({ + Value: surroundingAgent.intrinsic('%Array.prototype.values%'), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + X(DefinePropertyOrThrow(obj, new Value('callee'), Descriptor({ + Get: surroundingAgent.intrinsic('%ThrowTypeError%'), + Set: surroundingAgent.intrinsic('%ThrowTypeError%'), + Enumerable: Value.false, + Configurable: Value.false, + }))); + return obj; +} + +function ArgGetterSteps() { + const f = this; + const name = f.Name; + const env = f.Env; + return env.GetBindingValue(name, Value.false); +} + +// 9.4.4.7.1 #sec-makearggetter +function MakeArgGetter(name, env) { + const steps = ArgGetterSteps; + const getter = X(CreateBuiltinFunction(steps, ['Name', 'Env'])); + getter.Name = name; + getter.Env = env; + return getter; +} + +function ArgSetterSteps([value]) { + Assert(value !== undefined); + const f = this; + const name = f.Name; + const env = f.Env; + return env.SetMutableBinding(name, value, Value.false); +} + +// 9.4.4.7.2 #sec-makeargsetter +function MakeArgSetter(name, env) { + const steps = ArgSetterSteps; + const setter = X(CreateBuiltinFunction(steps, ['Name', 'Env'])); + SetFunctionLength(setter, new Value(1)); + setter.Name = name; + setter.Env = env; + return setter; +} + +// 9.4.4.7 #sec-createmappedargumentsobject +export function CreateMappedArgumentsObject(func, formals, argumentsList, env) { + // Assert: formals does not contain a rest parameter, any binding + // patterns, or any initializers. It may contain duplicate identifiers. + const len = argumentsList.length; + const obj = X(MakeBasicObject(['Prototype', 'Extensible', 'ParameterMap'])); + obj.GetOwnProperty = ArgumentsGetOwnProperty; + obj.DefineOwnProperty = ArgumentsDefineOwnProperty; + obj.Get = ArgumentsGet; + obj.Set = ArgumentsSet; + obj.Delete = ArgumentsDelete; + obj.Prototype = surroundingAgent.intrinsic('%Object.prototype%'); + const map = OrdinaryObjectCreate(Value.null); + obj.ParameterMap = map; + const parameterNames = BoundNames(formals); + const numberOfParameters = parameterNames.length; + let index = 0; + while (index < len) { + const val = argumentsList[index]; + const idxStr = X(ToString(new Value(index))); + X(CreateDataProperty(obj, idxStr, val)); + index += 1; + } + X(DefinePropertyOrThrow(obj, new Value('length'), Descriptor({ + Value: new Value(len), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + const mappedNames = new ValueSet(); + index = numberOfParameters - 1; + while (index >= 0) { + const name = parameterNames[index]; + if (!mappedNames.has(name)) { + mappedNames.add(name); + if (index < len) { + const g = MakeArgGetter(name, env); + const p = MakeArgSetter(name, env); + X(map.DefineOwnProperty(X(ToString(new Value(index))), Descriptor({ + Set: p, + Get: g, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + } + index -= 1; + } + X(DefinePropertyOrThrow(obj, wellKnownSymbols.iterator, Descriptor({ + Value: surroundingAgent.intrinsic('%Array.prototype.values%'), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + X(DefinePropertyOrThrow(obj, new Value('callee'), Descriptor({ + Value: func, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + return obj; +} diff --git a/engine262/src/abstract-ops/array-objects.mjs b/engine262/src/abstract-ops/array-objects.mjs new file mode 100644 index 0000000..1eb5e3f --- /dev/null +++ b/engine262/src/abstract-ops/array-objects.mjs @@ -0,0 +1,250 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Descriptor, + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { + AbstractRelationalComparison, + Assert, + Call, + Construct, + Get, + GetFunctionRealm, + IsDataDescriptor, + IsArray, + IsConstructor, + OrdinaryDefineOwnProperty, + OrdinaryGetOwnProperty, + OrdinaryObjectCreate, + MakeBasicObject, + SameValue, + ToBoolean, + ToNumber, + ToString, + ToUint32, + IsPropertyKey, + IsNonNegativeInteger, + isArrayIndex, +} from './all.mjs'; + +// #sec-array-exotic-objects-defineownproperty-p-desc +function ArrayDefineOwnProperty(P, Desc) { + const A = this; + + Assert(IsPropertyKey(P)); + if (Type(P) === 'String' && P.stringValue() === 'length') { + return Q(ArraySetLength(A, Desc)); + } else if (isArrayIndex(P)) { + const oldLenDesc = OrdinaryGetOwnProperty(A, new Value('length')); + Assert(X(IsDataDescriptor(oldLenDesc))); + Assert(oldLenDesc.Configurable === Value.false); + const oldLen = oldLenDesc.Value; + const index = X(ToUint32(P)); + if (index.numberValue() >= oldLen.numberValue() && oldLenDesc.Writable === Value.false) { + return Value.false; + } + const succeeded = X(OrdinaryDefineOwnProperty(A, P, Desc)); + if (succeeded === Value.false) { + return Value.false; + } + if (index.numberValue() >= oldLen.numberValue()) { + oldLenDesc.Value = new Value(index.numberValue() + 1); + const succeeded = OrdinaryDefineOwnProperty(A, new Value('length'), oldLenDesc); // eslint-disable-line no-shadow + Assert(succeeded === Value.true); + } + return Value.true; + } + return OrdinaryDefineOwnProperty(A, P, Desc); +} + +export function isArrayExoticObject(O) { + return O.DefineOwnProperty === ArrayDefineOwnProperty; +} + +// 9.4.2.2 #sec-arraycreate +export function ArrayCreate(length, proto) { + Assert(X(IsNonNegativeInteger(length)) === Value.true); + if (Object.is(length.numberValue(), -0)) { + length = new Value(0); + } + if (length.numberValue() > (2 ** 32) - 1) { + return surroundingAgent.Throw('RangeError', 'InvalidArrayLength', length); + } + if (proto === undefined) { + proto = surroundingAgent.intrinsic('%Array.prototype%'); + } + const A = X(MakeBasicObject(['Prototype', 'Extensible'])); + A.Prototype = proto; + A.DefineOwnProperty = ArrayDefineOwnProperty; + + X(OrdinaryDefineOwnProperty(A, new Value('length'), Descriptor({ + Value: length, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + return A; +} + +// 9.4.2.3 #sec-arrayspeciescreate +export function ArraySpeciesCreate(originalArray, length) { + Assert(Type(length) === 'Number' && Number.isInteger(length.numberValue()) && length.numberValue() >= 0); + if (Object.is(length.numberValue(), -0)) { + length = new Value(+0); + } + const isArray = Q(IsArray(originalArray)); + if (isArray === Value.false) { + return Q(ArrayCreate(length)); + } + let C = Q(Get(originalArray, new Value('constructor'))); + if (IsConstructor(C) === Value.true) { + const thisRealm = surroundingAgent.currentRealmRecord; + const realmC = Q(GetFunctionRealm(C)); + if (thisRealm !== realmC) { + if (SameValue(C, realmC.Intrinsics['%Array%']) === Value.true) { + C = Value.undefined; + } + } + } + if (Type(C) === 'Object') { + C = Q(Get(C, wellKnownSymbols.species)); + if (C === Value.null) { + C = Value.undefined; + } + } + if (C === Value.undefined) { + return Q(ArrayCreate(length)); + } + if (IsConstructor(C) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + return Q(Construct(C, [length])); +} + +// 9.4.2.4 #sec-arraysetlength +export function ArraySetLength(A, Desc) { + if (Desc.Value === undefined) { + return OrdinaryDefineOwnProperty(A, new Value('length'), Desc); + } + const newLenDesc = Descriptor({ ...Desc }); + const newLen = Q(ToUint32(Desc.Value)).numberValue(); + const numberLen = Q(ToNumber(Desc.Value)).numberValue(); + if (newLen !== numberLen) { + return surroundingAgent.Throw('RangeError', 'InvalidArrayLength', Desc.Value); + } + newLenDesc.Value = new Value(newLen); + const oldLenDesc = OrdinaryGetOwnProperty(A, new Value('length')); + Assert(X(IsDataDescriptor(oldLenDesc))); + Assert(oldLenDesc.Configurable === Value.false); + const oldLen = oldLenDesc.Value.numberValue(); + if (newLen >= oldLen) { + return OrdinaryDefineOwnProperty(A, new Value('length'), newLenDesc); + } + if (oldLenDesc.Writable === Value.false) { + return Value.false; + } + let newWritable; + if (newLenDesc.Writable === undefined || newLenDesc.Writable === Value.true) { + newWritable = true; + } else { + newWritable = false; + newLenDesc.Writable = Value.true; + } + const succeeded = X(OrdinaryDefineOwnProperty(A, new Value('length'), newLenDesc)); + if (succeeded === Value.false) { + return Value.false; + } + const keys = []; + A.properties.forEach((value, key) => { + if (isArrayIndex(key) && Number(key.stringValue()) >= newLen) { + keys.push(key); + } + }); + keys.sort((a, b) => Number(b.stringValue()) - Number(a.stringValue())); + for (const P of keys) { + const deleteSucceeded = X(A.Delete(P)); + if (deleteSucceeded === Value.false) { + newLenDesc.Value = new Value(X(ToUint32(P)).numberValue() + 1); + if (newWritable === false) { + newLenDesc.Writable = Value.false; + } + X(OrdinaryDefineOwnProperty(A, new Value('length'), newLenDesc)); + return Value.false; + } + } + if (newWritable === false) { + const s = OrdinaryDefineOwnProperty(A, new Value('length'), Descriptor({ Writable: Value.false })); + Assert(s === Value.true); + } + return Value.true; +} + +// 22.1.3.1.1 #sec-isconcatspreadable +export function IsConcatSpreadable(O) { + if (Type(O) !== 'Object') { + return Value.false; + } + const spreadable = Q(Get(O, wellKnownSymbols.isConcatSpreadable)); + if (spreadable !== Value.undefined) { + return ToBoolean(spreadable); + } + return Q(IsArray(O)); +} + +// 22.1.3.27.1 #sec-sortcompare +export function SortCompare(x, y, comparefn) { + if (x === Value.undefined && y === Value.undefined) { + return new Value(+0); + } + if (x === Value.undefined) { + return new Value(1); + } + if (y === Value.undefined) { + return new Value(-1); + } + if (comparefn !== Value.undefined) { + const callRes = Q(Call(comparefn, Value.undefined, [x, y])); + const v = Q(ToNumber(callRes)); + if (v.isNaN()) { + return new Value(+0); + } + return v; + } + const xString = Q(ToString(x)); + const yString = Q(ToString(y)); + const xSmaller = AbstractRelationalComparison(xString, yString); + if (xSmaller === Value.true) { + return new Value(-1); + } + const ySmaller = AbstractRelationalComparison(yString, xString); + if (ySmaller === Value.true) { + return new Value(1); + } + return new Value(+0); +} + +// 22.1.5.1 #sec-createarrayiterator +export function CreateArrayIterator(array, kind) { + // 1. Assert: Type(array) is Object. + Assert(Type(array) === 'Object'); + // 2. Assert: kind is key+value, key, or value. + Assert(kind === 'key+value' || kind === 'key' || kind === 'value'); + // 3. Let iterator be ObjectCreate(%ArrayIteratorPrototype%, « [[IteratedArrayLike]], [[ArrayLikeNextIndex]], [[ArrayLikeIterationKind]] »). + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%ArrayIterator.prototype%'), [ + 'IteratedArrayLike', + 'ArrayLikeNextIndex', + 'ArrayLikeIterationKind', + ]); + // 4. Set iterator.[[IteratedArrayLike]] to array. + iterator.IteratedArrayLike = array; + // 5. Set iterator.[[ArrayLikeNextIndex]] to 0. + iterator.ArrayLikeNextIndex = 0; + // 6. Set iterator.[[ArrayLikeIterationKind]] to kind. + iterator.ArrayLikeIterationKind = kind; + // 7. Return iterator. + return iterator; +} diff --git a/engine262/src/abstract-ops/arraybuffer-objects.mjs b/engine262/src/abstract-ops/arraybuffer-objects.mjs new file mode 100644 index 0000000..1da4932 --- /dev/null +++ b/engine262/src/abstract-ops/arraybuffer-objects.mjs @@ -0,0 +1,213 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q, X, NormalCompletion } from '../completion.mjs'; +import { + Assert, OrdinaryCreateFromConstructor, + IsNonNegativeInteger, CreateByteDataBlock, + SameValue, IsConstructor, CopyDataBlockBytes, + typedArrayInfoByType, +} from './all.mjs'; + +// #sec-allocatearraybuffer +export function AllocateArrayBuffer(constructor, byteLength) { + // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »). + const obj = Q(OrdinaryCreateFromConstructor(constructor, '%ArrayBuffer.prototype%', [ + 'ArrayBufferData', 'ArrayBufferByteLength', 'ArrayBufferDetachKey', + ])); + // 2. Assert: ! IsNonNegativeInteger(byteLength) is true. + Assert(X(IsNonNegativeInteger(byteLength)) === Value.true); + // 3. Let block be ? CreateByteDataBlock(byteLength). + const block = Q(CreateByteDataBlock(byteLength)); + // 4. Set obj.[[ArrayBufferData]] to block. + obj.ArrayBufferData = block; + // 5. Set obj.[[ArrayBufferByteLength]] to byteLength. + obj.ArrayBufferByteLength = byteLength; + // 6. Return obj. + return obj; +} + +// #sec-isdetachedbuffer +export function IsDetachedBuffer(arrayBuffer) { + // 1. Assert: Type(arrayBuffer) is Object and it has an [[ArrayBufferData]] internal slot. + Assert(Type(arrayBuffer) === 'Object' && 'ArrayBufferData' in arrayBuffer); + // 2. If arrayBuffer.[[ArrayBufferData]] is null, return true. + if (arrayBuffer.ArrayBufferData === Value.null) { + return Value.true; + } + // 3. Return false. + return Value.false; +} + +// #sec-detacharraybuffer +export function DetachArrayBuffer(arrayBuffer, key) { + // 1. Assert: Type(arrayBuffer) is Object and it has [[ArrayBufferData]], [[ArrayBufferByteLength]], and [[ArrayBufferDetachKey]] internal slots. + Assert(Type(arrayBuffer) === 'Object' + && 'ArrayBufferData' in arrayBuffer + && 'ArrayBufferByteLength' in arrayBuffer + && 'ArrayBufferDetachKey' in arrayBuffer); + // 2. Assert: IsSharedArrayBuffer(arrayBuffer) is false. + Assert(IsSharedArrayBuffer(arrayBuffer) === Value.false); + // 3. If key is not present, set key to undefined. + if (key === undefined) { + key = Value.undefined; + } + // 4. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. + if (SameValue(arrayBuffer.ArrayBufferDetachKey, key) === Value.false) { + return surroundingAgent.Throw('TypeError', 'BufferDetachKeyMismatch', key, arrayBuffer); + } + // 5. Set arrayBuffer.[[ArrayBufferData]] to null. + arrayBuffer.ArrayBufferData = Value.null; + // 6. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. + arrayBuffer.ArrayBufferByteLength = new Value(0); + // 7. Return NormalCompletion(null). + return NormalCompletion(Value.null); +} + +// #sec-issharedarraybuffer +export function IsSharedArrayBuffer(_obj) { + return Value.false; +} + +export function CloneArrayBuffer(srcBuffer, srcByteOffset, srcLength, cloneConstructor) { + // 1. Assert: Type(srcBuffer) is Object and it has an [[ArrayBufferData]] internal slot. + Assert(Type(srcBuffer) === 'Object' && 'ArrayBufferData' in srcBuffer); + // 2. Assert: IsConstructor(cloneConstructor) is true. + Assert(IsConstructor(cloneConstructor) === Value.true); + // 3. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength). + const targetBuffer = Q(AllocateArrayBuffer(cloneConstructor, srcLength)); + // 4. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(srcBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Let srcBlock be srcBuffer.[[ArrayBufferData]]. + const srcBlock = srcBuffer.ArrayBufferData; + // 6. Let targetBlock be targetBuffer.[[ArrayBufferData]]. + const targetBlock = targetBuffer.ArrayBufferData; + // 7. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). + CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset.numberValue(), srcLength.numberValue()); + // 8. Return targetBuffer. + return targetBuffer; +} + +// #sec-isbigintelementtype +export function IsBigIntElementType(type) { + // 1. If type is BigUint64 or BigInt64, return true. + if (type === 'BigUint64' || type === 'BigInt64') { + return Value.true; + } + // 2. Return false + return Value.false; +} + +const throwawayBuffer = new ArrayBuffer(8); +const throwawayDataView = new DataView(throwawayBuffer); +const throwawayArray = new Uint8Array(throwawayBuffer); + +// #sec-rawbytestonumeric +export function RawBytesToNumeric(type, rawBytes, isLittleEndian) { + // 1. Let elementSize be the Element Size value specified in Table 61 for Element Type type. + const elementSize = typedArrayInfoByType[type].ElementSize; + Assert(elementSize === rawBytes.length); + const dataViewType = type === 'Uint8C' ? 'Uint8' : type; + Object.assign(throwawayArray, rawBytes); + return new Value(throwawayDataView[`get${dataViewType}`](0, isLittleEndian === Value.true)); +} + +// #sec-getvaluefrombuffer +export function GetValueFromBuffer(arrayBuffer, byteIndex, type, isTypedArray, order, isLittleEndian) { + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + Assert(IsDetachedBuffer(arrayBuffer) === Value.false); + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Assert: ! IsNonNegativeInteger(byteIndex) is true. + Assert(X(IsNonNegativeInteger(byteIndex)) === Value.true); + // 4. Let block be arrayBuffer.[[ArrayBufferData]]. + const block = arrayBuffer.ArrayBufferData; + // 5. Let elementSize be the Element Size value specified in Table 61 for Element Type type. + const elementSize = typedArrayInfoByType[type].ElementSize; + // 6. If IsSharedArrayBuffer(arrayBuffer) is true, then + if (IsSharedArrayBuffer(arrayBuffer) === Value.true) { + Assert(false); + } + // 7. Else, let rawValue be a List of elementSize containing, in order, the elementSize sequence of bytes starting with block[byteIndex]. + const rawValue = [...block.subarray(byteIndex.numberValue(), byteIndex.numberValue() + elementSize)]; + // 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + if (isLittleEndian === undefined) { + isLittleEndian = surroundingAgent.AgentRecord.LittleEndian; + } + // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian). + return RawBytesToNumeric(type, rawValue, isLittleEndian); +} + +const float32NaNLE = Object.freeze([0, 0, 192, 127]); +const float32NaNBE = Object.freeze([127, 192, 0, 0]); +const float64NaNLE = Object.freeze([0, 0, 0, 0, 0, 0, 248, 127]); +const float64NaNBE = Object.freeze([127, 248, 0, 0, 0, 0, 0, 0]); + +// #sec-numerictorawbytes +export function NumericToRawBytes(type, value, isLittleEndian) { + Assert(Type(isLittleEndian) === 'Boolean'); + isLittleEndian = isLittleEndian === Value.true; + let rawBytes; + // One day, we will write our own IEEE 754 and two's complement encoder… + if (type === 'Float32') { + if (Number.isNaN(value.numberValue())) { + rawBytes = isLittleEndian ? [...float32NaNLE] : [...float32NaNBE]; + } else { + throwawayDataView.setFloat32(0, value.numberValue(), isLittleEndian); + rawBytes = [...throwawayArray.subarray(0, 4)]; + } + } else if (type === 'Float64') { + if (Number.isNaN(value.numberValue())) { + rawBytes = isLittleEndian ? [...float64NaNLE] : [...float64NaNBE]; + } else { + throwawayDataView.setFloat64(0, value.numberValue(), isLittleEndian); + rawBytes = [...throwawayArray.subarray(0, 8)]; + } + } else { + // a. Let n be the Element Size value specified in Table 61 for Element Type type. + const n = typedArrayInfoByType[type].ElementSize; + // b. Let convOp be the abstract operation named in the Conversion Operation column in Table 61 for Element Type type. + const convOp = typedArrayInfoByType[type].ConversionOperation; + // c. Let intValue be convOp(value) treated as a mathematical value, whether the result is a BigInt or Number. + const intValue = X(convOp(value)); + const dataViewType = type === 'Uint8C' ? 'Uint8' : type; + throwawayDataView[`set${dataViewType}`](0, intValue.bigintValue ? intValue.bigintValue() : intValue.numberValue(), isLittleEndian); + rawBytes = [...throwawayArray.subarray(0, n)]; + } + return rawBytes; +} + +// #sec-setvalueinbuffer +export function SetValueInBuffer(arrayBuffer, byteIndex, type, value, isTypedArray, order, isLittleEndian) { + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + Assert(IsDetachedBuffer(arrayBuffer) === Value.false); + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Assert: ! IsNonNegativeInteger(byteIndex) is true. + Assert(X(IsNonNegativeInteger(byteIndex)) === Value.true); + // 4. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number. + if (X(IsBigIntElementType(type)) === Value.true) { + Assert(Type(value) === 'BigInt'); + } else { + Assert(Type(value) === 'Number'); + } + // 5. Let block be arrayBuffer.[[ArrayBufferData]]. + const block = arrayBuffer.ArrayBufferData; + // 6. Let elementSize be the Element Size value specified in Table 61 for Element Type type. + // const elementSize = typedArrayInfo[type].ElementSize; + // 7. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + if (isLittleEndian === undefined) { + isLittleEndian = surroundingAgent.AgentRecord.LittleEndian; + } + // 8. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). + const rawBytes = NumericToRawBytes(type, value, isLittleEndian); + // 9. If IsSharedArrayBuffer(arrayBuffer) is true, then + if (IsSharedArrayBuffer(arrayBuffer) === Value.true) { + Assert(false); + } + // 10. Else, store the individual bytes of rawBytes into block, in order, starting at block[byteIndex]. + rawBytes.forEach((byte, i) => { + block[byteIndex.numberValue() + i] = byte; + }); + // 11. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} diff --git a/engine262/src/abstract-ops/async-function-operations.mjs b/engine262/src/abstract-ops/async-function-operations.mjs new file mode 100644 index 0000000..e8a7b4f --- /dev/null +++ b/engine262/src/abstract-ops/async-function-operations.mjs @@ -0,0 +1,42 @@ +import { EnsureCompletion, X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Value } from '../value.mjs'; +import { resume } from '../helpers.mjs'; +import { Assert, Call } from './all.mjs'; + +// This file covers abstract operations defined in +// 25.7 #sec-async-function-objects + +// https://tc39.es/proposal-top-level-await/#sec-asyncblockstart +export function AsyncBlockStart(promiseCapability, asyncBody, asyncContext) { + asyncContext.promiseCapability = promiseCapability; + + const runningContext = surroundingAgent.runningExecutionContext; + asyncContext.codeEvaluationState = (function* resumer() { + const result = EnsureCompletion(yield* Evaluate(asyncBody)); + // Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. + surroundingAgent.executionContextStack.pop(asyncContext); + if (result.Type === 'normal') { + X(Call(promiseCapability.Resolve, Value.undefined, [Value.undefined])); + } else if (result.Type === 'return') { + X(Call(promiseCapability.Resolve, Value.undefined, [result.Value])); + } else { + Assert(result.Type === 'throw'); + X(Call(promiseCapability.Reject, Value.undefined, [result.Value])); + } + return Value.undefined; + }()); + surroundingAgent.executionContextStack.push(asyncContext); + const result = EnsureCompletion(resume(asyncContext, undefined)); + Assert(surroundingAgent.runningExecutionContext === runningContext); + Assert(result.Type === 'normal' && result.Value === Value.undefined); + return Value.undefined; +} + +// 25.7.5.1 #sec-async-functions-abstract-operations-async-function-start +export function AsyncFunctionStart(promiseCapability, asyncFunctionBody) { + const runningContext = surroundingAgent.runningExecutionContext; + const asyncContext = runningContext.copy(); + X(AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext)); +} diff --git a/engine262/src/abstract-ops/async-generator-objects.mjs b/engine262/src/abstract-ops/async-generator-objects.mjs new file mode 100644 index 0000000..2b65ba7 --- /dev/null +++ b/engine262/src/abstract-ops/async-generator-objects.mjs @@ -0,0 +1,211 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Q, X, + Await, + Completion, + EnsureCompletion, + NormalCompletion, + AbruptCompletion, +} from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Value, Type } from '../value.mjs'; +import { resume, handleInResume } from '../helpers.mjs'; +import { + Assert, + Call, + CreateBuiltinFunction, + CreateIterResultObject, + GetGeneratorKind, + NewPromiseCapability, + PerformPromiseThen, + PromiseResolve, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 25.5 #sec-asyncgenerator-objects + +// 25.5.3.1 #sec-asyncgeneratorrequest-records +class AsyncGeneratorRequestRecord { + constructor(completion, promiseCapability) { + this.Completion = completion; + this.Capability = promiseCapability; + } +} + +// 25.5.3.2 #sec-asyncgeneratorstart +export function AsyncGeneratorStart(generator, generatorBody) { + // Assert: generator is an AsyncGenerator instance. + Assert(generator.AsyncGeneratorState === Value.undefined); + const genContext = surroundingAgent.runningExecutionContext; + genContext.Generator = generator; + genContext.codeEvaluationState = (function* resumer() { + const result = EnsureCompletion(yield* Evaluate(generatorBody)); + // Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return. + surroundingAgent.executionContextStack.pop(genContext); + generator.AsyncGeneratorState = 'completed'; + let resultValue; + if (result instanceof NormalCompletion) { + resultValue = Value.undefined; + } else { + resultValue = result.Value; + if (result.Type !== 'return') { + return X(AsyncGeneratorReject(generator, resultValue)); + } + } + return X(AsyncGeneratorResolve(generator, resultValue, Value.true)); + }()); + generator.AsyncGeneratorContext = genContext; + generator.AsyncGeneratorState = 'suspendedStart'; + generator.AsyncGeneratorQueue = []; + return Value.undefined; +} + +// 25.5.3.3 #sec-asyncgeneratorresolve +function AsyncGeneratorResolve(generator, value, done) { + // Assert: generator is an AsyncGenerator instance. + const queue = generator.AsyncGeneratorQueue; + Assert(queue.length > 0); + const next = queue.shift(); + const promiseCapability = next.Capability; + const iteratorResult = X(CreateIterResultObject(value, done)); + X(Call(promiseCapability.Resolve, Value.undefined, [iteratorResult])); + X(AsyncGeneratorResumeNext(generator)); + return Value.undefined; +} + +// 25.5.3.4 #sec-asyncgeneratorreject +function AsyncGeneratorReject(generator, exception) { + // Assert: generator is an AsyncGenerator instance. + const queue = generator.AsyncGeneratorQueue; + Assert(queue.length > 0); + const next = queue.shift(); + const promiseCapability = next.Capability; + X(Call(promiseCapability.Reject, Value.undefined, [exception])); + X(AsyncGeneratorResumeNext(generator)); + return Value.undefined; +} + +// 25.5.3.5.1 #async-generator-resume-next-return-processor-fulfilled +function AsyncGeneratorResumeNextReturnProcessorFulfilledFunctions([value = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + F.Generator.AsyncGeneratorState = 'completed'; + return X(AsyncGeneratorResolve(F.Generator, value, Value.true)); +} + +// 25.5.3.5.2 #async-generator-resume-next-return-processor-rejected +function AsyncGeneratorResumeNextReturnProcessorRejectedFunctions([reason = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + F.Generator.AsyncGeneratorState = 'completed'; + return X(AsyncGeneratorReject(F.Generator, reason)); +} + +// 25.5.3.5 #sec-asyncgeneratorresumenext +function AsyncGeneratorResumeNext(generator) { + // Assert: generator is an AsyncGenerator instance. + let state = generator.AsyncGeneratorState; + Assert(state !== 'executing'); + if (state === 'awaiting-return') { + return Value.undefined; + } + const queue = generator.AsyncGeneratorQueue; + if (queue.length === 0) { + return Value.undefined; + } + const next = queue[0]; + Assert(next instanceof AsyncGeneratorRequestRecord); + const completion = next.Completion; + if (completion instanceof AbruptCompletion) { + if (state === 'suspendedStart') { + generator.AsyncGeneratorState = 'completed'; + state = 'completed'; + } + if (state === 'completed') { + if (completion.Type === 'return') { + generator.AsyncGeneratorState = 'awaiting-return'; + const promise = Q(PromiseResolve(surroundingAgent.intrinsic('%Promise%'), completion.Value)); + const stepsFulfilled = AsyncGeneratorResumeNextReturnProcessorFulfilledFunctions; + const onFulfilled = X(CreateBuiltinFunction(stepsFulfilled, ['Generator'])); + onFulfilled.Generator = generator; + const stepsRejected = AsyncGeneratorResumeNextReturnProcessorRejectedFunctions; + const onRejected = X(CreateBuiltinFunction(stepsRejected, ['Generator'])); + onRejected.Generator = generator; + X(PerformPromiseThen(promise, onFulfilled, onRejected)); + return Value.undefined; + } else { + Assert(completion.Type === 'throw'); + X(AsyncGeneratorReject(generator, completion.Value)); + return Value.undefined; + } + } + } else if (state === 'completed') { + return X(AsyncGeneratorResolve(generator, Value.undefined, Value.true)); + } + Assert(state === 'suspendedStart' || state === 'suspendedYield'); + const genContext = generator.AsyncGeneratorContext; + const callerContext = surroundingAgent.runningExecutionContext; + // Suspend callerContext + generator.AsyncGeneratorState = 'executing'; + surroundingAgent.executionContextStack.push(genContext); + const result = resume(genContext, completion); + Assert(!(result instanceof AbruptCompletion)); + Assert(surroundingAgent.runningExecutionContext === callerContext); + return Value.undefined; +} + +// 25.5.3.6 #sec-asyncgeneratorenqueue +export function AsyncGeneratorEnqueue(generator, completion) { + Assert(completion instanceof Completion); + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + if (Type(generator) !== 'Object' || !('AsyncGeneratorState' in generator)) { + const badGeneratorError = surroundingAgent.Throw('TypeError', 'NotATypeObject', 'AsyncGenerator', generator).Value; + X(Call(promiseCapability.Reject, Value.undefined, [badGeneratorError])); + return promiseCapability.Promise; + } + const queue = generator.AsyncGeneratorQueue; + const request = new AsyncGeneratorRequestRecord(completion, promiseCapability); + queue.push(request); + const state = generator.AsyncGeneratorState; + if (state !== 'executing') { + X(AsyncGeneratorResumeNext(generator)); + } + return promiseCapability.Promise; +} + +// #sec-asyncgeneratoryield +export function* AsyncGeneratorYield(value) { + // 1. Let genContext be the running execution context. + const genContext = surroundingAgent.runningExecutionContext; + // 2. Assert: genContext is the execution context of a generator. + Assert(genContext.Generator !== Value.undefined); + // 3. Let generator be the value of the Generator component of genContext. + const generator = genContext.Generator; + // 4. Assert: GetGeneratorKind() is async. + Assert(GetGeneratorKind() === 'async'); + // 5. Set value to ? Await(value). + value = Q(yield* Await(value)); + // 6. Set generator.[[AsyncGeneratorState]] to suspendedYield. + generator.AsyncGeneratorState = 'suspendedYield'; + // 7. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + surroundingAgent.executionContextStack.pop(genContext); + // 8. Set the code evaluation state of genContext such that when evaluation is resumed with a Completion resumptionValue the following steps will be performed: + const resumptionValue = EnsureCompletion(yield handleInResume(AsyncGeneratorResolve, generator, value, Value.false)); + + // a. If resumptionValue.[[Type]] is not return, return Completion(resumptionValue). + if (resumptionValue.Type !== 'return') { + return Completion(resumptionValue); + } + // b. Let awaited be Await(resumptionValue.[[Value]]). + const awaited = EnsureCompletion(yield* Await(resumptionValue.Value)); + // c. If awaited.[[Type]] is throw, return Completion(awaited). + if (awaited.Type === 'Throw') { + return Completion(awaited); + } + // d. Assert: awaited.[[Type]] is normal. + Assert(awaited.Type === 'normal'); + // e. Return Completion { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }. + return new Completion({ Type: 'return', Value: awaited.Value, Target: undefined }); + // f. NOTE: When one of the above steps returns, it returns to the evaluation of the YieldExpression production that originally called this abstract operation. + + // 9. Return ! AsyncGeneratorResolve(generator, value, false). + // 10. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of genContext. +} diff --git a/engine262/src/abstract-ops/data-types-and-values.mjs b/engine262/src/abstract-ops/data-types-and-values.mjs new file mode 100644 index 0000000..374db02 --- /dev/null +++ b/engine262/src/abstract-ops/data-types-and-values.mjs @@ -0,0 +1,39 @@ +import { Type, Value } from '../value.mjs'; +import { X } from '../completion.mjs'; +import { CanonicalNumericIndexString } from './all.mjs'; + +// This file covers predicates defined in +// 6 #sec-ecmascript-data-types-and-values + +// 6.1.7 #integer-index +export function isIntegerIndex(V) { + if (Type(V) !== 'String') { + return false; + } + const numeric = X(CanonicalNumericIndexString(V)); + if (numeric === Value.undefined) { + return false; + } + if (Object.is(numeric.numberValue(), +0)) { + return true; + } + return numeric.numberValue() > 0 && Number.isSafeInteger(numeric.numberValue()); +} + +// 6.1.7 #array-index +export function isArrayIndex(V) { + if (Type(V) !== 'String') { + return false; + } + const numeric = X(CanonicalNumericIndexString(V)); + if (numeric === Value.undefined) { + return false; + } + if (!Number.isInteger(numeric.numberValue())) { + return false; + } + if (Object.is(numeric.numberValue(), +0)) { + return true; + } + return numeric.numberValue() > 0 && numeric.numberValue() < (2 ** 32) - 1; +} diff --git a/engine262/src/abstract-ops/dataview-objects.mjs b/engine262/src/abstract-ops/dataview-objects.mjs new file mode 100644 index 0000000..59d3e33 --- /dev/null +++ b/engine262/src/abstract-ops/dataview-objects.mjs @@ -0,0 +1,91 @@ +import { Q, X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + Assert, + GetValueFromBuffer, + IsDetachedBuffer, + IsBigIntElementType, + SetValueInBuffer, + ToBoolean, + ToIndex, + ToNumber, + ToBigInt, + RequireInternalSlot, + typedArrayInfoByType, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 24.3 #sec-dataview-objects + +// 24.3.1.1 #sec-getviewvalue +export function GetViewValue(view, requestIndex, isLittleEndian, type) { + // 1. Perform ? RequireInternalSlot(view, [[DataView]]). + Q(RequireInternalSlot(view, 'DataView')); + // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in view); + // 3. Let getIndex be ? ToIndex(requestIndex). + const getIndex = Q(ToIndex(requestIndex)).numberValue(); + // 4. Set isLittleEndian to ! ToBoolean(isLittleEndian). + isLittleEndian = X(ToBoolean(isLittleEndian)); + // 5. Let buffer be view.[[ViewedArrayBuffer]]. + const buffer = view.ViewedArrayBuffer; + // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 7. Let viewOffset be view.[[ByteOffset]]. + const viewOffset = view.ByteOffset.numberValue(); + // 8. Let viewSize be view.[[ByteLength]]. + const viewSize = view.ByteLength.numberValue(); + // 9. Let elementSize be the Element Size value specified in Table 61 for Element Type type. + const elementSize = typedArrayInfoByType[type].ElementSize; + // 10. If getIndex + elementSize > viewSize, throw a RangeError exception. + if (getIndex + elementSize > viewSize) { + return surroundingAgent.Throw('RangeError', 'DataViewOOB'); + } + // 11. Let bufferIndex be getIndex + viewOffset. + const bufferIndex = new Value(getIndex + viewOffset); + // 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian). + return GetValueFromBuffer(buffer, bufferIndex, type, Value.false, 'Unordered', isLittleEndian); +} + +// 24.3.1.2 #sec-setviewvalue +export function SetViewValue(view, requestIndex, isLittleEndian, type, value) { + // 1. Perform ? RequireInternalSlot(view, [[DataView]]). + Q(RequireInternalSlot(view, 'DataView')); + // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in view); + // 3. Let getIndex be ? ToIndex(requestIndex). + const getIndex = Q(ToIndex(requestIndex)).numberValue(); + // 4. If ! IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). + // 5. Otherwise, let numberValue be ? ToNumber(value). + let numberValue; + if (X(IsBigIntElementType(type)) === Value.true) { + numberValue = Q(ToBigInt(value)); + } else { + numberValue = Q(ToNumber(value)); + } + // 6. Set isLittleEndian to ! ToBoolean(isLittleEndian). + isLittleEndian = X(ToBoolean(isLittleEndian)); + // 7. Let buffer be view.[[ViewedArrayBuffer]]. + const buffer = view.ViewedArrayBuffer; + // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 9. Let viewOffset be view.[[ByteOffset]]. + const viewOffset = view.ByteOffset.numberValue(); + // 10. Let viewSize be view.[[ByteLength]]. + const viewSize = view.ByteLength.numberValue(); + // 11. Let elementSize be the Element Size value specified in Table 61 for Element Type type. + const elementSize = typedArrayInfoByType[type].ElementSize; + // 12. If getIndex + elementSize > viewSize, throw a RangeError exception. + if (getIndex + elementSize > viewSize) { + return surroundingAgent.Throw('RangeError', 'DataViewOOB'); + } + // 13. Let bufferIndex be getIndex + viewOffset. + const bufferIndex = new Value(getIndex + viewOffset); + // 14. Return SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian). + return SetValueInBuffer(buffer, bufferIndex, type, numberValue, Value.false, 'Unordered', isLittleEndian); +} diff --git a/engine262/src/abstract-ops/date-objects.mjs b/engine262/src/abstract-ops/date-objects.mjs new file mode 100644 index 0000000..b0d4427 --- /dev/null +++ b/engine262/src/abstract-ops/date-objects.mjs @@ -0,0 +1,233 @@ +// This file covers abstract operations defined in +// 20.3 #sec-date-objects + +import { + Value, +} from '../value.mjs'; +import { X } from '../completion.mjs'; +import { + ToInteger, +} from './all.mjs'; + +const mod = (n, m) => { + const r = n % m; + return Math.floor(r >= 0 ? r : r + m); +}; + +export const HoursPerDay = 24; +export const MinutesPerHour = 60; +export const SecondsPerMinute = 60; +export const msPerSecond = 1000; +export const msPerMinute = msPerSecond * SecondsPerMinute; +export const msPerHour = msPerMinute * MinutesPerHour; +export const msPerDay = msPerHour * HoursPerDay; + +// 20.3.1.2 #sec-day-number-and-time-within-day +export function Day(t) { + return new Value(Math.floor(t.numberValue() / msPerDay)); +} + +export function TimeWithinDay(t) { + return new Value(mod(t.numberValue(), msPerDay)); +} + +// 20.3.1.3 #sec-year-number +export function DaysInYear(y) { + y = y.numberValue(); + if (mod(y, 4) !== 0) { + return new Value(365); + } + if (mod(y, 4) === 0 && mod(y, 100) !== 0) { + return new Value(366); + } + if (mod(y, 100) === 0 && mod(y, 400) !== 0) { + return new Value(365); + } + if (mod(y, 400) === 0) { + return new Value(366); + } +} + +export function DayFromYear(y) { + y = y.numberValue(); + return new Value(365 * (y - 1970) + Math.floor((y - 1969) / 4) - Math.floor((y - 1901) / 100) + Math.floor((y - 1601) / 400)); +} + +export function TimeFromYear(y) { + return new Value(msPerDay * DayFromYear(y).numberValue()); +} + +export const msPerAverageYear = 12 * 30.436875 * msPerDay; + +export function YearFromTime(t) { + t = t.numberValue(); + let year = Math.floor((t + msPerAverageYear / 2) / msPerAverageYear) + 1970; + if (TimeFromYear(new Value(year)).numberValue() > t) { + year -= 1; + } + return new Value(year); +} + +export function InLeapYear(t) { + if (DaysInYear(YearFromTime(t)).numberValue() === 365) { + return new Value(0); + } + if (DaysInYear(YearFromTime(t)).numberValue() === 366) { + return new Value(1); + } +} + +// 20.3.1.4 #sec-month-number +export function MonthFromTime(t) { + const dayWithinYear = DayWithinYear(t).numberValue(); + const inLeapYear = InLeapYear(t).numberValue(); + if (dayWithinYear >= 0 && dayWithinYear < 31) { + return new Value(0); + } + if (dayWithinYear >= 31 && dayWithinYear < 59 + inLeapYear) { + return new Value(1); + } + if (dayWithinYear >= 59 + inLeapYear && dayWithinYear < 90 + inLeapYear) { + return new Value(2); + } + if (dayWithinYear >= 90 + inLeapYear && dayWithinYear < 120 + inLeapYear) { + return new Value(3); + } + if (dayWithinYear >= 120 + inLeapYear && dayWithinYear < 151 + inLeapYear) { + return new Value(4); + } + if (dayWithinYear >= 151 + inLeapYear && dayWithinYear < 181 + inLeapYear) { + return new Value(5); + } + if (dayWithinYear >= 181 + inLeapYear && dayWithinYear < 212 + inLeapYear) { + return new Value(6); + } + if (dayWithinYear >= 212 + inLeapYear && dayWithinYear < 243 + inLeapYear) { + return new Value(7); + } + if (dayWithinYear >= 243 + inLeapYear && dayWithinYear < 273 + inLeapYear) { + return new Value(8); + } + if (dayWithinYear >= 273 + inLeapYear && dayWithinYear < 304 + inLeapYear) { + return new Value(9); + } + if (dayWithinYear >= 304 + inLeapYear && dayWithinYear < 334 + inLeapYear) { + return new Value(10); + } + if (dayWithinYear >= 334 + inLeapYear && dayWithinYear < 365 + inLeapYear) { + return new Value(11); + } +} + +export function DayWithinYear(t) { + return new Value(Day(t).numberValue() - DayFromYear(YearFromTime(t)).numberValue()); +} + +// 20.3.1.5 #sec-date-number +export function DateFromTime(t) { + const dayWithinYear = DayWithinYear(t).numberValue(); + const monthFromTime = MonthFromTime(t).numberValue(); + const inLeapYear = InLeapYear(t).numberValue(); + switch (monthFromTime) { + case 0: return new Value(dayWithinYear + 1); + case 1: return new Value(dayWithinYear - 30); + case 2: return new Value(dayWithinYear - 58 - inLeapYear); + case 3: return new Value(dayWithinYear - 89 - inLeapYear); + case 4: return new Value(dayWithinYear - 119 - inLeapYear); + case 5: return new Value(dayWithinYear - 150 - inLeapYear); + case 6: return new Value(dayWithinYear - 180 - inLeapYear); + case 7: return new Value(dayWithinYear - 211 - inLeapYear); + case 8: return new Value(dayWithinYear - 242 - inLeapYear); + case 9: return new Value(dayWithinYear - 272 - inLeapYear); + case 10: return new Value(dayWithinYear - 303 - inLeapYear); + case 11: return new Value(dayWithinYear - 333 - inLeapYear); + default: // Unreachable + } +} + +// 20.3.1.6 #sec-week-day +export function WeekDay(t) { + return new Value(mod(Day(t).numberValue() + 4, 7)); +} + +// 20.3.1.7 #sec-local-time-zone-adjustment +export function LocalTZA(/* t, isUTC */) { + // TODO: implement this function properly. + return 0; +} + +// 20.3.1.8 #sec-localtime +export function LocalTime(t) { + return new Value(t.numberValue() + LocalTZA(t, true)); +} + +// 20.3.1.9 #sec-utc-t +export function UTC(t) { + return new Value(t.numberValue() - LocalTZA(t, false)); +} + +// 20.3.1.10 #sec-hours-minutes-second-and-milliseconds +export function HourFromTime(t) { + return new Value(mod(Math.floor(t.numberValue() / msPerHour), HoursPerDay)); +} + +export function MinFromTime(t) { + return new Value(mod(Math.floor(t.numberValue() / msPerMinute), MinutesPerHour)); +} + +export function SecFromTime(t) { + return new Value(mod(Math.floor(t.numberValue() / msPerSecond), SecondsPerMinute)); +} + +export function msFromTime(t) { + return new Value(mod(t.numberValue(), msPerSecond)); +} + +// 20.3.1.11 #sec-maketime +export function MakeTime(hour, min, sec, ms) { + if (!Number.isFinite(hour.numberValue()) || !Number.isFinite(min.numberValue()) || !Number.isFinite(sec.numberValue()) || !Number.isFinite(ms.numberValue())) { + return new Value(NaN); + } + const h = X(ToInteger(hour)).numberValue(); + const m = X(ToInteger(min)).numberValue(); + const s = X(ToInteger(sec)).numberValue(); + const milli = X(ToInteger(ms)).numberValue(); + const t = h * msPerHour + m * msPerMinute + s * msPerSecond + milli; + return new Value(t); +} + +const daysWithinYearToEndOfMonth = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + +// 20.3.1.12 #sec-makeday +export function MakeDay(year, month, date) { + if (!Number.isFinite(year.numberValue()) || !Number.isFinite(month.numberValue()) || !Number.isFinite(date.numberValue())) { + return new Value(NaN); + } + const y = X(ToInteger(year)).numberValue(); + const m = X(ToInteger(month)).numberValue(); + const dt = X(ToInteger(date)).numberValue(); + const ym = y + Math.floor(m / 12); + const mn = mod(m, 12); + const ymday = DayFromYear(new Value(ym + (mn > 1 ? 1 : 0))).numberValue() - 365 * (mn > 1 ? 1 : 0) + daysWithinYearToEndOfMonth[mn]; + const t = new Value(ymday * msPerDay); + return new Value(Day(t).numberValue() + dt - 1); +} + +// 20.3.1.13 #sec-makedate +export function MakeDate(day, time) { + if (!Number.isFinite(day.numberValue()) || !Number.isFinite(time.numberValue())) { + return new Value(NaN); + } + return new Value(day.numberValue() * msPerDay + time.numberValue()); +} + +// 20.3.1.14 #sec-timeclip +export function TimeClip(time) { + if (!Number.isFinite(time.numberValue())) { + return new Value(NaN); + } + if (Math.abs(time.numberValue()) > 8.64e15) { + return new Value(NaN); + } + return X(ToInteger(time)); +} diff --git a/engine262/src/abstract-ops/execution-contexts.mjs b/engine262/src/abstract-ops/execution-contexts.mjs new file mode 100644 index 0000000..98f8363 --- /dev/null +++ b/engine262/src/abstract-ops/execution-contexts.mjs @@ -0,0 +1,76 @@ +import { Q } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { + GetIdentifierReference, + EnvironmentRecord, +} from '../environment.mjs'; +import { Value } from '../value.mjs'; +import { Assert } from './all.mjs'; + +// This file covers abstract operations defined in +// 8.3 #sec-execution-contexts + +// 8.3.1 #sec-getactivescriptormodule +export function GetActiveScriptOrModule() { + for (let i = surroundingAgent.executionContextStack.length - 1; i >= 0; i -= 1) { + const e = surroundingAgent.executionContextStack[i]; + if (e.ScriptOrModule !== Value.null) { + return e.ScriptOrModule; + } + } + return Value.null; +} + +// 8.3.2 #sec-resolvebinding +export function ResolveBinding(name, env, strict) { + // 1. If env is not present or if env is undefined, then + if (env === undefined || env === Value.undefined) { + // a. Set env to the running execution context's LexicalEnvironment. + env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + } + // 2. Assert: env is an Environment Record. + Assert(env instanceof EnvironmentRecord); + // 3. If the code matching the syntactic production that is being evaluated is contained in strict mode code, let strict be true; else let strict be false. + // 4. Return ? GetIdentifierReference(env, name, strict). + return GetIdentifierReference(env, name, strict ? Value.true : Value.false); +} + +// #sec-getthisenvironment +export function GetThisEnvironment() { + // 1. Let env be the running execution context's LexicalEnvironment. + let env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Repeat, + while (true) { + // a. Let exists be env.HasThisBinding(). + const exists = env.HasThisBinding(); + // b. If exists is true, return envRec. + if (exists === Value.true) { + return env; + } + // c. Let outer be env.[[OuterEnv]]. + const outer = env.OuterEnv; + // d. Assert: outer is not null. + Assert(outer !== Value.null); + // e. Set env to outer. + env = outer; + } +} + +// 8.3.4 #sec-resolvethisbinding +export function ResolveThisBinding() { + const envRec = GetThisEnvironment(); + return Q(envRec.GetThisBinding()); +} + +// 8.3.5 #sec-getnewtarget +export function GetNewTarget() { + const envRec = GetThisEnvironment(); + Assert('NewTarget' in envRec); + return envRec.NewTarget; +} + +// 8.3.6 #sec-getglobalobject +export function GetGlobalObject() { + const currentRealm = surroundingAgent.currentRealmRecord; + return currentRealm.GlobalObject; +} diff --git a/engine262/src/abstract-ops/function-operations.mjs b/engine262/src/abstract-ops/function-operations.mjs new file mode 100644 index 0000000..11be4e4 --- /dev/null +++ b/engine262/src/abstract-ops/function-operations.mjs @@ -0,0 +1,449 @@ +import { + surroundingAgent, + ExecutionContext, +} from '../engine.mjs'; +import { + Descriptor, + Type, + Value, +} from '../value.mjs'; +import { + EnsureCompletion, + NormalCompletion, + ReturnIfAbrupt, + Q, X, +} from '../completion.mjs'; +import { ExpectedArgumentCount } from '../static-semantics/all.mjs'; +import { EvaluateBody } from '../runtime-semantics/all.mjs'; +import { + FunctionEnvironmentRecord, + GlobalEnvironmentRecord, + NewFunctionEnvironment, +} from '../environment.mjs'; +import { unwind } from '../helpers.mjs'; +import { + Assert, + DefinePropertyOrThrow, + GetActiveScriptOrModule, + HasOwnProperty, + IsConstructor, + IsExtensible, + IsInteger, + MakeBasicObject, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + ToObject, + isStrictModeCode, + Realm, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 9.2 #sec-ecmascript-function-objects +// 9.3 #sec-built-in-function-objects +// and +// 14.9 #sec-tail-position-calls + +export function isECMAScriptFunctionObject(O) { + return 'ECMAScriptCode' in O; +} + +export function isFunctionObject(O) { + return 'Call' in O; +} + +// #sec-prepareforordinarycall +export function PrepareForOrdinaryCall(F, newTarget) { + // 1. Assert: Type(newTarget) is Undefined or Object. + Assert(Type(newTarget) === 'Undefined' || Type(newTarget) === 'Object'); + // 2. Let callerContext be the running execution context. + // const callerContext = surroundingAgent.runningExecutionContext; + // 3. Let calleeContext be a new ECMAScript code execution context. + const calleeContext = new ExecutionContext(); + // 4. Set the Function of calleeContext to F. + calleeContext.Function = F; + // 5. Let calleeRealm be F.[[Realm]]. + const calleeRealm = F.Realm; + // 6. Set the Realm of calleeContext to calleeRealm. + calleeContext.Realm = calleeRealm; + // 7. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]]. + calleeContext.ScriptOrModule = F.ScriptOrModule; + // 8. Let localEnv be NewFunctionEnvironment(F, newTarget). + const localEnv = NewFunctionEnvironment(F, newTarget); + // 9. Set the LexicalEnvironment of calleeContext to localEnv. + calleeContext.LexicalEnvironment = localEnv; + // 10. Set the VariableEnvironment of calleeContext to localEnv. + calleeContext.VariableEnvironment = localEnv; + // 11. Set the VariableEnvironment of calleeContext to localEnv. + // 12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context. + surroundingAgent.executionContextStack.push(calleeContext); + // 13. NOTE: Any exception objects produced after this point are associated with calleeRealm. + // 14. Return calleeContext. + return calleeContext; +} + +// #sec-ordinarycallbindthis +export function OrdinaryCallBindThis(F, calleeContext, thisArgument) { + // 1. Let thisMode be F.[[ThisMode]]. + const thisMode = F.ThisMode; + // 2. If thisMode is lexical, return NormalCompletion(undefined). + if (thisMode === 'lexical') { + return NormalCompletion(Value.undefined); + } + // 3. Let calleeRealm be F.[[Realm]]. + const calleeRealm = F.Realm; + // 4. Let localEnv be the LexicalEnvironment of calleeContext. + const localEnv = calleeContext.LexicalEnvironment; + let thisValue; + // 5. If thisMode is strict, let thisValue be thisArgument. + if (thisMode === 'strict') { + thisValue = thisArgument; + } else { // 6. Else, + // a. If thisArgument is undefined or null, then + if (thisArgument === Value.undefined || thisArgument === Value.null) { + // i. Let globalEnv be calleeRealm.[[GlobalEnv]]. + const globalEnv = calleeRealm.GlobalEnv; + // ii. Assert: globalEnv is a global Environment Record. + Assert(globalEnv instanceof GlobalEnvironmentRecord); + // iii. Let thisValue be globalEnv.[[GlobalThisValue]]. + thisValue = globalEnv.GlobalThisValue; + } else { // b. Else, + // i. Let thisValue be ! ToObject(thisArgument). + thisValue = X(ToObject(thisArgument)); + // ii. NOTE: ToObject produces wrapper objects using calleeRealm. + } + } + // 7. Assert: localEnv is a function Environment Record. + Assert(localEnv instanceof FunctionEnvironmentRecord); + // 8. Assert: The next step never returns an abrupt completion because localEnv.[[ThisBindingStatus]] is not initialized. + Assert(localEnv.ThisBindingStatus !== 'initialized'); + // 10. Return localEnv.BindThisValue(thisValue). + return localEnv.BindThisValue(thisValue); +} + +// #sec-ordinarycallevaluatebody +export function OrdinaryCallEvaluateBody(F, argumentsList) { + // 1. Return the result of EvaluateBody of the parsed code that is F.[[ECMAScriptCode]] passing F and argumentsList as the arguments. + return EnsureCompletion(unwind(EvaluateBody(F.ECMAScriptCode, F, argumentsList))); +} + +// #sec-ecmascript-function-objects-call-thisargument-argumentslist +function FunctionCallSlot(thisArgument, argumentsList) { + const F = this; + + // 1. Assert: F is an ECMAScript function object. + Assert(isECMAScriptFunctionObject(F)); + // 2. If F.[[IsClassConstructor]] is true, throw a TypeError exception. + if (F.IsClassConstructor === Value.true) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', F); + } + // 3. Let callerContext be the running execution context. + // 4. Let calleeContext be PrepareForOrdinaryCall(F, undefined). + const calleeContext = PrepareForOrdinaryCall(F, Value.undefined); + // 5. Assert: calleeContext is now the running execution context. + Assert(surroundingAgent.runningExecutionContext === calleeContext); + // 6. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). + OrdinaryCallBindThis(F, calleeContext, thisArgument); + // 7. Let result be OrdinaryCallEvaluateBody(F, argumentsList). + const result = OrdinaryCallEvaluateBody(F, argumentsList); + // 8. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + // 9. If result.[[Type]] is return, return NormalCompletion(result.[[Value]]). + if (result.Type === 'return') { + return NormalCompletion(result.Value); + } + // 10. ReturnIfAbrupt(result). + ReturnIfAbrupt(result); + // 11. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} + +// 9.2.2 #sec-ecmascript-function-objects-construct-argumentslist-newtarget +function FunctionConstructSlot(argumentsList, newTarget) { + const F = this; + + // 1. Assert: F is an ECMAScript function object. + Assert(isECMAScriptFunctionObject(F)); + // 2. Assert: Type(newTarget) is Object. + Assert(Type(newTarget) === 'Object'); + // 3. Let callerContext be the running execution context. + // 4. Let kind be F.[[ConstructorKind]]. + const kind = F.ConstructorKind; + let thisArgument; + // 5. If kind is base, then + if (kind === 'base') { + // a. Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%"). + thisArgument = Q(OrdinaryCreateFromConstructor(newTarget, '%Object.prototype%')); + } + // 6. Let calleeContext be PrepareForOrdinaryCall(F, newTarget). + const calleeContext = PrepareForOrdinaryCall(F, newTarget); + // 7. Assert: calleeContext is now the running execution context. + Assert(surroundingAgent.runningExecutionContext === calleeContext); + surroundingAgent.runningExecutionContext.callSite.constructCall = true; + // 8. If kind is base, perform OrdinaryCallBindThis(F, calleeContext, thisArgument). + if (kind === 'base') { + OrdinaryCallBindThis(F, calleeContext, thisArgument); + } + // 9. Let constructorEnv be the LexicalEnvironment of calleeContext. + const constructorEnv = calleeContext.LexicalEnvironment; + // 10. Let result be OrdinaryCallEvaluateBody(F, argumentsList). + const result = OrdinaryCallEvaluateBody(F, argumentsList); + // 11. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + // 12. If result.[[Type]] is return, then + if (result.Type === 'return') { + // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]). + if (Type(result.Value) === 'Object') { + return NormalCompletion(result.Value); + } + // b. If kind is base, return NormalCompletion(thisArgument). + if (kind === 'base') { + return NormalCompletion(thisArgument); + } + // c. If result.[[Value]] is not undefined, throw a TypeError exception. + if (result.Value !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'DerivedConstructorReturnedNonObject'); + } + } else { // 13. Else, ReturnIfAbrupt(result). + ReturnIfAbrupt(result); + } + // 14. Return ? constructorEnv.GetThisBinding(). + return Q(constructorEnv.GetThisBinding()); +} + +// 9.2.3 #sec-functionallocate +export function OrdinaryFunctionCreate(functionPrototype, sourceText, ParameterList, Body, thisMode, Scope) { + Assert(Type(functionPrototype) === 'Object'); + const internalSlotsList = [ + 'Environment', + 'FormalParameters', + 'ECMAScriptCode', + 'ConstructorKind', + 'Realm', + 'ScriptOrModule', + 'ThisMode', + 'Strict', + 'HomeObject', + 'SourceText', + 'IsClassConstructor', + ]; + const F = X(OrdinaryObjectCreate(functionPrototype, internalSlotsList)); + F.Call = surroundingAgent.hostDefinedOptions.boost + ? surroundingAgent.hostDefinedOptions.boost.callFunction + : FunctionCallSlot; + F.SourceText = sourceText; + F.Environment = Scope; + F.FormalParameters = ParameterList; + F.ECMAScriptCode = Body; + const Strict = isStrictModeCode(Body); + F.Strict = Strict; + if (thisMode === 'lexical-this') { + F.ThisMode = 'lexical'; + } else if (Strict) { + F.ThisMode = 'strict'; + } else { + F.ThisMode = 'global'; + } + F.IsClassConstructor = Value.false; + F.Environment = Scope; + F.ScriptOrModule = GetActiveScriptOrModule(); + F.Realm = surroundingAgent.currentRealmRecord; + F.HomeObject = Value.undefined; + const len = ExpectedArgumentCount(ParameterList); + X(SetFunctionLength(F, new Value(len))); + return F; +} + +// 9.2.10 #sec-makeconstructor +export function MakeConstructor(F, writablePrototype, prototype) { + Assert(isECMAScriptFunctionObject(F)); + Assert(IsConstructor(F) === Value.false); + Assert(X(IsExtensible(F)) === Value.true && X(HasOwnProperty(F, new Value('prototype'))) === Value.false); + F.Construct = surroundingAgent.hostDefinedOptions.boost + ? surroundingAgent.hostDefinedOptions.boost.constructFunction + : FunctionConstructSlot; + F.ConstructorKind = 'base'; + if (writablePrototype === undefined) { + writablePrototype = Value.true; + } + if (prototype === undefined) { + prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(DefinePropertyOrThrow(prototype, new Value('constructor'), Descriptor({ + Value: F, + Writable: writablePrototype, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + X(DefinePropertyOrThrow(F, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: writablePrototype, + Enumerable: Value.false, + Configurable: Value.false, + }))); + return NormalCompletion(Value.undefined); +} + +// 9.2.11 #sec-makeclassconstructor +export function MakeClassConstructor(F) { + Assert(isECMAScriptFunctionObject(F)); + Assert(F.IsClassConstructor === Value.false); + F.IsClassConstructor = Value.true; + return NormalCompletion(Value.undefined); +} + +// 9.2.12 #sec-makemethod +export function MakeMethod(F, homeObject) { + Assert(isECMAScriptFunctionObject(F)); + Assert(Type(homeObject) === 'Object'); + F.HomeObject = homeObject; + return NormalCompletion(Value.undefined); +} + +// #sec-setfunctionname +export function SetFunctionName(F, name, prefix) { + // 1. Assert: F is an extensible object that does not have a "name" own property. + Assert(IsExtensible(F) === Value.true && HasOwnProperty(F, new Value('name')) === Value.false); + // 2. Assert: Type(name) is either Symbol or String. + Assert(Type(name) === 'Symbol' || Type(name) === 'String'); + // 3. Assert: If prefix is present, then Type(prefix) is String. + Assert(!prefix || Type(prefix) === 'String'); + // 4. If Type(name) is Symbol, then + if (Type(name) === 'Symbol') { + // a. Let description be name's [[Description]] value. + const description = name.Description; + // b. If description is undefined, set name to the empty String. + if (description === Value.undefined) { + name = new Value(''); + } else { + // c. Else, set name to the string-concatenation of "[", description, and "]". + name = new Value(`[${description.stringValue()}]`); + } + } + // 5. If F has an [[InitialName]] internal slot, then + if ('InitialName' in F) { + // a. Set F.[[InitialName]] to name. + F.InitialName = name; + } + // 6. If prefix is present, then + if (prefix !== undefined) { + // a. Set name to the string-concatenation of prefix, the code unit 0x0020 (SPACE), and name. + name = new Value(`${prefix.stringValue()} ${name.stringValue()}`); + // b. If F has an [[InitialName]] internal slot, then + if ('InitialName' in F) { + // i. Optionally, set F.[[InitialName]] to name. + } + } + // 7. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor { [[Value]]: name, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). + return X(DefinePropertyOrThrow(F, new Value('name'), Descriptor({ + Value: name, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); +} + +// 9.2.14 #sec-setfunctionlength +export function SetFunctionLength(F, length) { + Assert(IsExtensible(F) === Value.true && HasOwnProperty(F, new Value('length')) === Value.false); + Assert(Type(length) === 'Number'); + Assert(length.numberValue() >= 0 && X(IsInteger(length)) === Value.true); + return X(DefinePropertyOrThrow(F, new Value('length'), Descriptor({ + Value: length, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); +} + + +function nativeCall(F, argumentsList, thisArgument, newTarget) { + return F.nativeFunction(argumentsList, { + thisValue: thisArgument || Value.undefined, + NewTarget: newTarget || Value.undefined, + }); +} + +function BuiltinFunctionCall(thisArgument, argumentsList) { + const F = this; + + // const callerContext = surroundingAgent.runningExecutionContext; + // If callerContext is not already suspended, suspend callerContext. + const calleeContext = new ExecutionContext(); + calleeContext.Function = F; + const calleeRealm = F.Realm; + calleeContext.Realm = calleeRealm; + calleeContext.ScriptOrModule = F.ScriptOrModule; + // 8. Perform any necessary implementation-defined initialization of calleeContext. + surroundingAgent.executionContextStack.push(calleeContext); + const result = nativeCall(F, argumentsList, thisArgument, Value.undefined); + // Remove calleeContext from the execution context stack and + // restore callerContext as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + return result; +} + +function BuiltinFunctionConstruct(argumentsList, newTarget) { + const F = this; + + // const callerContext = surroundingAgent.runningExecutionContext; + // If callerContext is not already suspended, suspend callerContext. + const calleeContext = new ExecutionContext(); + calleeContext.Function = F; + const calleeRealm = F.Realm; + calleeContext.Realm = calleeRealm; + calleeContext.ScriptOrModule = F.ScriptOrModule; + // 8. Perform any necessary implementation-defined initialization of calleeContext. + surroundingAgent.executionContextStack.push(calleeContext); + surroundingAgent.runningExecutionContext.callSite.constructCall = true; + const result = nativeCall(F, argumentsList, undefined, newTarget); + // Remove calleeContext from the execution context stack and + // restore callerContext as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + return result; +} + +// 9.3.3 #sec-createbuiltinfunction +export function CreateBuiltinFunction(steps, internalSlotsList, realm, prototype, isConstructor = Value.false) { + // 1. Assert: steps is either a set of algorithm steps or other definition of a function's behaviour provided in this specification. + Assert(typeof steps === 'function'); + // 2. If realm is not present, set realm to the current Realm Record. + if (realm === undefined) { + realm = surroundingAgent.currentRealmRecord; + } + // 3. Assert: realm is a Realm Record. + Assert(realm instanceof Realm); + // 4. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]]. + if (prototype === undefined) { + prototype = realm.Intrinsics['%Function.prototype%']; + } + // 5. Let func be a new built-in function object that when called performs the action described by steps. The new function object has internal slots whose names are the elements of internalSlotsList. + const func = X(MakeBasicObject(['Prototype', 'Extensible', 'Realm', 'ScriptOrModule', 'InitialName'].concat(internalSlotsList))); + func.Call = BuiltinFunctionCall; + if (isConstructor === Value.true) { + func.Construct = BuiltinFunctionConstruct; + } + func.nativeFunction = steps; + // 6. Set func.[[Realm]] to realm. + func.Realm = realm; + // 7. Set func.[[Prototype]] to prototype. + func.Prototype = prototype; + // 8. Set func.[[Extensible]] to true. + func.Extensible = Value.true; + // 9. Set func.[[ScriptOrModule]] to null. + func.ScriptOrModule = Value.null; + // 10. Set func.[[InitialName]] to null. + func.InitialName = Value.null; + // 11. Return func. + return func; +} + +// 14.9.3 #sec-preparefortailcall +export function PrepareForTailCall() { + // 1. Let leafContext be the running execution context. + const leafContext = surroundingAgent.runningExecutionContext; + // 2. Suspend leafContext. + // 3. Pop leafContext from the execution context stack. The execution context now on the top of the stack becomes the running execution context. + surroundingAgent.executionContextStack.pop(leafContext); + // 4. Assert: leafContext has no further use. It will never be activated as the running execution context. + leafContext.poppedForTailCall = true; +} diff --git a/engine262/src/abstract-ops/generator-operations.mjs b/engine262/src/abstract-ops/generator-operations.mjs new file mode 100644 index 0000000..cffad6a --- /dev/null +++ b/engine262/src/abstract-ops/generator-operations.mjs @@ -0,0 +1,200 @@ +import { + Completion, + NormalCompletion, + Q, X, + EnsureCompletion, +} from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { resume } from '../helpers.mjs'; +import { + Assert, + CreateIterResultObject, + RequireInternalSlot, +} from './all.mjs'; + +// This file covers abstract operations defined in #sec-generator-objects + +// #sec-generatorstart +export function GeneratorStart(generator, generatorBody) { + // 1. Assert: The value of generator.[[GeneratorState]] is undefined. + Assert(generator.GeneratorState === Value.undefined); + // 2. Let genContext be the running execution context. + const genContext = surroundingAgent.runningExecutionContext; + // 3. Set the Generator component of genContext to generator. + genContext.Generator = generator; + // 4. Set the code evaluation state of genContext such that when evaluation is resumed + // for that execution context the following steps will be performed: + genContext.codeEvaluationState = (function* resumer() { + // a. Let result be the result of evaluating generatorBody. + const result = EnsureCompletion(yield* Evaluate(generatorBody)); + // b. Assert: If we return here, the generator either threw an exception or + // performed either an implicit or explicit return. + // c. Remove genContext from the execution context stack and restore the execution context + // that is at the top of the execution context stack as the running execution context. + surroundingAgent.executionContextStack.pop(genContext); + // d. Set generator.[[GeneratorState]] to completed. + generator.GeneratorState = 'completed'; + // e. Once a generator enters the completed state it never leaves it and its + // associated execution context is never resumed. Any execution state associated + // with generator can be discarded at this point. + genContext.codeEvaluationState = null; + // f. If result.[[Type]] is normal, let resultValue be undefined. + let resultValue; + if (result.Type === 'normal') { + resultValue = Value.undefined; + } else if (result.Type === 'return') { + // g. Else if result.[[Type]] is return, let resultValue be result.[[Value]]. + resultValue = result.Value; + } else { + // i. Assert: result.[[Type]] is throw. + Assert(result.Type === 'throw'); + // ii. Return Completion(result). + return Completion(result); + } + // i. Return CreateIterResultObject(resultValue, true). + return X(CreateIterResultObject(resultValue, Value.true)); + }()); + // 5. Set generator.[[GeneratorContext]] to genContext. + generator.GeneratorContext = genContext; + // 6. Set generator.[[GeneratorState]] to suspendedStart. + generator.GeneratorState = 'suspendedStart'; + // 7. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} + +// #sec-generatorvalidate +export function GeneratorValidate(generator) { + // 1. Perform ? RequireInternalSlot(generator, [[GeneratorState]]). + Q(RequireInternalSlot(generator, 'GeneratorState')); + // 2. Assert: generator also has a [[GeneratorContext]] internal slot. + Assert('GeneratorContext' in generator); + // 3. Let state be generator.[[GeneratorState]]. + const state = generator.GeneratorState; + // 4. If state is executing, throw a TypeError exception. + if (state === 'executing') { + return surroundingAgent.Throw('TypeError', 'GeneratorRunning'); + } + // 5. Return state. + return state; +} + +// #sec-generatorresume +export function GeneratorResume(generator, value) { + // 1. Let state be ? GeneratorValidate(generator). + const state = Q(GeneratorValidate(generator)); + // 2. If state is completed, return CreateIterResultObject(undefined, true). + if (state === 'completed') { + return X(CreateIterResultObject(Value.undefined, Value.true)); + } + // 3. Assert: state is either suspendedStart or suspendedYield. + Assert(state === 'suspendedStart' || state === 'suspendedYield'); + // 4. Let genContext be generator.[[GeneratorContext]]. + const genContext = generator.GeneratorContext; + // 5. Let methodContext be the running execution context. + // 6. Suspend methodContext. + const methodContext = surroundingAgent.runningExecutionContext; + // 7. Set generator.[[GeneratorState]] to executing. + generator.GeneratorState = 'executing'; + // 8. Push genContext onto the execution context stack. + surroundingAgent.executionContextStack.push(genContext); + // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as + // the result of the operation that suspended it. Let result be the value returned by + // the resumed computation. + const result = EnsureCompletion(resume(genContext, NormalCompletion(value))); + // 10. Assert: When we return here, genContext has already been removed from the execution + // context stack and methodContext is the currently running execution context. + Assert(surroundingAgent.runningExecutionContext === methodContext); + // 11. Return Completion(result). + return Completion(result); +} + +// #sec-generatorresumeabrupt +export function GeneratorResumeAbrupt(generator, abruptCompletion) { + // 1. Let state be ? GeneratorValidate(generator). + let state = Q(GeneratorValidate(generator)); + // 2. If state is suspendedStart, then + if (state === 'suspendedStart') { + // a. Set generator.[[GeneratorState]] to completed. + generator.GeneratorState = 'completed'; + // b. Once a generator enters the completed state it never leaves it and its + // associated execution context is never resumed. Any execution state associate + // with generator can be discarded at this point. + generator.GeneratorContext = null; + // c. Set state to completed. + state = 'completed'; + } + // 3. If state is completed, then + if (state === 'completed') { + // a. If abruptCompletion.[[Type]] is return, then + if (abruptCompletion.Type === 'return') { + // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true). + return X(CreateIterResultObject(abruptCompletion.Value, Value.true)); + } + // b. Return Completion(abruptCompletion). + return Completion(abruptCompletion); + } + // 4. Assert: state is suspendedYield. + Assert(state === 'suspendedYield'); + // 5. Let genContext be generator.[[GeneratorContext]]. + const genContext = generator.GeneratorContext; + // 6. Let methodContext be the running execution context. + // 7. Suspend methodContext. + const methodContext = surroundingAgent.runningExecutionContext; + // 8. Set generator.[[GeneratorState]] to executing. + generator.GeneratorState = 'executing'; + // 9. Push genContext onto the execution context stack. + surroundingAgent.executionContextStack.push(genContext); + // 10. Resume the suspended evaluation of genContext using abruptCompletion as the + // result of the operation that suspended it. Let result be the completion record + // returned by the resumed computation. + const result = EnsureCompletion(resume(genContext, abruptCompletion)); + // 11. Assert: When we return here, genContext has already been removed from the + // execution context stack and methodContext is the currently running execution context. + Assert(surroundingAgent.runningExecutionContext === methodContext); + // 12. Return Completion(result). + return Completion(result); +} + +// #sec-getgeneratorkind +export function GetGeneratorKind() { + // 1. Let genContext be the running execution context. + const genContext = surroundingAgent.runningExecutionContext; + // 2. If genContext does not have a Generator component, return non-generator. + if (!genContext.Generator) { + return 'non-generator'; + } + // 3. Let generator be the Generator component of genContext. + const generator = genContext.Generator; + // 4. If generator has an [[AsyncGeneratorState]] internal slot, return async. + if ('AsyncGeneratorState' in generator) { + return 'async'; + } + // 5. Else, return sync. + return 'sync'; +} + +// #sec-generatoryield +export function* GeneratorYield(iterNextObj) { + // 1. Assert: iterNextObj is an Object that implements the IteratorResult interface. + // 2. Let genContext be the running execution context. + const genContext = surroundingAgent.runningExecutionContext; + // 3. Assert: genContext is the execution context of a generator. + Assert(genContext.Generator !== undefined); + // 4. Let generator be the value of the Generator component of genContext. + const generator = genContext.Generator; + // 5. Assert: GetGeneratorKind is sync. + Assert(GetGeneratorKind() === 'sync'); + // 6. Set generator.GeneratorState to suspendedYield. + generator.GeneratorState = 'suspendedYield'; + // 7. Remove genContext from the execution context stack. + surroundingAgent.executionContextStack.pop(genContext); + // 8. Set the code evaluation state of genContext such that when evaluation is resumed with + // a Completion resumptionValue the following steps will be performed: + // a. Return resumptionValue + const resumptionValue = yield NormalCompletion(iterNextObj); + // 9. Return NormalCompletion(iterNextObj). + return resumptionValue; + // 10. NOTE: this returns to the evaluation of the operation that had most previously resumed evaluation of genContext. +} diff --git a/engine262/src/abstract-ops/global-object.mjs b/engine262/src/abstract-ops/global-object.mjs new file mode 100644 index 0000000..029a6b2 --- /dev/null +++ b/engine262/src/abstract-ops/global-object.mjs @@ -0,0 +1,332 @@ +import { ExecutionContext, HostEnsureCanCompileStrings, surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { InstantiateFunctionObject } from '../runtime-semantics/all.mjs'; +import { + IsStrict, + VarDeclaredNames, + VarScopedDeclarations, + LexicallyScopedDeclarations, + BoundNames, + IsConstantDeclaration, +} from '../static-semantics/all.mjs'; +import { + Completion, + AbruptCompletion, + NormalCompletion, + EnsureCompletion, + Q, X, +} from '../completion.mjs'; +import { wrappedParse } from '../parse.mjs'; +import { + NewDeclarativeEnvironment, + FunctionEnvironmentRecord, + GlobalEnvironmentRecord, + ObjectEnvironmentRecord, +} from '../environment.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { unwind, ValueSet } from '../helpers.mjs'; +import { Assert, GetThisEnvironment } from './all.mjs'; + +// This file covers abstract operations defined in +// 18 #sec-global-object + +// 2.1.1 #sec-performeval +export function PerformEval(x, callerRealm, strictCaller, direct) { + // 1. Assert: If direct is false, then strictCaller is also false. + if (direct === false) { + Assert(strictCaller === false); + } + // 2. If Type(x) is not String, return x. + if (Type(x) !== 'String') { + return x; + } + // 3. Let evalRealm be the current Realm Record. + const evalRealm = surroundingAgent.currentRealmRecord; + // 4. Perform ? HostEnsureCanCompileStrings(callerRealm, evalRealm). + Q(HostEnsureCanCompileStrings(callerRealm, evalRealm)); + // 5. Let inFunction be false. + let inFunction = false; + // 6. Let inMethod be false. + let inMethod = false; + // 7. Let inDerivedConstructor be false. + let inDerivedConstructor = false; + // 8. If direct is true, then + if (direct === true) { + // a. Let thisEnv be ! GetThisEnvironment(). + const thisEnv = X(GetThisEnvironment()); + // b. If thisEnv is a function Environment Record, then + if (thisEnv instanceof FunctionEnvironmentRecord) { + // i. Let F be thisEnv.[[FunctionObject]]. + const F = thisEnv.FunctionObject; + // ii. Let inFunction be true. + inFunction = true; + // iii. Let inMethod be thisEnv.HasSuperBinding(). + inMethod = thisEnv.HasSuperBinding() === Value.true; + // iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true. + if (F.ConstructorKind === 'derived') { + inDerivedConstructor = true; + } + } + } + // 9. Perform the following substeps in an implementation-dependent order, possibly interleaving parsing and error detection: + // a. Let script be the ECMAScript code that is the result of parsing ! UTF16DecodeString(x), for the goal symbol Script. + // b. If script Contains ScriptBody is false, return undefined. + // c. Let body be the ScriptBody of script. + // d. If inFunction is false, and body Contains NewTarget, throw a SyntaxError exception. + // e. If inMethod is false, and body Contains SuperProperty, throw a SyntaxError exception. + // f. If inDerivedConstructor is false, and body Contains SuperCall, throw a SyntaxError exception. + const script = wrappedParse({ source: x.stringValue() }, (parser) => parser.scope.with({ + strict: strictCaller, + newTarget: inFunction, + superProperty: inMethod, + superCall: inDerivedConstructor, + }, () => parser.parseScript())); + if (Array.isArray(script)) { + return surroundingAgent.Throw(script[0]); + } + if (!script.ScriptBody) { + return Value.undefined; + } + const body = script.ScriptBody; + // 10. If strictCaller is true, let strictEval be true. + // 11. Else, let strictEval be IsStrict of script. + let strictEval; + if (strictCaller === true) { + strictEval = true; + } else { + strictEval = IsStrict(script); + } + // 12. Let runningContext be the running execution context. + const runningContext = surroundingAgent.runningExecutionContext; + let lexEnv; + let varEnv; + // 13. NOTE: If direct is true, runningContext will be the execution context that performed the direct eval. + // If direct is false, runningContext will be the execution context for the invocation of the eval function. + // 14. If direct is true, then + if (direct === true) { + // a. Let lexEnv be NewDeclarativeEnvironment(runningContext's LexicalEnvironment). + lexEnv = NewDeclarativeEnvironment(runningContext.LexicalEnvironment); + // b. Let varEnv be runningContext's VariableEnvironment. + varEnv = runningContext.VariableEnvironment; + } else { // 15. Else, + // a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]). + lexEnv = NewDeclarativeEnvironment(evalRealm.GlobalEnv); + // b. Let varEnv be evalRealm.[[GlobalEnv]]. + varEnv = evalRealm.GlobalEnv; + } + // 16. If strictEval is true, set varEnv to lexEnv. + if (strictEval === true) { + varEnv = lexEnv; + } + // 17. If runningContext is not already suspended, suspend runningContext. + // 18. Let evalContext be a new ECMAScript code execution context. + const evalContext = new ExecutionContext(); + // 19. Set evalContext's Function to null. + evalContext.Function = Value.null; + // 20. Set evalContext's Realm to evalRealm. + evalContext.Realm = evalRealm; + // 21. Set evalContext's ScriptOrModule to runningContext's ScriptOrModule. + evalContext.ScriptOrModule = runningContext.ScriptOrModule; + // 22. Set evalContext's VariableEnvironment to varEnv. + evalContext.VariableEnvironment = varEnv; + // 23. Set evalContext's LexicalEnvironment to lexEnv. + evalContext.LexicalEnvironment = lexEnv; + // 24. Push evalContext onto the execution context stack. + surroundingAgent.executionContextStack.push(evalContext); + // 25. Let result be EvalDeclarationInstantiation(body, varEnv, lexEnv, strictEval). + let result = EnsureCompletion(EvalDeclarationInstantiation(body, varEnv, lexEnv, strictEval)); + // 26. If result.[[Type]] is normal, then + if (result.Type === 'normal') { + // a. Set result to the result of evaluating body. + result = EnsureCompletion(unwind(Evaluate(body))); + } + // 27. If result.[[Type]] is normal and result.[[Value]] is empty, then + if (result.Type === 'normal' && result.Value === undefined) { + // a. Set result to NormalCompletion(undefined). + result = NormalCompletion(Value.undefined); + } + // 28. Suspend evalContext and remove it from the execution context stack. + // 29. Resume the context that is now on the top of the execution context stack as the running execution context. + surroundingAgent.executionContextStack.pop(evalContext); + // 30. Return Completion(result). + return Completion(result); +} + +// 18.2.1.3 #sec-evaldeclarationinstantiation +function EvalDeclarationInstantiation(body, varEnv, lexEnv, strict) { + // 1. Let varNames be the VarDeclaredNames of body. + const varNames = VarDeclaredNames(body); + // 2. Let varDeclarations be the VarScopedDeclarations of body. + const varDeclarations = VarScopedDeclarations(body); + // 3. If strict is false, then + if (strict === false) { + // a. If varEnv is a global Environment Record, then + if (varEnv instanceof GlobalEnvironmentRecord) { + // i. For each name in varNames, do + for (const name of varNames) { + // 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if (varEnv.HasLexicalDeclaration(name) === Value.true) { + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + } + // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration. + } + } + // b. Let thisLex be lexEnv. + let thisEnv = lexEnv; + // c. Assert: The following loop will terminate. + // d. Repeat, while thisEnv is not the same as varEnv, + while (thisEnv !== varEnv) { + // i. If thisEnv is not an object Environment Record, then + if (!(thisEnv instanceof ObjectEnvironmentRecord)) { + // 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts. + // 2. For each name in varNames, do + for (const name of varNames) { + // a. If thisEnv.HasBinding(name) is true, then + if (thisEnv.HasBinding(name) === Value.true) { + // i. Throw a SyntaxError exception. + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + // ii. NOTE: Annex B.3.5 defines alternate semantics for the above step. + } + // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration + } + } + // ii. Set thisEnv to thisEnv.[[OuterEnv]]. + thisEnv = thisEnv.OuterEnv; + } + } + // 4. Let functionsToInitialize be a new empty List. + const functionsToInitialize = []; + // 5. Let declaredFunctionNames be a new empty List. + const declaredFunctionNames = new ValueSet(); + // 6. For each d in varDeclarations, in reverse list order, do + for (const d of [...varDeclarations].reverse()) { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + if (d.type !== 'VariableDeclaration' + && d.type !== 'ForBinding' + && d.type !== 'BindingIdentifier') { + // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + Assert(d.type === 'FunctionDeclaration' + || d.type === 'GeneratorDeclaration' + || d.type === 'AsyncFunctionDeclaration' + || d.type === 'AsyncGeneratorDeclaration'); + // ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + // iii. Let fn be the sole element of the BoundNames of d. + const fn = BoundNames(d)[0]; + // iv. If fn is not an element of declaredFunctionNames, then + if (!declaredFunctionNames.has(fn)) { + // 1. If varEnv is a global Environment Record, then + if (varEnv instanceof GlobalEnvironmentRecord) { + // a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn). + const fnDefinable = Q(varEnv.CanDeclareGlobalFunction(fn)); + // b. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn). + if (fnDefinable === Value.false) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', fn); + } + } + // 2. Append fn to declaredFunctionNames. + declaredFunctionNames.add(fn); + // 3. Insert d as the first element of functionsToInitialize. + functionsToInitialize.unshift(d); + } + } + } + // 7. NOTE: Annex B.3.3.3 adds additional steps at this point. + // 8. Let declaredVarNames be a new empty List. + const declaredVarNames = new ValueSet(); + // 9. For each d in varDeclarations, do + for (const d of varDeclarations) { + // a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + if (d.type === 'VariableDeclaration' + || d.type === 'ForBinding' + || d.type === 'BindingIdentifier') { + // i. For each String vn in the BoundNames of d, do + for (const vn of BoundNames(d)) { + // 1. If vn is not an element of declaredFunctionNames, then + if (!declaredFunctionNames.has(vn)) { + // a. If varEnv is a global Environment Record, then + if (varEnv instanceof GlobalEnvironmentRecord) { + // i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn). + const vnDefinable = Q(varEnv.CanDeclareGlobalVar(vn)); + // ii. If vnDefinable is false, throw a TypeError exception. + if (vnDefinable === Value.false) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', vn); + } + } + // b. If vn is not an element of declaredVarNames, then + if (!declaredVarNames.has(vn)) { + // i. Append vn to declaredVarNames. + declaredVarNames.add(vn); + } + } + } + } + } + // 10. NOTE: No abnormal terminations occur after this algorithm step unless + // varEnv is a global Environment Record and the global object is a Proxy exotic object. + // 11. Let lexDeclarations be the LexicallyScopedDeclarations of body. + const lexDeclarations = LexicallyScopedDeclarations(body); + // 12. For each element d in lexDeclarations, do + for (const d of lexDeclarations) { + // a. NOTE: Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + for (const dn of BoundNames(d)) { + // i. If IsConstantDeclaration of d is true, then + if (IsConstantDeclaration(d)) { + // 1. Perform ? lexEnv.CreateImmutableBinding(dn, true). + Q(lexEnv.CreateImmutableBinding(dn, Value.true)); + } else { // ii. Else, + // 1. Perform ? lexEnv.CreateMutableBinding(dn, false). + Q(lexEnv.CreateMutableBinding(dn, Value.false)); + } + } + } + // 13. For each Parse Node f in functionsToInitialize, do + for (const f of functionsToInitialize) { + // a. Let fn be the sole element of the BoundNames of f. + const fn = BoundNames(f)[0]; + // b. Let fn be the sole element of the BoundNames of f. + const fo = InstantiateFunctionObject(f, lexEnv); + // c. If varEnv is a global Environment Record, then + if (varEnv instanceof GlobalEnvironmentRecord) { + // i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true). + Q(varEnv.CreateGlobalFunctionBinding(fn, fo, Value.true)); + } else { // d. Else, + // i. Let bindingExists be varEnv.HasBinding(fn). + const bindingExists = varEnv.HasBinding(fn); + // ii. If bindingExists is false, then + if (bindingExists === Value.false) { + // 1. Let status be ! varEnv.CreateMutableBinding(fn, true). + const status = X(varEnv.CreateMutableBinding(fn, Value.true)); + // 2. Assert: status is not an abrupt completion because of validation preceding step 12. + Assert(!(status instanceof AbruptCompletion)); + // 3. Perform ! varEnv.InitializeBinding(fn, fo). + X(varEnv.InitializeBinding(fn, fo)); + } else { // iii. Else, + // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). + X(varEnv.SetMutableBinding(fn, fo, Value.false)); + } + } + } + // 14. For each String vn in declaredVarNames, in list order, do + for (const vn of declaredVarNames) { + // a. If varEnv is a global Environment Record, then + if (varEnv instanceof GlobalEnvironmentRecord) { + // i. Perform ? varEnv.CreateGlobalVarBinding(vn, true). + Q(varEnv.CreateGlobalVarBinding(vn, Value.true)); + } else { // b. Else, + // i. Let bindingExists be varEnv.HasBinding(vn). + const bindingExists = varEnv.HasBinding(vn); + // ii. If bindingExists is false, then + if (bindingExists === Value.false) { + // 1. Let status be ! varEnv.CreateMutableBinding(vn, true). + const status = X(varEnv.CreateMutableBinding(vn, Value.true)); + // 2. Assert: status is not an abrupt completion because of validation preceding step 12. + Assert(!(status instanceof AbruptCompletion)); + // 3. Perform ! varEnv.InitializeBinding(vn, undefined). + X(varEnv.InitializeBinding(vn, Value.undefined)); + } + } + } + // 15. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/abstract-ops/immutable-prototype-objects.mjs b/engine262/src/abstract-ops/immutable-prototype-objects.mjs new file mode 100644 index 0000000..f453265 --- /dev/null +++ b/engine262/src/abstract-ops/immutable-prototype-objects.mjs @@ -0,0 +1,17 @@ +import { Type, Value } from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { Assert, SameValue } from './all.mjs'; + +// #sec-set-immutable-prototype +export function SetImmutablePrototype(O, V) { + // 1. Assert: Either Type(V) is Object or Type(V) is Null. + Assert(Type(V) === 'Object' || Type(V) === 'Null'); + // 2. Let current be ? O.[[GetPrototypeOf]](). + const current = Q(O.GetPrototypeOf()); + // 3. If SameValue(V, current) is true, return true. + if (SameValue(V, current) === Value.true) { + return Value.true; + } + // 4. Return false. + return Value.false; +} diff --git a/engine262/src/abstract-ops/integer-indexed-objects.mjs b/engine262/src/abstract-ops/integer-indexed-objects.mjs new file mode 100644 index 0000000..b8c1743 --- /dev/null +++ b/engine262/src/abstract-ops/integer-indexed-objects.mjs @@ -0,0 +1,314 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Type, Descriptor } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { + Assert, + CanonicalNumericIndexString, + IsAccessorDescriptor, + IsDetachedBuffer, + IsPropertyKey, + IsValidIntegerIndex, + MakeBasicObject, + OrdinaryGetOwnProperty, + OrdinaryHasProperty, + OrdinaryDefineOwnProperty, + OrdinaryGet, + OrdinarySet, + GetValueFromBuffer, + SetValueInBuffer, + ToString, + ToNumber, + ToBigInt, + isIntegerIndex, + typedArrayInfoByName, +} from './all.mjs'; + +export function isIntegerIndexedExoticObject(O) { + return O.GetOwnProperty === IntegerIndexedGetOwnProperty; +} + +// 9.4.5.1 #sec-integer-indexed-exotic-objects-getownproperty-p +export function IntegerIndexedGetOwnProperty(P) { + const O = this; + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. Assert: O is an Integer-Indexed exotic object. + Assert(isIntegerIndexedExoticObject(O)); + // 3. If Type(P) is String, then + if (Type(P) === 'String') { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + const numericIndex = X(CanonicalNumericIndexString(P)); + // b. If numericIndex is not undefined, then + if (numericIndex !== Value.undefined) { + // i. Let value be ? IntegerIndexedElementGet(O, numericIndex). + const value = Q(IntegerIndexedElementGet(O, numericIndex)); + // ii. If value is undefined, return undefined. + if (value === Value.undefined) { + return Value.undefined; + } + // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }. + return Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.false, + }); + } + } + // 4. Return OrdinaryGetOwnProperty(O, P). + return OrdinaryGetOwnProperty(O, P); +} + +// 9.4.5.2 #sec-integer-indexed-exotic-objects-hasproperty-p +export function IntegerIndexedHasProperty(P) { + const O = this; + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. Assert: O is an Integer-Indexed exotic object. + Assert(isIntegerIndexedExoticObject(O)); + // 3. If Type(P) is String, then + if (Type(P) === 'String') { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + const numericIndex = X(CanonicalNumericIndexString(P)); + // b. If numericIndex is not undefined, then + if (numericIndex !== Value.undefined) { + // i. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // ii. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // iii. If ! IsValidIntegerIndex(O, numericIndex) is false, return false. + if (IsValidIntegerIndex(O, numericIndex) === Value.false) { + return Value.false; + } + // iv. Return true. + return Value.true; + } + } + // 4. Return ? OrdinaryHasProperty(O, P) + return Q(OrdinaryHasProperty(O, P)); +} + +// #sec-integer-indexed-exotic-objects-defineownproperty-p-desc +export function IntegerIndexedDefineOwnProperty(P, Desc) { + const O = this; + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. Assert: O is an Integer-Indexed exotic object. + Assert(isIntegerIndexedExoticObject(O)); + // 3. If Type(P) is String, then + if (Type(P) === 'String') { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + const numericIndex = X(CanonicalNumericIndexString(P)); + // b. If numericIndex is not undefined, then + if (numericIndex !== Value.undefined) { + // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false. + if (IsValidIntegerIndex(O, numericIndex) === Value.false) { + return Value.false; + } + // ii. If IsAccessorDescriptor(Desc) is true, return false. + if (IsAccessorDescriptor(Desc)) { + return Value.false; + } + // iii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is true, return false. + if (Desc.Configurable === Value.true) { + return Value.false; + } + // iv. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] is false, return false. + if (Desc.Enumerable === Value.false) { + return Value.false; + } + // v. If Desc has a [[Writable]] field and if Desc.[[Writable]] is false, return false. + if (Desc.Writable === Value.false) { + return Value.false; + } + // vi. If Desc has a [[Value]] field, then + if (Desc.Value !== undefined) { + // 1. Let value be Desc.[[Value]]. + const value = Desc.Value; + // 2. Return ? IntegerIndexedElementSet(O, numericIndex, value). + return Q(IntegerIndexedElementSet(O, numericIndex, value)); + } + // vii. Return true. + return Value.true; + } + } + // 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc). + return Q(OrdinaryDefineOwnProperty(O, P, Desc)); +} + +// 9.4.5.4 #sec-integer-indexed-exotic-objects-get-p-receiver +export function IntegerIndexedGet(P, Receiver) { + const O = this; + // 1. Assert: IsPropertykey(P) is true. + Assert(IsPropertyKey(P)); + // 2. If Type(P) is String, then + if (Type(P) === 'String') { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + const numericIndex = X(CanonicalNumericIndexString(P)); + // b. If numericIndex is not undefined, then + if (numericIndex !== Value.undefined) { + // i. Return ? IntegerIndexedElementGet(O, numericIndex). + return Q(IntegerIndexedElementGet(O, numericIndex)); + } + } + // 3. Return ? OrdinaryGet(O, P, Receiver). + return Q(OrdinaryGet(O, P, Receiver)); +} + +// 9.4.5.5 #sec-integer-indexed-exotic-objects-set-p-v-receiver +export function IntegerIndexedSet(P, V, Receiver) { + const O = this; + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. If Type(P) is String, then + if (Type(P) === 'String') { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + const numericIndex = X(CanonicalNumericIndexString(P)); + // b. If numericIndex is not undefined, then + if (numericIndex !== Value.undefined) { + // i. Return ? IntegerIndexedElementSet(O, numericIndex, V). + return Q(IntegerIndexedElementSet(O, numericIndex, V)); + } + } + // 3. Return ? OrdinarySet(O, P, V, Receiver). + return Q(OrdinarySet(O, P, V, Receiver)); +} + +// 9.4.5.6 #sec-integer-indexed-exotic-objects-ownpropertykeys +export function IntegerIndexedOwnPropertyKeys() { + const O = this; + // 1. Let keys be a new empty List. + const keys = []; + // 2. Assert: O is an Integer-Indexed exotic object. + Assert(isIntegerIndexedExoticObject(O)); + // 3. Let len be O.[[ArrayLength]]. + const len = O.ArrayLength.numberValue(); + // 4. For each integer i starting with 0 such that i < len, in ascending order, do + for (let i = 0; i < len; i += 1) { + // a. Add ! ToString(i) as the last element of keys. + keys.push(X(ToString(new Value(i)))); + } + // 5. For each own property key P of O such that Type(P) is String and P is not an integer index, in ascending chronological order of property creation, do + for (const P of O.properties.keys()) { + if (Type(P) === 'String') { + if (!isIntegerIndex(P)) { + // a. Add P as the last element of keys. + keys.push(P); + } + } + } + // 6. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do + for (const P of O.properties.keys()) { + if (Type(P) === 'Symbol') { + // a. Add P as the last element of keys. + keys.push(P); + } + } + // 7. Return keys. + return keys; +} + +// #sec-integerindexedelementget +export function IntegerIndexedElementGet(O, index) { + // 1. Assert: O is an Integer-Indexed exotic object. + Assert(isIntegerIndexedExoticObject(O)); + // 2. Assert: Type(index) is Number. + Assert(Type(index) === 'Number'); + // 3. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. If ! IsValidIntegerIndex(O, index) is false, return undefined. + if (IsValidIntegerIndex(O, index) === Value.false) { + return Value.undefined; + } + // 6. Let offset be O.[[ByteOffset]]. + const offset = O.ByteOffset; + // 7. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + const arrayTypeName = O.TypedArrayName.stringValue(); + // 8. Let elementSize be the Element Size value specified in Table 61 for arrayTypeName. + const elementSize = typedArrayInfoByName[arrayTypeName].ElementSize; + // 9. Let indexedPosition be (index × elementSize) + offset. + const indexedPosition = new Value((index.numberValue() * elementSize) + offset.numberValue()); + // 10. Let elementType be the Element Type value in Table 61 for arrayTypeName. + const elementType = typedArrayInfoByName[arrayTypeName].ElementType; + // 11. Return GetValueFromBuffer(buffer, indexedPosition, elementType, true, Unordered). + return GetValueFromBuffer(buffer, indexedPosition, elementType, Value.true, 'Unordered'); +} + +// #sec-integerindexedelementset +export function IntegerIndexedElementSet(O, index, value) { + // 1. Assert: O is an Integer-Indexed exotic object. + Assert(isIntegerIndexedExoticObject(O)); + // 2. Assert: Type(index) is Number. + Assert(Type(index) === 'Number'); + // 3. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). + // 4. Otherwise, let numValue be ? ToNumber(value). + let numValue; + if (O.ContentType === 'BigInt') { + numValue = Q(ToBigInt(value)); + } else { + numValue = Q(ToNumber(value)); + } + // 5. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 7. If ! IsValidIntegerIndex(O, index) is false, return false. + if (IsValidIntegerIndex(O, index) === Value.false) { + return Value.false; + } + // 8. Let offset be O.[[ByteOffset]]. + const offset = O.ByteOffset; + // 9. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + const arrayTypeName = O.TypedArrayName.stringValue(); + // 10. Let elementSize be the Element Size value specified in Table 61 for arrayTypeName. + const elementSize = typedArrayInfoByName[arrayTypeName].ElementSize; + // 11. Let indexedPosition be (index × elementSize) + offset. + const indexedPosition = new Value((index.numberValue() * elementSize) + offset.numberValue()); + // 12. Let elementType be the Element Type value in Table 61 for arrayTypeName. + const elementType = typedArrayInfoByName[arrayTypeName].ElementType; + // 13. Perform SetValueInBuffer(buffer, indexedPosition, elementType, numValue, true, Unordered). + X(SetValueInBuffer(buffer, indexedPosition, elementType, numValue, Value.true, 'Unordered')); + // 14. Return true. + return Value.true; +} + +// #sec-integerindexedobjectcreate +export function IntegerIndexedObjectCreate(prototype) { + // 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]], [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]], [[ArrayLength]] ». + const internalSlotsList = [ + 'Prototype', + 'Extensible', + 'ViewedArrayBuffer', + 'TypedArrayName', + 'ContentType', + 'ByteLength', + 'ByteOffset', + 'ArrayLength', + ]; + // 2. Let A be ! MakeBasicObject(internalSlotsList). + const A = X(MakeBasicObject(internalSlotsList)); + // 3. Set A.[[GetOwnProperty]] as specified in 9.4.5.1. + A.GetOwnProperty = IntegerIndexedGetOwnProperty; + // 4. Set A.[[HasProperty]] as specified in 9.4.5.2. + A.HasProperty = IntegerIndexedHasProperty; + // 5. Set A.[[DefineOwnProperty]] as specified in 9.4.5.3. + A.DefineOwnProperty = IntegerIndexedDefineOwnProperty; + // 6. Set A.[[Get]] as specified in 9.4.5.4. + A.Get = IntegerIndexedGet; + // 7. Set A.[[Set]] as specified in 9.4.5.5. + A.Set = IntegerIndexedSet; + // 8. Set A.[[OwnPropertyKeys]] as specified in 9.4.5.6. + A.OwnPropertyKeys = IntegerIndexedOwnPropertyKeys; + // 9. Set A.[[Prototype]] to prototype. + A.Prototype = prototype; + // 10. Return A. + return A; +} diff --git a/engine262/src/abstract-ops/iterator-operations.mjs b/engine262/src/abstract-ops/iterator-operations.mjs new file mode 100644 index 0000000..e8005d6 --- /dev/null +++ b/engine262/src/abstract-ops/iterator-operations.mjs @@ -0,0 +1,255 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + Completion, + EnsureCompletion, + IfAbruptRejectPromise, + Q, X, + Await, +} from '../completion.mjs'; +import { + Assert, + Call, + CreateBuiltinFunction, + CreateDataProperty, + Get, + GetMethod, + GetV, + PromiseResolve, + OrdinaryObjectCreate, + PerformPromiseThen, + ToBoolean, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 7.4 #sec-operations-on-iterator-objects +// and +// 25.1 #sec-iteration + +// 7.4.1 #sec-getiterator +export function GetIterator(obj, hint, method) { + if (!hint) { + hint = 'sync'; + } + Assert(hint === 'sync' || hint === 'async'); + if (!method) { + if (hint === 'async') { + method = Q(GetMethod(obj, wellKnownSymbols.asyncIterator)); + if (method === Value.undefined) { + const syncMethod = Q(GetMethod(obj, wellKnownSymbols.iterator)); + const syncIteratorRecord = Q(GetIterator(obj, 'sync', syncMethod)); + return Q(CreateAsyncFromSyncIterator(syncIteratorRecord)); + } + } else { + method = Q(GetMethod(obj, wellKnownSymbols.iterator)); + } + } + const iterator = Q(Call(method, obj)); + if (Type(iterator) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', iterator); + } + const nextMethod = Q(GetV(iterator, new Value('next'))); + const iteratorRecord = { + Iterator: iterator, + NextMethod: nextMethod, + Done: Value.false, + }; + return EnsureCompletion(iteratorRecord); +} + +// 7.4.2 #sec-iteratornext +export function IteratorNext(iteratorRecord, value) { + let result; + if (!value) { + result = Q(Call(iteratorRecord.NextMethod, iteratorRecord.Iterator)); + } else { + result = Q(Call(iteratorRecord.NextMethod, iteratorRecord.Iterator, [value])); + } + if (Type(result) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', result); + } + return EnsureCompletion(result); +} + +// 7.4.3 #sec-iteratorcomplete +export function IteratorComplete(iterResult) { + Assert(Type(iterResult) === 'Object'); + return EnsureCompletion(ToBoolean(Q(Get(iterResult, new Value('done'))))); +} + +// 7.4.4 #sec-iteratorvalue +export function IteratorValue(iterResult) { + Assert(Type(iterResult) === 'Object'); + return EnsureCompletion(Q(Get(iterResult, new Value('value')))); +} + +// 7.4.5 #sec-iteratorstep +export function IteratorStep(iteratorRecord) { + const result = Q(IteratorNext(iteratorRecord)); + const done = Q(IteratorComplete(result)); + if (done === Value.true) { + return EnsureCompletion(Value.false); + } + return EnsureCompletion(result); +} + +// #sec-iteratorclose +export function IteratorClose(iteratorRecord, completion) { + // 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object. + Assert(Type(iteratorRecord.Iterator) === 'Object'); + // 2. Assert: completion is a Completion Record. + // TODO: completion should be a Completion Record so this should not be necessary + completion = EnsureCompletion(completion); + Assert(completion instanceof Completion); + // 3. Let iterator be iteratorRecord.[[Iterator]]. + const iterator = iteratorRecord.Iterator; + // 4. Let innerResult be GetMethod(iterator, "return"). + let innerResult = EnsureCompletion(GetMethod(iterator, new Value('return'))); + // 5. If innerResult.[[Type]] is normal, then + if (innerResult.Type === 'normal') { + // a. Let return be innerResult.[[Value]]. + const ret = innerResult.Value; + // b. If return is undefined, return Completion(completion). + if (ret === Value.undefined) { + return Completion(completion); + } + // c. Set innerResult to Call(return, iterator). + innerResult = Call(ret, iterator); + } + // 6. If completion.[[Type]] is throw, return Completion(completion). + if (completion.Type === 'throw') { + return Completion(completion); + } + // 7. If innerResult.[[Type]] is throw, return Completion(innerResult). + if (innerResult.Type === 'throw') { + return Completion(innerResult); + } + // 8. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception. + if (Type(innerResult.Value) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult.Value); + } + // 9. Return Completion(completion). + return Completion(completion); +} + +// #sec-asynciteratorclose +export function* AsyncIteratorClose(iteratorRecord, completion) { + // 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object. + Assert(Type(iteratorRecord.Iterator) === 'Object'); + // 2. Assert: completion is a Completion Record. + Assert(completion instanceof Completion); + // 3. Let iterator be iteratorRecord.[[Iterator]]. + const iterator = iteratorRecord.Iterator; + // 4. Let innerResult be GetMethod(iterator, "return"). + let innerResult = EnsureCompletion(GetMethod(iterator, new Value('return'))); + // 5. If innerResult.[[Type]] is normal, then + if (innerResult.Type === 'normal') { + // a. Let return be innerResult.[[Value]]. + const ret = innerResult.Value; + // b. If return is undefined, return Completion(completion). + if (ret === Value.undefined) { + return Completion(completion); + } + // c. Set innerResult to Call(return, iterator). + innerResult = Call(ret, iterator); + // d. If innerResult.[[Type]] is normal, set innerResult to Await(innerResult.[[Value]]). + if (innerResult.Type === 'normal') { + innerResult = EnsureCompletion(yield* Await(innerResult.Value)); + } + } + // 6. If completion.[[Type]] is throw, return Completion(completion). + if (completion.Type === 'throw') { + return Completion(completion); + } + // 7. If innerResult.[[Type]] is throw, return Completion(innerResult). + if (innerResult.Type === 'throw') { + return Completion(innerResult); + } + // 8. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception. + if (Type(innerResult.Value) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult.Value); + } + // 9. Return Completion(completion). + return Completion(completion); +} + +// 7.4.8 #sec-createiterresultobject +export function CreateIterResultObject(value, done) { + Assert(Type(done) === 'Boolean'); + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataProperty(obj, new Value('value'), value)); + X(CreateDataProperty(obj, new Value('done'), done)); + return obj; +} + +// 7.4.9 #sec-createlistiteratorRecord +export function CreateListIteratorRecord(list) { + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%IteratorPrototype%'), [ + 'IteratedList', + 'ListNextIndex', + ]); + iterator.IteratedList = list; + iterator.ListNextIndex = 0; + const steps = ListIteratorNextSteps; + const next = X(CreateBuiltinFunction(steps, [])); + return { + Iterator: iterator, + NextMethod: next, + Done: Value.false, + }; +} + +// 7.4.9.1 #sec-listiterator-next +function ListIteratorNextSteps(args, { thisValue }) { + const O = thisValue; + Assert(Type(O) === 'Object'); + Assert('IteratedList' in O); + const list = O.IteratedList; + const index = O.ListNextIndex; + const len = list.length; + if (index >= len) { + return CreateIterResultObject(Value.undefined, Value.true); + } + O.ListNextIndex += 1; + return CreateIterResultObject(list[index], Value.false); +} + +// 25.1.4.1 #sec-createasyncfromsynciterator +export function CreateAsyncFromSyncIterator(syncIteratorRecord) { + const asyncIterator = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncFromSyncIteratorPrototype%'), [ + 'SyncIteratorRecord', + ])); + asyncIterator.SyncIteratorRecord = syncIteratorRecord; + const nextMethod = X(Get(asyncIterator, new Value('next'))); + return { + Iterator: asyncIterator, + NextMethod: nextMethod, + Done: Value.false, + }; +} + +// 25.1.4.2.4 #sec-async-from-sync-iterator-value-unwrap-functions +function AsyncFromSyncIteratorValueUnwrapFunctions([value = Value.undefined]) { + const F = this; + + return X(CreateIterResultObject(value, F.Done)); +} + +// 25.1.4.4 #sec-asyncfromsynciteratorcontinuation +export function AsyncFromSyncIteratorContinuation(result, promiseCapability) { + const done = IteratorComplete(result); + IfAbruptRejectPromise(done, promiseCapability); + const value = IteratorValue(result); + IfAbruptRejectPromise(value, promiseCapability); + const valueWrapper = PromiseResolve(surroundingAgent.intrinsic('%Promise%'), value); + IfAbruptRejectPromise(valueWrapper, promiseCapability); + const steps = AsyncFromSyncIteratorValueUnwrapFunctions; + const onFulfilled = X(CreateBuiltinFunction(steps, ['Done'])); + onFulfilled.Done = done; + X(PerformPromiseThen(valueWrapper, onFulfilled, Value.undefined, promiseCapability)); + return promiseCapability.Promise; +} diff --git a/engine262/src/abstract-ops/module-namespace-exotic-objects.mjs b/engine262/src/abstract-ops/module-namespace-exotic-objects.mjs new file mode 100644 index 0000000..c6fb13c --- /dev/null +++ b/engine262/src/abstract-ops/module-namespace-exotic-objects.mjs @@ -0,0 +1,218 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Q, X } from '../completion.mjs'; +import { AbstractModuleRecord, ResolvedBindingRecord } from '../modules.mjs'; +import { + Type, + Value, + Descriptor, + wellKnownSymbols, +} from '../value.mjs'; +import { ValueSet } from '../helpers.mjs'; +import { + Assert, + SortCompare, + SameValue, + MakeBasicObject, + IsPropertyKey, + IsAccessorDescriptor, + SetImmutablePrototype, + OrdinaryGetOwnProperty, + OrdinaryDefineOwnProperty, + OrdinaryHasProperty, + OrdinaryGet, + OrdinaryDelete, + OrdinaryOwnPropertyKeys, + GetModuleNamespace, +} from './all.mjs'; + + +function ModuleNamespaceSetPrototypeOf(V) { + const O = this; + + return Q(SetImmutablePrototype(O, V)); +} + +function ModuleNamespaceIsExtensible() { + return Value.false; +} + +function ModuleNamespacePreventExtensions() { + return Value.true; +} + +function ModuleNamespaceGetOwnProperty(P) { + const O = this; + + if (Type(P) === 'Symbol') { + return OrdinaryGetOwnProperty(O, P); + } + const exports = O.Exports; + if (!exports.has(P)) { + return Value.undefined; + } + const value = Q(O.Get(P, O)); + return Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.false, + }); +} + +function ModuleNamespaceDefineOwnProperty(P, Desc) { + const O = this; + + if (Type(P) === 'Symbol') { + return OrdinaryDefineOwnProperty(O, P, Desc); + } + + const current = Q(O.GetOwnProperty(P)); + if (current === Value.undefined) { + return Value.false; + } + if (IsAccessorDescriptor(Desc)) { + return Value.false; + } + if (Desc.Writable !== undefined && Desc.Writable === Value.false) { + return Value.false; + } + if (Desc.Enumerable !== undefined && Desc.Enumerable === Value.false) { + return Value.false; + } + if (Desc.Configurable !== undefined && Desc.Configurable === Value.true) { + return Value.false; + } + if (Desc.Value !== undefined) { + return SameValue(Desc.Value, current.Value); + } + return Value.true; +} + +function ModuleNamespaceHasProperty(P) { + const O = this; + + if (Type(P) === 'Symbol') { + return OrdinaryHasProperty(O, P); + } + const exports = O.Exports; + if (exports.has(P)) { + return Value.true; + } + return Value.false; +} + +// #sec-module-namespace-exotic-objects-get-p-receiver +function ModuleNamespaceGet(P, Receiver) { + const O = this; + + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. If Type(P) is Symbol, then + if (Type(P) === 'Symbol') { + // a. Return ? OrdinaryGet(O, P, Receiver). + return OrdinaryGet(O, P, Receiver); + } + // 3. Let exports be O.[[Exports]]. + const exports = O.Exports; + // 4. If P is not an element of exports, return undefined. + if (!exports.has(P)) { + return Value.undefined; + } + // 5. Let m be O.[[Module]]. + const m = O.Module; + // 6. Let binding be ! m.ResolveExport(P). + const binding = m.ResolveExport(P); + // 7. Assert: binding is a ResolvedBinding Record. + Assert(binding instanceof ResolvedBindingRecord); + // 8. Let targetModule be binding.[[Module]]. + const targetModule = binding.Module; + // 9. Assert: targetModule is not undefined. + Assert(targetModule !== Value.undefined); + // 10. If binding.[[BindingName]] is ~namespace~, then + if (binding.BindingName === 'namespace') { + // a. Return ? GetModuleNamespace(targetModule). + return Q(GetModuleNamespace(targetModule)); + } + // 11. Let targetEnv be targetModule.[[Environment]]. + const targetEnv = targetModule.Environment; + // 12. If targetEnv is undefined, throw a ReferenceError exception. + if (targetEnv === Value.undefined) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', P); + } + // 13. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true). + return Q(targetEnv.GetBindingValue(binding.BindingName, Value.true)); +} + +function ModuleNamespaceSet() { + return Value.false; +} + +function ModuleNamespaceDelete(P) { + const O = this; + + Assert(IsPropertyKey(P)); + if (Type(P) === 'Symbol') { + return Q(OrdinaryDelete(O, P)); + } + const exports = O.Exports; + if (exports.has(P)) { + return Value.false; + } + return Value.true; +} + +function ModuleNamespaceOwnPropertyKeys() { + const O = this; + + const exports = [...O.Exports]; + const symbolKeys = X(OrdinaryOwnPropertyKeys(O)); + exports.push(...symbolKeys); + return exports; +} + +// 9.4.6.11 #sec-modulenamespacecreate +export function ModuleNamespaceCreate(module, exports) { + // 1. Assert: module is a Module Record. + Assert(module instanceof AbstractModuleRecord); + // 2. Assert: module.[[Namespace]] is undefined. + Assert(module.Namespace === Value.undefined); + // 3. Assert: exports is a List of String values. + Assert(Array.isArray(exports)); + // 4. Let internalSlotsList be the internal slots listed in Table 31. + const internalSlotsList = ['Module', 'Exports', 'Prototype']; + // 5. Let M be ! MakeBasicObject(internalSlotsList). + const M = X(MakeBasicObject(internalSlotsList)); + // 6. Set M's essential internal methods to the definitions specified in #sec-module-namespace-exotic-objects + M.SetPrototypeOf = ModuleNamespaceSetPrototypeOf; + M.IsExtensible = ModuleNamespaceIsExtensible; + M.PreventExtensions = ModuleNamespacePreventExtensions; + M.GetOwnProperty = ModuleNamespaceGetOwnProperty; + M.DefineOwnProperty = ModuleNamespaceDefineOwnProperty; + M.HasProperty = ModuleNamespaceHasProperty; + M.Get = ModuleNamespaceGet; + M.Set = ModuleNamespaceSet; + M.Delete = ModuleNamespaceDelete; + M.OwnPropertyKeys = ModuleNamespaceOwnPropertyKeys; + // 7. Set M.[[Prototype]] to null. + M.Prototype = Value.null; + // 8. Set M.[[Module]] to module. + M.Module = module; + // 9. Let sortedExports be a new List containing the same values as the list exports where the values are ordered as if an Array of the same values had been sorted using Array.prototype.sort using undefined as comparefn. + const sortedExports = [...exports].sort((x, y) => { + const result = X(SortCompare(x, y, Value.undefined)); + return result.numberValue(); + }); + // 10. Set M.[[Exports]] to sortedExports. + M.Exports = new ValueSet(sortedExports); + // 11. Create own properties of M corresponding to the definitions in 26.3. + M.properties.set(wellKnownSymbols.toStringTag, Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + Value: new Value('Module'), + })); + // 12. Set module.[[Namespace]] to M. + module.Namespace = M; + // 13. Return M; + return M; +} diff --git a/engine262/src/abstract-ops/module-records.mjs b/engine262/src/abstract-ops/module-records.mjs new file mode 100644 index 0000000..57f4cf1 --- /dev/null +++ b/engine262/src/abstract-ops/module-records.mjs @@ -0,0 +1,263 @@ +import { surroundingAgent, HostResolveImportedModule } from '../engine.mjs'; +import { + AbstractModuleRecord, + CyclicModuleRecord, + ResolvedBindingRecord, +} from '../modules.mjs'; +import { Value } from '../value.mjs'; +import { + Q, X, NormalCompletion, ThrowCompletion, +} from '../completion.mjs'; +import { + Assert, + ModuleNamespaceCreate, + NewPromiseCapability, + PerformPromiseThen, + CreateBuiltinFunction, + Call, +} from './all.mjs'; + +// 15.2.1.16.1.1 #sec-InnerModuleLinking +export function InnerModuleLinking(module, stack, index) { + if (!(module instanceof CyclicModuleRecord)) { + Q(module.Link()); + return index; + } + if (module.Status === 'linking' || module.Status === 'linked' || module.Status === 'evaluated') { + return index; + } + Assert(module.Status === 'unlinked'); + module.Status = 'linking'; + module.DFSIndex = index; + module.DFSAncestorIndex = index; + index += 1; + stack.push(module); + for (const required of module.RequestedModules) { + const requiredModule = Q(HostResolveImportedModule(module, required)); + index = Q(InnerModuleLinking(requiredModule, stack, index)); + if (requiredModule instanceof CyclicModuleRecord) { + Assert(requiredModule.Status === 'linking' || requiredModule.Status === 'linked' || requiredModule.Status === 'evaluated'); + Assert((requiredModule.Status === 'linking') === stack.includes(requiredModule)); + if (requiredModule.Status === 'linking') { + module.DFSAncestorIndex = Math.min(module.DFSAncestorIndex, requiredModule.DFSAncestorIndex); + } + } + } + Q(module.InitializeEnvironment()); + Assert(stack.indexOf(module) === stack.lastIndexOf(module)); + Assert(module.DFSAncestorIndex <= module.DFSIndex); + if (module.DFSAncestorIndex === module.DFSIndex) { + let done = false; + while (done === false) { + const requiredModule = stack.pop(); + Assert(requiredModule instanceof CyclicModuleRecord); + requiredModule.Status = 'linked'; + if (requiredModule === module) { + done = true; + } + } + } + return index; +} + +// 15.2.1.16.2.1 #sec-innermoduleevaluation +export function InnerModuleEvaluation(module, stack, index) { + if (!(module instanceof CyclicModuleRecord)) { + Q(module.Evaluate()); + return index; + } + if (module.Status === 'evaluated') { + if (module.EvaluationError === Value.undefined) { + return index; + } else { + return module.EvaluationError; + } + } + if (module.Status === 'evaluating') { + return index; + } + Assert(module.Status === 'linked'); + module.Status = 'evaluating'; + module.DFSIndex = index; + module.DFSAncestorIndex = index; + module.PendingAsyncDependencies = 0; + module.AsyncParentModules = []; + index += 1; + stack.push(module); + for (const required of module.RequestedModules) { + let requiredModule = X(HostResolveImportedModule(module, required)); + index = Q(InnerModuleEvaluation(requiredModule, stack, index)); + if (requiredModule instanceof CyclicModuleRecord) { + Assert(requiredModule.Status === 'evaluating' || requiredModule.Status === 'evaluated'); + if (stack.includes(requiredModule)) { + Assert(requiredModule.Status === 'evaluating'); + } + if (requiredModule.Status === 'evaluating') { + module.DFSAncestorIndex = Math.min(module.DFSAncestorIndex, requiredModule.DFSAncestorIndex); + } else { + requiredModule = GetAsyncCycleRoot(requiredModule); + Assert(requiredModule.Status === 'evaluated'); + if (requiredModule.EvaluationError !== Value.undefined) { + return module.EvaluationError; + } + } + if (requiredModule.AsyncEvaluating === Value.true) { + module.PendingAsyncDependencies += 1; + requiredModule.AsyncParentModules.push(module); + } + } + } + if (module.PendingAsyncDependencies > 0) { + module.AsyncEvaluating = Value.true; + } else if (module.Async === Value.true) { + X(ExecuteAsyncModule(module)); + } else { + Q(module.ExecuteModule()); + } + Assert(stack.indexOf(module) === stack.lastIndexOf(module)); + Assert(module.DFSAncestorIndex <= module.DFSIndex); + if (module.DFSAncestorIndex === module.DFSIndex) { + let done = false; + while (done === false) { + const requiredModule = stack.pop(); + Assert(requiredModule instanceof CyclicModuleRecord); + requiredModule.Status = 'evaluated'; + if (requiredModule === module) { + done = true; + } + } + } + return index; +} + +// https://tc39.es/proposal-top-level-await/#sec-execute-async-module +function ExecuteAsyncModule(module) { + Assert(module.Status === 'evaluating' || module.Status === 'evaluated'); + Assert(module.Async === Value.true); + module.AsyncEvaluating = Value.true; + const capability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + const stepsFulfilled = CallAsyncModuleFulfilled; + const onFulfilled = CreateBuiltinFunction(stepsFulfilled, ['Module']); + onFulfilled.Module = module; + const stepsRejected = CallAsyncModuleRejected; + const onRejected = CreateBuiltinFunction(stepsRejected, ['Module']); + onRejected.Module = module; + X(PerformPromiseThen(capability.Promise, onFulfilled, onRejected)); + X(module.ExecuteModule(capability)); + return Value.undefined; +} + +// https://tc39.es/proposal-top-level-await/#sec-execute-async-module +function CallAsyncModuleFulfilled() { + const f = surroundingAgent.activeFunctionObject; + const module = f.Module; + X(AsyncModuleExecutionFulfilled(module)); + return Value.undefined; +} + +// https://tc39.es/proposal-top-level-await/#sec-execute-async-module +function CallAsyncModuleRejected([error = Value.undefined]) { + const f = surroundingAgent.activeFunctionObject; + const module = f.Module; + X(AsyncModuleExecutionRejected(module, error)); + return Value.undefined; +} + +// https://tc39.es/proposal-top-level-await/#sec-getcycleroot +export function GetAsyncCycleRoot(module) { + Assert(module.Status === 'evaluated'); + if (module.AsyncParentModules.length === 0) { + return module; + } + while (module.DFSIndex > module.DFSAncestorIndex) { + Assert(module.AsyncParentModules.length > 0); + const nextCycleModule = module.AsyncParentModules[0]; + Assert(nextCycleModule.DFSAncestorIndex === module.DFSAncestorIndex); + module = nextCycleModule; + } + Assert(module.DFSIndex === module.DFSAncestorIndex); + return module; +} + +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled +function AsyncModuleExecutionFulfilled(module) { + Assert(module.Status === 'evaluated'); + if (module.AsyncEvaluating === Value.false) { + Assert(module.EvaluationError !== Value.undefined); + return Value.undefined; + } + Assert(module.EvaluationError === Value.undefined); + module.AsyncEvaluating = Value.false; + for (const m of module.AsyncParentModules) { + if (module.DFSIndex !== module.DFSAncestorIndex) { + Assert(m.DFSAncestorIndex === module.DFSAncestorIndex); + } + m.PendingAsyncDependencies -= 1; + if (m.PendingAsyncDependencies === 0 && m.EvaluationError === Value.undefined) { + Assert(m.AsyncEvaluating === Value.true); + const cycleRoot = X(GetAsyncCycleRoot(m)); + if (cycleRoot.EvaluationError !== Value.undefined) { + return Value.undefined; + } + if (m.Async === Value.true) { + X(ExecuteAsyncModule(m)); + } else { + const result = m.ExecuteModule(); + if (result instanceof NormalCompletion) { + X(AsyncModuleExecutionFulfilled(m)); + } else { + X(AsyncModuleExecutionRejected(m, result.Value)); + } + } + } + } + if (module.TopLevelCapability !== Value.undefined) { + Assert(module.DFSIndex === module.DFSAncestorIndex); + X(Call(module.TopLevelCapability.Resolve, Value.undefined, [Value.undefined])); + } + return Value.undefined; +} + +// https://tc39.es/proposal-top-level-await/#sec-AsyncModuleExecutionRejected +function AsyncModuleExecutionRejected(module, error) { + Assert(module.Status === 'evaluated'); + if (module.AsyncEvaluating === Value.false) { + Assert(module.EvaluationError !== Value.undefined); + return Value.undefined; + } + Assert(module.EvaluationError === Value.undefined); + module.EvaluationError = ThrowCompletion(error); + module.AsyncEvaluating = Value.false; + for (const m of module.AsyncParentModules) { + if (module.DFSIndex !== module.DFSAncestorIndex) { + Assert(m.DFSAncestorIndex === module.DFSAncestorIndex); + } + X(AsyncModuleExecutionRejected(m, error)); + } + if (module.TopLevelCapability !== Value.undefined) { + Assert(module.DFSIndex === module.DFSAncestorIndex); + X(Call(module.TopLevelCapability.Reject, Value.undefined, [error])); + } + return Value.undefined; +} + +// 15.2.1.21 #sec-getmodulenamespace +export function GetModuleNamespace(module) { + Assert(module instanceof AbstractModuleRecord); + if (module instanceof CyclicModuleRecord) { + Assert(module.Status !== 'unlinked'); + } + let namespace = module.Namespace; + if (namespace === Value.undefined) { + const exportedNames = Q(module.GetExportedNames()); + const unambiguousNames = []; + for (const name of exportedNames) { + const resolution = Q(module.ResolveExport(name)); + if (resolution instanceof ResolvedBindingRecord) { + unambiguousNames.push(name); + } + } + namespace = ModuleNamespaceCreate(module, unambiguousNames); + } + return namespace; +} diff --git a/engine262/src/abstract-ops/notational-conventions.mjs b/engine262/src/abstract-ops/notational-conventions.mjs new file mode 100644 index 0000000..5eca34d --- /dev/null +++ b/engine262/src/abstract-ops/notational-conventions.mjs @@ -0,0 +1,48 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type } from '../value.mjs'; + +export function Assert(invariant, source) { + /* istanbul ignore next */ + if (!invariant) { + throw new TypeError(`Assert failed${source ? `: ${source}` : ''}`.trim()); + } +} + +// 9.1.15 #sec-requireinternalslot +export function RequireInternalSlot(O, internalSlot) { + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + if (!(internalSlot in O)) { + return surroundingAgent.Throw('TypeError', 'InternalSlotMissing', O, internalSlot); + } +} + +export function sourceTextMatchedBy(node) { + return node.sourceText(); +} + +// An ECMAScript Script syntactic unit may be processed using either unrestricted or strict mode syntax and semantics. +// Code is interpreted as strict mode code in the following situations: +// +// - Global code is strict mode code if it begins with a Directive Prologue that contains a Use Strict Directive. +// +// - Module code is always strict mode code. +// +// - All parts of a ClassDeclaration or a ClassExpression are strict mode code. +// +// - Eval code is strict mode code if it begins with a Directive Prologue that contains a Use Strict Directive or +// if the call to eval is a direct eval that is contained in strict mode code. +// +// - Function code is strict mode code if the associated FunctionDeclaration, FunctionExpression, GeneratorDeclaration, +// GeneratorExpression, AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncGeneratorDeclaration, +// AsyncGeneratorExpression, MethodDefinition, ArrowFunction, or AsyncArrowFunction is contained in strict mode code +// or if the code that produces the value of the function's [[ECMAScriptCode]] internal slot begins with a Directive +// Prologue that contains a Use Strict Directive. +// +// - Function code that is supplied as the arguments to the built-in Function, Generator, AsyncFunction, and +// AsyncGenerator constructors is strict mode code if the last argument is a String that when processed is a +// FunctionBody that begins with a Directive Prologue that contains a Use Strict Directive. +export function isStrictModeCode(node) { + return node.strict; +} diff --git a/engine262/src/abstract-ops/object-operations.mjs b/engine262/src/abstract-ops/object-operations.mjs new file mode 100644 index 0000000..fa5b789 --- /dev/null +++ b/engine262/src/abstract-ops/object-operations.mjs @@ -0,0 +1,433 @@ +import { + Descriptor, + Type, + Value, + ObjectValue, + wellKnownSymbols, +} from '../value.mjs'; +import { + surroundingAgent, +} from '../engine.mjs'; +import { InstanceofOperator } from '../runtime-semantics/all.mjs'; +import { + NormalCompletion, + EnsureCompletion, + Q, X, +} from '../completion.mjs'; +import { + ArrayCreate, + Assert, + IsAccessorDescriptor, + IsCallable, + IsConstructor, + IsDataDescriptor, + IsExtensible, + IsPropertyKey, + SameValue, + ToLength, + ToObject, + ToString, + isProxyExoticObject, +} from './all.mjs'; + + +// This file covers abstract operations defined in +// 7.3 #sec-operations-on-objects + +// #sec-makebasicobject +export function MakeBasicObject(internalSlotsList) { + // 1. Assert: internalSlotsList is a List of internal slot names. + Assert(Array.isArray(internalSlotsList)); + // 2. Let obj be a newly created object with an internal slot for each name in internalSlotsList. + // 3. Set obj's essential internal methods to the default ordinary object definitions specified in 9.1. + const obj = new ObjectValue(internalSlotsList); + internalSlotsList.forEach((s) => { + obj[s] = Value.undefined; + }); + // 4. Assert: If the caller will not be overriding both obj's [[GetPrototypeOf]] and [[SetPrototypeOf]] essential internal methods, then internalSlotsList contains [[Prototype]]. + // 5. Assert: If the caller will not be overriding all of obj's [[SetPrototypeOf]], [[IsExtensible]], and [[PreventExtensions]] essential internal methods, then internalSlotsList contains [[Extensible]]. + // 6. If internalSlotsList contains [[Extensible]], then set obj.[[Extensible]] to true. + if (internalSlotsList.includes('Extensible')) { + obj.Extensible = Value.true; + } + // 7. Return obj. + return obj; +} + +// 7.3.1 #sec-get-o-p +export function Get(O, P) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + // TODO: This should just return Q(O.Get(P, O)) + return NormalCompletion(Q(O.Get(P, O))); +} + +// 7.3.2 #sec-getv +export function GetV(V, P) { + Assert(IsPropertyKey(P)); + const O = Q(ToObject(V)); + return Q(O.Get(P, V)); +} + +// 7.3.3 #sec-set-o-p-v-throw +export function Set(O, P, V, Throw) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + Assert(Type(Throw) === 'Boolean'); + const success = Q(O.Set(P, V, O)); + if (success === Value.false && Throw === Value.true) { + return surroundingAgent.Throw('TypeError', 'CannotSetProperty', P, O); + } + return success; +} + +// 7.3.4 #sec-createdataproperty +export function CreateDataProperty(O, P, V) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + + const newDesc = Descriptor({ + Value: V, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.true, + }); + return Q(O.DefineOwnProperty(P, newDesc)); +} + +// 7.3.5 #sec-createmethodproperty +export function CreateMethodProperty(O, P, V) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + + const newDesc = Descriptor({ + Value: V, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }); + return Q(O.DefineOwnProperty(P, newDesc)); +} + +// 7.3.6 #sec-createdatapropertyorthrow +export function CreateDataPropertyOrThrow(O, P, V) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + const success = Q(CreateDataProperty(O, P, V)); + if (success === Value.false) { + return surroundingAgent.Throw('TypeError', 'CannotDefineProperty', P); + } + return success; +} + +// 7.3.7 #sec-definepropertyorthrow +export function DefinePropertyOrThrow(O, P, desc) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + const success = Q(O.DefineOwnProperty(P, desc)); + if (success === Value.false) { + return surroundingAgent.Throw('TypeError', 'CannotDefineProperty', P); + } + return success; +} + +// 7.3.8 #sec-deletepropertyorthrow +export function DeletePropertyOrThrow(O, P) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + const success = Q(O.Delete(P)); + if (success === Value.false) { + return surroundingAgent.Throw('TypeError', 'CannotDeleteProperty', P); + } + return success; +} + +// 7.3.9 #sec-getmethod +export function GetMethod(V, P) { + Assert(IsPropertyKey(P)); + const func = Q(GetV(V, P)); + if (func === Value.null || func === Value.undefined) { + return Value.undefined; + } + if (IsCallable(func) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); + } + return func; +} + +// 7.3.10 #sec-hasproperty +export function HasProperty(O, P) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + return Q(O.HasProperty(P)); +} + +// 7.3.11 #sec-hasownproperty +export function HasOwnProperty(O, P) { + Assert(Type(O) === 'Object'); + Assert(IsPropertyKey(P)); + const desc = Q(O.GetOwnProperty(P)); + if (desc === Value.undefined) { + return Value.false; + } + return Value.true; +} + +// 7.3.12 #sec-call +export function Call(F, V, argumentsList) { + if (!argumentsList) { + argumentsList = []; + } + Assert(argumentsList.every((a) => a instanceof Value)); + + if (IsCallable(F) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', F); + } + + return EnsureCompletion(Q(F.Call(V, argumentsList))); +} + +// 7.3.13 #sec-construct +export function Construct(F, argumentsList, newTarget) { + if (!newTarget) { + newTarget = F; + } + if (!argumentsList) { + argumentsList = []; + } + Assert(IsConstructor(F) === Value.true); + Assert(IsConstructor(newTarget) === Value.true); + return Q(F.Construct(argumentsList, newTarget)); +} + +// 7.3.14 #sec-setintegritylevel +export function SetIntegrityLevel(O, level) { + Assert(Type(O) === 'Object'); + Assert(level === 'sealed' || level === 'frozen'); + const status = Q(O.PreventExtensions()); + if (status === Value.false) { + return Value.false; + } + const keys = Q(O.OwnPropertyKeys()); + if (level === 'sealed') { + for (const k of keys) { + Q(DefinePropertyOrThrow(O, k, Descriptor({ Configurable: Value.false }))); + } + } else if (level === 'frozen') { + for (const k of keys) { + const currentDesc = Q(O.GetOwnProperty(k)); + if (currentDesc !== Value.undefined) { + let desc; + if (IsAccessorDescriptor(currentDesc) === true) { + desc = Descriptor({ Configurable: Value.false }); + } else { + desc = Descriptor({ Configurable: Value.false, Writable: Value.false }); + } + Q(DefinePropertyOrThrow(O, k, desc)); + } + } + } + return Value.true; +} + +// 7.3.15 #sec-testintegritylevel +export function TestIntegrityLevel(O, level) { + Assert(Type(O) === 'Object'); + Assert(level === 'sealed' || level === 'frozen'); + const extensible = Q(IsExtensible(O)); + if (extensible === Value.true) { + return Value.false; + } + const keys = Q(O.OwnPropertyKeys()); + for (const k of keys) { + const currentDesc = Q(O.GetOwnProperty(k)); + if (currentDesc !== Value.undefined) { + if (currentDesc.Configurable === Value.true) { + return Value.false; + } + if (level === 'frozen' && IsDataDescriptor(currentDesc)) { + if (currentDesc.Writable === Value.true) { + return Value.false; + } + } + } + } + return Value.true; +} + +// 7.3.16 #sec-createarrayfromlist +export function CreateArrayFromList(elements) { + Assert(elements.every((e) => e instanceof Value)); + const array = X(ArrayCreate(new Value(0))); + let n = 0; + for (const e of elements) { + const nStr = X(ToString(new Value(n))); + const status = X(CreateDataProperty(array, nStr, e)); + Assert(status === Value.true); + n += 1; + } + return array; +} + +// 7.3.17 #sec-lengthofarraylike +export function LengthOfArrayLike(obj) { + Assert(Type(obj) === 'Object'); + return Q(ToLength(Q(Get(obj, new Value('length'))))); +} + +// 7.3.17 #sec-createlistfromarraylike +export function CreateListFromArrayLike(obj, elementTypes) { + if (!elementTypes) { + elementTypes = ['Undefined', 'Null', 'Boolean', 'String', 'Symbol', 'Number', 'Object']; + } + if (Type(obj) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', obj); + } + const len = Q(LengthOfArrayLike(obj)).numberValue(); + const list = []; + let index = 0; + while (index < len) { + const indexName = X(ToString(new Value(index))); + const next = Q(Get(obj, indexName)); + if (!elementTypes.includes(Type(next))) { + return surroundingAgent.Throw('TypeError', 'NotPropertyName', next); + } + list.push(next); + index += 1; + } + return list; +} + +// 7.3.18 #sec-invoke +export function Invoke(V, P, argumentsList) { + Assert(IsPropertyKey(P)); + if (!argumentsList) { + argumentsList = []; + } + const func = Q(GetV(V, P)); + return Q(Call(func, V, argumentsList)); +} + +// 7.3.19 #sec-ordinaryhasinstance +export function OrdinaryHasInstance(C, O) { + if (IsCallable(C) === Value.false) { + return Value.false; + } + if ('BoundTargetFunction' in C) { + const BC = C.BoundTargetFunction; + return Q(InstanceofOperator(O, BC)); + } + if (Type(O) !== 'Object') { + return Value.false; + } + const P = Q(Get(C, new Value('prototype'))); + if (Type(P) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', P); + } + while (true) { + O = Q(O.GetPrototypeOf()); + if (O === Value.null) { + return Value.false; + } + if (SameValue(P, O) === Value.true) { + return Value.true; + } + } +} + +// 7.3.20 #sec-speciesconstructor +export function SpeciesConstructor(O, defaultConstructor) { + Assert(Type(O) === 'Object'); + const C = Q(Get(O, new Value('constructor'))); + if (C === Value.undefined) { + return defaultConstructor; + } + if (Type(C) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', C); + } + const S = Q(Get(C, wellKnownSymbols.species)); + if (S === Value.undefined || S === Value.null) { + return defaultConstructor; + } + if (IsConstructor(S) === Value.true) { + return S; + } + return surroundingAgent.Throw('TypeError', 'SpeciesNotConstructor'); +} + +// 7.3.21 #sec-enumerableownpropertynames +export function EnumerableOwnPropertyNames(O, kind) { + Assert(Type(O) === 'Object'); + const ownKeys = Q(O.OwnPropertyKeys()); + const properties = []; + for (const key of ownKeys) { + if (Type(key) === 'String') { + const desc = Q(O.GetOwnProperty(key)); + if (desc !== Value.undefined && desc.Enumerable === Value.true) { + if (kind === 'key') { + properties.push(key); + } else { + const value = Q(Get(O, key)); + if (kind === 'value') { + properties.push(value); + } else { + Assert(kind === 'key+value'); + const entry = X(CreateArrayFromList([key, value])); + properties.push(entry); + } + } + } + } + } + return properties; +} + +// 7.3.22 #sec-getfunctionrealm +export function GetFunctionRealm(obj) { + Assert(X(IsCallable(obj)) === Value.true); + if ('Realm' in obj) { + return obj.Realm; + } + + if ('BoundTargetFunction' in obj) { + const target = obj.BoundTargetFunction; + return Q(GetFunctionRealm(target)); + } + + if (isProxyExoticObject(obj)) { + if (obj.ProxyHandler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'GetFunctionRealm'); + } + const proxyTarget = obj.ProxyTarget; + return Q(GetFunctionRealm(proxyTarget)); + } + + return surroundingAgent.currentRealmRecord; +} + +// 7.3.23 #sec-copydataproperties +export function CopyDataProperties(target, source, excludedItems) { + Assert(Type(target) === 'Object'); + Assert(excludedItems.every((i) => IsPropertyKey(i))); + if (source === Value.undefined || source === Value.null) { + return target; + } + const from = X(ToObject(source)); + const keys = Q(from.OwnPropertyKeys()); + for (const nextKey of keys) { + let excluded = false; + for (const e of excludedItems) { + if (SameValue(e, nextKey) === Value.true) { + excluded = true; + } + } + if (excluded === false) { + const desc = Q(from.GetOwnProperty(nextKey)); + if (desc !== Value.undefined && desc.Enumerable === Value.true) { + const propValue = Q(Get(from, nextKey)); + X(CreateDataProperty(target, nextKey, propValue)); + } + } + } + return target; +} diff --git a/engine262/src/abstract-ops/objects.mjs b/engine262/src/abstract-ops/objects.mjs new file mode 100644 index 0000000..036087b --- /dev/null +++ b/engine262/src/abstract-ops/objects.mjs @@ -0,0 +1,405 @@ +import { + Descriptor, + ObjectValue, + Type, + Value, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { + Assert, + Call, + CreateDataProperty, + Get, + GetFunctionRealm, + IsAccessorDescriptor, + IsCallable, + IsDataDescriptor, + IsExtensible, + IsGenericDescriptor, + IsPropertyKey, + SameValue, + MakeBasicObject, + isArrayIndex, +} from './all.mjs'; + +// 9.1.1.1 OrdinaryGetPrototypeOf +export function OrdinaryGetPrototypeOf(O) { + return O.Prototype; +} + +// 9.1.2.1 OrdinarySetPrototypeOf +export function OrdinarySetPrototypeOf(O, V) { + Assert(Type(V) === 'Object' || Type(V) === 'Null'); + + const current = O.Prototype; + if (SameValue(V, current) === Value.true) { + return Value.true; + } + const extensible = O.Extensible; + if (extensible === Value.false) { + return Value.false; + } + let p = V; + let done = false; + while (done === false) { + if (p === Value.null) { + done = true; + } else if (SameValue(p, O) === Value.true) { + return Value.false; + } else if (p.GetPrototypeOf !== ObjectValue.prototype.GetPrototypeOf) { + done = true; + } else { + p = p.Prototype; + } + } + O.Prototype = V; + return Value.true; +} + +// 9.1.3.1 OrdinaryIsExtensible +export function OrdinaryIsExtensible(O) { + return O.Extensible; +} + +// 9.1.4.1 OrdinaryPreventExtensions +export function OrdinaryPreventExtensions(O) { + O.Extensible = Value.false; + return Value.true; +} + +// 9.1.5.1 OrdinaryGetOwnProperty +export function OrdinaryGetOwnProperty(O, P) { + Assert(IsPropertyKey(P)); + + if (!O.properties.has(P)) { + return Value.undefined; + } + + const D = Descriptor({}); + + const x = O.properties.get(P); + + if (IsDataDescriptor(x)) { + D.Value = x.Value; + D.Writable = x.Writable; + } else if (IsAccessorDescriptor(x)) { + D.Get = x.Get; + D.Set = x.Set; + } + D.Enumerable = x.Enumerable; + D.Configurable = x.Configurable; + + return D; +} + +// 9.1.6.1 OrdinaryDefineOwnProperty +export function OrdinaryDefineOwnProperty(O, P, Desc) { + const current = Q(O.GetOwnProperty(P)); + const extensible = Q(IsExtensible(O)); + return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current); +} + +// 9.1.6.2 #sec-iscompatiblepropertydescriptor +export function IsCompatiblePropertyDescriptor(Extensible, Desc, Current) { + return ValidateAndApplyPropertyDescriptor( + Value.undefined, Value.undefined, Extensible, Desc, Current, + ); +} + +// 9.1.6.3 ValidateAndApplyPropertyDescriptor +export function ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current) { + Assert(O === Value.undefined || IsPropertyKey(P)); + + if (current === Value.undefined) { + if (extensible === Value.false) { + return Value.false; + } + + Assert(extensible === Value.true); + + if (IsGenericDescriptor(Desc) || IsDataDescriptor(Desc)) { + if (Type(O) !== 'Undefined') { + O.properties.set(P, Descriptor({ + Value: Desc.Value === undefined ? Value.undefined : Desc.Value, + Writable: Desc.Writable === undefined ? Value.false : Desc.Writable, + Enumerable: Desc.Enumerable === undefined ? Value.false : Desc.Enumerable, + Configurable: Desc.Configurable === undefined ? Value.false : Desc.Configurable, + })); + } + } else { + Assert(IsAccessorDescriptor(Desc)); + if (Type(O) !== 'Undefined') { + O.properties.set(P, Descriptor({ + Get: Desc.Get === undefined ? Value.undefined : Desc.Get, + Set: Desc.Set === undefined ? Value.undefined : Desc.Set, + Enumerable: Desc.Enumerable === undefined ? Value.false : Desc.Enumerable, + Configurable: Desc.Configurable === undefined ? Value.false : Desc.Configurable, + })); + } + } + + return Value.true; + } + + if (Desc.everyFieldIsAbsent()) { + return Value.true; + } + + if (current.Configurable === Value.false) { + if (Desc.Configurable !== undefined && Desc.Configurable === Value.true) { + return Value.false; + } + + if (Desc.Enumerable !== undefined && Desc.Enumerable !== current.Enumerable) { + return Value.false; + } + } + + if (IsGenericDescriptor(Desc)) { + // No further validation is required. + } else if (IsDataDescriptor(current) !== IsDataDescriptor(Desc)) { + if (current.Configurable === Value.false) { + return Value.false; + } + if (IsDataDescriptor(current)) { + if (Type(O) !== 'Undefined') { + const entry = O.properties.get(P); + entry.Value = undefined; + entry.Writable = undefined; + entry.Get = Value.undefined; + entry.Set = Value.undefined; + } + } else { + if (Type(O) !== 'Undefined') { + const entry = O.properties.get(P); + entry.Get = undefined; + entry.Set = undefined; + entry.Value = Value.undefined; + entry.Writable = Value.false; + } + } + } else if (IsDataDescriptor(current) && IsDataDescriptor(Desc)) { + if (current.Configurable === Value.false && current.Writable === Value.false) { + if (Desc.Writable !== undefined && Desc.Writable === Value.true) { + return Value.false; + } + if (Desc.Value !== undefined && SameValue(Desc.Value, current.Value) === Value.false) { + return Value.false; + } + return Value.true; + } + } else { + Assert(IsAccessorDescriptor(current) && IsAccessorDescriptor(Desc)); + if (current.Configurable === Value.false) { + if (Desc.Set !== undefined && SameValue(Desc.Set, current.Set) === Value.false) { + return Value.false; + } + if (Desc.Get !== undefined && SameValue(Desc.Get, current.Get) === Value.false) { + return Value.false; + } + return Value.true; + } + } + + if (Type(O) !== 'Undefined') { + const target = O.properties.get(P); + if (Desc.Value !== undefined) { + target.Value = Desc.Value; + } + if (Desc.Writable !== undefined) { + target.Writable = Desc.Writable; + } + if (Desc.Get !== undefined) { + target.Get = Desc.Get; + } + if (Desc.Set !== undefined) { + target.Set = Desc.Set; + } + if (Desc.Enumerable !== undefined) { + target.Enumerable = Desc.Enumerable; + } + if (Desc.Configurable !== undefined) { + target.Configurable = Desc.Configurable; + } + } + + return Value.true; +} + +// 9.1.7.1 OrdinaryHasProperty +export function OrdinaryHasProperty(O, P) { + Assert(IsPropertyKey(P)); + + const hasOwn = Q(O.GetOwnProperty(P)); + if (Type(hasOwn) !== 'Undefined') { + return Value.true; + } + const parent = Q(O.GetPrototypeOf()); + if (Type(parent) !== 'Null') { + return Q(parent.HasProperty(P)); + } + return Value.false; +} + +// 9.1.8.1 +export function OrdinaryGet(O, P, Receiver) { + Assert(IsPropertyKey(P)); + + const desc = Q(O.GetOwnProperty(P)); + if (Type(desc) === 'Undefined') { + const parent = Q(O.GetPrototypeOf()); + if (Type(parent) === 'Null') { + return Value.undefined; + } + return Q(parent.Get(P, Receiver)); + } + if (IsDataDescriptor(desc)) { + return desc.Value; + } + Assert(IsAccessorDescriptor(desc)); + const getter = desc.Get; + if (Type(getter) === 'Undefined') { + return Value.undefined; + } + return Q(Call(getter, Receiver)); +} + +// 9.1.9.1 OrdinarySet +export function OrdinarySet(O, P, V, Receiver) { + Assert(IsPropertyKey(P)); + const ownDesc = Q(O.GetOwnProperty(P)); + return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc); +} + +// 9.1.9.2 OrdinarySetWithOwnDescriptor +export function OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc) { + Assert(IsPropertyKey(P)); + + if (Type(ownDesc) === 'Undefined') { + const parent = Q(O.GetPrototypeOf()); + if (Type(parent) !== 'Null') { + return Q(parent.Set(P, V, Receiver)); + } + ownDesc = Descriptor({ + Value: Value.undefined, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.true, + }); + } + + if (IsDataDescriptor(ownDesc)) { + if (ownDesc.Writable !== undefined && ownDesc.Writable === Value.false) { + return Value.false; + } + if (Type(Receiver) !== 'Object') { + return Value.false; + } + + const existingDescriptor = Q(Receiver.GetOwnProperty(P)); + if (Type(existingDescriptor) !== 'Undefined') { + if (IsAccessorDescriptor(existingDescriptor)) { + return Value.false; + } + if (existingDescriptor.Writable === Value.false) { + return Value.false; + } + const valueDesc = Descriptor({ Value: V }); + return Q(Receiver.DefineOwnProperty(P, valueDesc)); + } + return CreateDataProperty(Receiver, P, V); + } + + Assert(IsAccessorDescriptor(ownDesc)); + const setter = ownDesc.Set; + if (setter === undefined || Type(setter) === 'Undefined') { + return Value.false; + } + Q(Call(setter, Receiver, [V])); + return Value.true; +} + +// 9.1.10.1 OrdinaryDelete +export function OrdinaryDelete(O, P) { + Assert(IsPropertyKey(P)); + const desc = Q(O.GetOwnProperty(P)); + if (Type(desc) === 'Undefined') { + return Value.true; + } + if (desc.Configurable === Value.true) { + O.properties.delete(P); + return Value.true; + } + return Value.false; +} + +// 9.1.11.1 +export function OrdinaryOwnPropertyKeys(O) { + const keys = []; + + // For each own property key P of O that is an array index, in ascending numeric index order, do + // Add P as the last element of keys. + for (const P of O.properties.keys()) { + if (isArrayIndex(P)) { + keys.push(P); + } + } + keys.sort((a, b) => Number.parseInt(a.stringValue(), 10) - Number.parseInt(b.stringValue(), 10)); + + // For each own property key P of O such that Type(P) is String and + // P is not an array index, in ascending chronological order of property creation, do + // Add P as the last element of keys. + for (const P of O.properties.keys()) { + if (Type(P) === 'String' && isArrayIndex(P) === false) { + keys.push(P); + } + } + + // For each own property key P of O such that Type(P) is Symbol, + // in ascending chronological order of property creation, do + // Add P as the last element of keys. + for (const P of O.properties.keys()) { + if (Type(P) === 'Symbol') { + keys.push(P); + } + } + + return keys; +} + +// #sec-ordinaryobjectcreate +export function OrdinaryObjectCreate(proto, additionalInternalSlotsList) { + // 1. Let internalSlotsList be « [[Prototype]], [[Extensible]] ». + const internalSlotsList = ['Prototype', 'Extensible']; + // 2. If additionalInternalSlotsList is present, append each of its elements to internalSlotsList. + if (additionalInternalSlotsList !== undefined) { + internalSlotsList.push(...additionalInternalSlotsList); + } + // 3. Let O be ! MakeBasicObject(internalSlotsList). + const O = X(MakeBasicObject(internalSlotsList)); + // 4. Set O.[[Prototype]] to proto. + O.Prototype = proto; + // 5. Return O. + return O; +} + +// 9.1.13 OrdinaryCreateFromConstructor +export function OrdinaryCreateFromConstructor(constructor, intrinsicDefaultProto, internalSlotsList) { + // Assert: intrinsicDefaultProto is a String value that is this specification's name of an intrinsic object. + const proto = Q(GetPrototypeFromConstructor(constructor, intrinsicDefaultProto)); + return OrdinaryObjectCreate(proto, internalSlotsList); +} + +// 9.1.14 GetPrototypeFromConstructor +export function GetPrototypeFromConstructor(constructor, intrinsicDefaultProto) { + // Assert: intrinsicDefaultProto is a String value that + // is this specification's name of an intrinsic object. + Assert(IsCallable(constructor) === Value.true); + let proto = Q(Get(constructor, new Value('prototype'))); + if (Type(proto) !== 'Object') { + const realm = Q(GetFunctionRealm(constructor)); + proto = realm.Intrinsics[intrinsicDefaultProto]; + } + return proto; +} + diff --git a/engine262/src/abstract-ops/promise-operations.mjs b/engine262/src/abstract-ops/promise-operations.mjs new file mode 100644 index 0000000..ffaee88 --- /dev/null +++ b/engine262/src/abstract-ops/promise-operations.mjs @@ -0,0 +1,423 @@ +import { + HostMakeJobCallback, + HostCallJobCallback, + HostEnqueuePromiseJob, + HostPromiseRejectionTracker, + surroundingAgent, +} from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { + Completion, + AbruptCompletion, + NormalCompletion, + Q, + X, + ThrowCompletion, +} from '../completion.mjs'; +import { + Assert, + Call, + Construct, + CreateBuiltinFunction, + Get, + IsCallable, + IsConstructor, + SameValue, + SetFunctionLength, + SetFunctionName, + GetFunctionRealm, + isFunctionObject, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 25.6 #sec-promise-objects + +// 25.6.1.1 #sec-promisecapability-records +export class PromiseCapabilityRecord { + constructor() { + this.Promise = Value.undefined; + this.Resolve = Value.undefined; + this.Reject = Value.undefined; + } +} + +// 25.6.1.2 #sec-promisereaction-records +export class PromiseReactionRecord { + constructor(O) { + Assert(O.Capability instanceof PromiseCapabilityRecord + || O.Capability === Value.undefined); + Assert(O.Type === 'Fulfill' || O.Type === 'Reject'); + Assert(O.Handler === undefined + || isFunctionObject(O.Handler.Callback)); + this.Capability = O.Capability; + this.Type = O.Type; + this.Handler = O.Handler; + } +} + +// 25.6.1.3 #sec-createresolvingfunctions +export function CreateResolvingFunctions(promise) { + const alreadyResolved = { Value: false }; + const stepsResolve = PromiseResolveFunctions; + const resolve = X(CreateBuiltinFunction(stepsResolve, ['Promise', 'AlreadyResolved'])); + SetFunctionLength(resolve, new Value(1)); + SetFunctionName(resolve, new Value('')); + resolve.Promise = promise; + resolve.AlreadyResolved = alreadyResolved; + const stepsReject = PromiseRejectFunctions; + const reject = X(CreateBuiltinFunction(stepsReject, ['Promise', 'AlreadyResolved'])); + SetFunctionLength(reject, new Value(1)); + SetFunctionName(reject, new Value('')); + reject.Promise = promise; + reject.AlreadyResolved = alreadyResolved; + return { + Resolve: resolve, + Reject: reject, + }; +} + +// 25.6.1.3.1 #sec-promise-reject-functions +function PromiseRejectFunctions([reason = Value.undefined]) { + const F = this; + + Assert('Promise' in F && Type(F.Promise) === 'Object'); + const promise = F.Promise; + const alreadyResolved = F.AlreadyResolved; + if (alreadyResolved.Value === true) { + return Value.undefined; + } + alreadyResolved.Value = true; + return RejectPromise(promise, reason); +} + +// #sec-newpromiseresolvethenablejob +function NewPromiseResolveThenableJob(promiseToResolve, thenable, then) { + // 1. Let job be a new Job abstract closure with no parameters that captures + // promiseToResolve, thenable, and then and performs the following steps when called: + const job = () => { + // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). + const resolvingFunctions = CreateResolvingFunctions(promiseToResolve); + // b. Let thenCallResult be HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). + const thenCallResult = HostCallJobCallback(then, thenable, [resolvingFunctions.Resolve, resolvingFunctions.Reject]); + // c. If thenCallResult is an abrupt completion, then + if (thenCallResult instanceof AbruptCompletion) { + // i .Let status be Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). + const status = Call(resolvingFunctions.Reject, Value.undefined, [thenCallResult.Value]); + // ii. Return Completion(status). + return Completion(status); + } + // d. Return Completion(thenCallResult). + return Completion(thenCallResult); + }; + // 2. Let getThenRealmResult be GetFunctionRealm(then.[[Callback]]). + const getThenRealmResult = GetFunctionRealm(then.Callback); + // 3. If getThenRealmResult is a normal completion, then let thenRealm be getThenRealmResult.[[Value]]. + let thenRealm; + if (getThenRealmResult instanceof NormalCompletion) { + thenRealm = getThenRealmResult.Value; + } else { + // 4. Else, let _thenRealm_ be the current Realm Record. + thenRealm = surroundingAgent.currentRealmRecord; + } + // 5. NOTE: _thenRealm_ is never *null*. When _then_.[[Callback]] is a revoked Proxy and no code runs, _thenRealm_ is used to create error objects. + // 6. Return { [[Job]]: job, [[Realm]]: thenRealm }. + return { Job: job, Realm: thenRealm }; +} + +// 25.6.1.3.2 #sec-promise-resolve-functions +function PromiseResolveFunctions([resolution = Value.undefined]) { + // 1. Let F be the active function object. + const F = this; + // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. + Assert('Promise' in F && Type(F.Promise) === 'Object'); + // 3. Let promise be F.[[Promise]]. + const promise = F.Promise; + // 4. Let alreadyResolved be F.[[AlreadyResolved]]. + const alreadyResolved = F.AlreadyResolved; + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if (alreadyResolved.Value === true) { + return Value.undefined; + } + // 6. Set alreadyResolved.[[Value]] to true. + alreadyResolved.Value = true; + // 7. If SameValue(resolution, promise) is true, then + if (SameValue(resolution, promise) === Value.true) { + // a. Let selfResolutionError be a newly created TypeError object. + const selfResolutionError = surroundingAgent.Throw('TypeError', 'CannotResolvePromiseWithItself').Value; + // b. Return RejectPromise(promise, selfResolutionError). + return RejectPromise(promise, selfResolutionError); + } + // 8. If Type(resolution) is not Object, then + if (Type(resolution) !== 'Object') { + // a. Return FulfillPromise(promise, resolution). + return FulfillPromise(promise, resolution); + } + // 9. Let then be Get(resolution, "then"). + const then = Get(resolution, new Value('then')); + // 10. If then is an abrupt completion, then + if (then instanceof AbruptCompletion) { + // a. Return RejectPromise(promise, then.[[Value]]). + return RejectPromise(promise, then.Value); + } + // 11. Let thenAction be then.[[Value]]. + const thenAction = then.Value; + // 12. If IsCallable(thenAction) is false, then + if (IsCallable(thenAction) === Value.false) { + // a. Return FulfillPromise(promise, resolution). + return FulfillPromise(promise, resolution); + } + // 13. Let thenJobCallback be HostMakeJobCallback(thenAction). + const thenJobCallback = HostMakeJobCallback(thenAction); + // 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). + const job = NewPromiseResolveThenableJob(promise, resolution, thenJobCallback); + // 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). + HostEnqueuePromiseJob(job.Job, job.Realm); + // 16. Return undefined. + return Value.undefined; +} + +// 25.6.1.4 #sec-fulfillpromise +function FulfillPromise(promise, value) { + Assert(promise.PromiseState === 'pending'); + const reactions = promise.PromiseFulfillReactions; + promise.PromiseResult = value; + promise.PromiseFulfillReactions = undefined; + promise.PromiseRejectReactions = undefined; + promise.PromiseState = 'fulfilled'; + return TriggerPromiseReactions(reactions, value); +} + +// 25.6.1.5 #sec-newpromisecapability +export function NewPromiseCapability(C) { + if (IsConstructor(C) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + const promiseCapability = new PromiseCapabilityRecord(); + const steps = GetCapabilitiesExecutorFunctions; + const executor = X(CreateBuiltinFunction(steps, ['Capability'])); + SetFunctionLength(executor, new Value(2)); + SetFunctionName(executor, new Value('')); + executor.Capability = promiseCapability; + const promise = Q(Construct(C, [executor])); + if (IsCallable(promiseCapability.Resolve) === Value.false) { + return surroundingAgent.Throw('TypeError', 'PromiseResolveFunction', promiseCapability.Resolve); + } + if (IsCallable(promiseCapability.Reject) === Value.false) { + return surroundingAgent.Throw('TypeError', 'PromiseRejectFunction', promiseCapability.Reject); + } + promiseCapability.Promise = promise; + return promiseCapability; +} + +// 25.6.1.5.1 #sec-getcapabilitiesexecutor-functions +function GetCapabilitiesExecutorFunctions([resolve = Value.undefined, reject = Value.undefined]) { + const F = this; + + const promiseCapability = F.Capability; + if (Type(promiseCapability.Resolve) !== 'Undefined') { + return surroundingAgent.Throw('TypeError', 'PromiseCapabilityFunctionAlreadySet', 'resolve'); + } + if (Type(promiseCapability.Reject) !== 'Undefined') { + return surroundingAgent.Throw('TypeError', 'PromiseCapabilityFunctionAlreadySet', 'reject'); + } + promiseCapability.Resolve = resolve; + promiseCapability.Reject = reject; + return Value.undefined; +} + +// 25.6.1.6 #sec-ispromise +export function IsPromise(x) { + if (Type(x) !== 'Object') { + return Value.false; + } + if (!('PromiseState' in x)) { + return Value.false; + } + return Value.true; +} + +// 25.6.1.7 #sec-rejectpromise +function RejectPromise(promise, reason) { + Assert(promise.PromiseState === 'pending'); + const reactions = promise.PromiseRejectReactions; + promise.PromiseResult = reason; + promise.PromiseFulfillReactions = undefined; + promise.PromiseRejectReactions = undefined; + promise.PromiseState = 'rejected'; + if (promise.PromiseIsHandled === Value.false) { + HostPromiseRejectionTracker(promise, 'reject'); + } + return TriggerPromiseReactions(reactions, reason); +} + +// #sec-triggerpromisereactions +function TriggerPromiseReactions(reactions, argument) { + // 1. For each reaction in reactions, in original insertion order, do + reactions.forEach((reaction) => { + // a. Let job be NewPromiseReactionJob(reaction, argument). + const job = NewPromiseReactionJob(reaction, argument); + // b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). + HostEnqueuePromiseJob(job.Job, job.Realm); + }); + // 2. Return undefined. + return Value.undefined; +} + +// 25.6.4.5.1 #sec-promise-resolve +export function PromiseResolve(C, x) { + Assert(Type(C) === 'Object'); + if (IsPromise(x) === Value.true) { + const xConstructor = Q(Get(x, new Value('constructor'))); + if (SameValue(xConstructor, C) === Value.true) { + return x; + } + } + const promiseCapability = Q(NewPromiseCapability(C)); + Q(Call(promiseCapability.Resolve, Value.undefined, [x])); + return promiseCapability.Promise; +} + +// #sec-newpromisereactionjob +function NewPromiseReactionJob(reaction, argument) { + // 1. Let job be a new Job abstract closure with no parameters that captures + // reaction and argument and performs the following steps when called: + const job = () => { + // a. Assert: reaction is a PromiseReaction Record. + Assert(reaction instanceof PromiseReactionRecord); + // b. Let promiseCapability be reaction.[[Capability]]. + const promiseCapability = reaction.Capability; + // c. Let type be reaction.[[Type]]. + const type = reaction.Type; + // d. Let handler be reaction.[[Handler]]. + const handler = reaction.Handler; + let handlerResult; + // e. If handler is empty, then + if (handler === undefined) { + // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). + if (type === 'Fulfill') { + handlerResult = NormalCompletion(argument); + } else { + // 1. Assert: type is Reject. + Assert(type === 'Reject'); + // 2. Let handlerResult be ThrowCompletion(argument). + handlerResult = ThrowCompletion(argument); + } + } else { + // f. Else, let handlerResult be HostCallJobCallback(handler, undefined, « argument »). + handlerResult = HostCallJobCallback(handler, Value.undefined, [argument]); + } + // g. If promiseCapability is undefined, then + if (promiseCapability === Value.undefined) { + // i. Assert: handlerResult is not an abrupt completion. + Assert(!(handlerResult instanceof AbruptCompletion)); + // ii. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + let status; + // h. If handlerResult is an abrupt completion, then + if (handlerResult instanceof AbruptCompletion) { + // i. Let status be Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). + status = Call(promiseCapability.Reject, Value.undefined, [handlerResult.Value]); + } else { + // ii. Let status be Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). + status = Call(promiseCapability.Resolve, Value.undefined, [handlerResult.Value]); + } + // j. Return Completion(status). + return Completion(status); + }; + // 2. Let handlerRealm be null. + let handlerRealm = Value.null; + // 3. If reaction.[[Handler]] is not empty, then + if (reaction.Handler !== undefined) { + // a. Let getHandlerRealmResult be GetFunctionRealm(reaction.[[Handler]].[[Callback]]). + const getHandlerRealmResult = GetFunctionRealm(reaction.Handler.Callback); + // b. If getHandlerRealmResult is a normal completion, then set handlerRealm to getHandlerRealmResult.[[Value]]. + if (getHandlerRealmResult instanceof NormalCompletion) { + handlerRealm = getHandlerRealmResult.Value; + } else { + // c. Else, set _handlerRealm_ to the current Realm Record. + handlerRealm = surroundingAgent.currentRealmRecord; + } + // d. NOTE: _handlerRealm_ is never *null* unless the handler is *undefined*. When the handler + // is a revoked Proxy and no ECMAScript code runs, _handlerRealm_ is used to create error objects. + } + // 4. Return { [[Job]]: job, [[Realm]]: handlerRealm }. + return { Job: job, Realm: handlerRealm }; +} + +// 25.6.5.4.1 #sec-performpromisethen +export function PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability) { + // 1. Assert: IsPromise(promise) is true. + Assert(IsPromise(promise) === Value.true); + // 2. If resultCapability is not present, then + if (resultCapability === undefined) { + // a. Set resultCapability to undefined. + resultCapability = Value.undefined; + } + let onFulfilledJobCallback; + // 3. If IsCallable(onFulfilled) is false, then + if (IsCallable(onFulfilled) === Value.false) { + // a. Let onFulfilledJobCallback be empty. + onFulfilledJobCallback = undefined; + } else { // 4. Else, + // a. Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled). + onFulfilledJobCallback = HostMakeJobCallback(onFulfilled); + } + let onRejectedJobCallback; + // 5. If IsCallable(onRejected) is false, then + if (IsCallable(onRejected) === Value.false) { + // a. Let onRejectedJobCallback be empty. + onRejectedJobCallback = undefined; + } else { // 6. Else, + onRejectedJobCallback = HostMakeJobCallback(onRejected); + } + // 7. Let fulfillReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Fulfill, [[Handler]]: onFulfilled }. + const fulfillReaction = new PromiseReactionRecord({ + Capability: resultCapability, + Type: 'Fulfill', + Handler: onFulfilledJobCallback, + }); + // 8. Let rejectReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Reject, [[Handler]]: onRejected }. + const rejectReaction = new PromiseReactionRecord({ + Capability: resultCapability, + Type: 'Reject', + Handler: onRejectedJobCallback, + }); + // 9. If promise.[[PromiseState]] is pending, then + if (promise.PromiseState === 'pending') { + // a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]]. + promise.PromiseFulfillReactions.push(fulfillReaction); + // b. Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]]. + promise.PromiseRejectReactions.push(rejectReaction); + } else if (promise.PromiseState === 'fulfilled') { + // a. Let value be promise.[[PromiseResult]]. + const value = promise.PromiseResult; + // b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value). + const fulfillJob = NewPromiseReactionJob(fulfillReaction, value); + // c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]). + HostEnqueuePromiseJob(fulfillJob.Job, fulfillJob.Realm); + } else { + // a. Assert: The value of promise.[[PromiseState]] is rejected. + Assert(promise.PromiseState === 'rejected'); + // b. Let reason be promise.[[PromiseResult]]. + const reason = promise.PromiseResult; + // c. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle"). + if (promise.PromiseIsHandled === Value.false) { + HostPromiseRejectionTracker(promise, 'handle'); + } + // d. Let rejectJob be NewPromiseReactionJob(rejectReaction, reason). + const rejectJob = NewPromiseReactionJob(rejectReaction, reason); + // e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]). + HostEnqueuePromiseJob(rejectJob.Job, rejectJob.Realm); + } + // 12. Set promise.[[PromiseIsHandled]] to true. + promise.PromiseIsHandled = Value.true; + // 13. If resultCapability is undefined, then + if (resultCapability === Value.undefined) { + // a. Return undefined. + return Value.undefined; + } else { // 14. Else, + // a. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } +} diff --git a/engine262/src/abstract-ops/proxy-objects.mjs b/engine262/src/abstract-ops/proxy-objects.mjs new file mode 100644 index 0000000..85dec5f --- /dev/null +++ b/engine262/src/abstract-ops/proxy-objects.mjs @@ -0,0 +1,579 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { ValueSet } from '../helpers.mjs'; +import { + Assert, + MakeBasicObject, + IsConstructor, + IsCallable, + Call, + Construct, + GetMethod, + CreateArrayFromList, + CreateListFromArrayLike, + IsExtensible, + IsPropertyKey, + SameValue, + ToBoolean, + ToPropertyDescriptor, + FromPropertyDescriptor, + CompletePropertyDescriptor, + IsCompatiblePropertyDescriptor, + IsDataDescriptor, + IsAccessorDescriptor, +} from './all.mjs'; + +// #sec-proxy-object-internal-methods-and-internal-slots-getprototypeof +function ProxyGetPrototypeOf() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'getPrototypeOf'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('getPrototypeOf'))); + if (trap === Value.undefined) { + return Q(target.GetPrototypeOf()); + } + const handlerProto = Q(Call(trap, handler, [target])); + if (Type(handlerProto) !== 'Object' && Type(handlerProto) !== 'Null') { + return surroundingAgent.Throw('TypeError', 'ProxyGetPrototypeOfInvalid'); + } + const extensibleTarget = Q(IsExtensible(target)); + if (extensibleTarget === Value.true) { + return handlerProto; + } + const targetProto = Q(target.GetPrototypeOf()); + if (SameValue(handlerProto, targetProto) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyGetPrototypeOfNonExtensible'); + } + return handlerProto; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v +function ProxySetPrototypeOf(V) { + const O = this; + + Assert(Type(V) === 'Object' || Type(V) === 'Null'); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'setPrototypeOf'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('setPrototypeOf'))); + if (trap === Value.undefined) { + return Q(target.SetPrototypeOf(V)); + } + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target, V]))); + if (booleanTrapResult === Value.false) { + return Value.false; + } + const extensibleTarget = Q(IsExtensible(target)); + if (extensibleTarget === Value.true) { + return Value.true; + } + const targetProto = Q(target.GetPrototypeOf()); + if (SameValue(V, targetProto) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxySetPrototypeOfNonExtensible'); + } + return Value.true; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-isextensible +function ProxyIsExtensible() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'isExtensible'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('isExtensible'))); + if (trap === Value.undefined) { + return Q(IsExtensible(target)); + } + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target]))); + const targetResult = Q(IsExtensible(target)); + if (SameValue(booleanTrapResult, targetResult) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyIsExtensibleInconsistent', targetResult); + } + return booleanTrapResult; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-preventextensions +function ProxyPreventExtensions() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'preventExtensions'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('preventExtensions'))); + if (trap === Value.undefined) { + return Q(target.PreventExtensions()); + } + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target]))); + if (booleanTrapResult === Value.true) { + const extensibleTarget = Q(IsExtensible(target)); + if (extensibleTarget === Value.true) { + return surroundingAgent.Throw('TypeError', 'ProxyPreventExtensionsExtensible'); + } + } + return booleanTrapResult; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p +function ProxyGetOwnProperty(P) { + const O = this; + + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. Let handler be O.[[ProxyHandler]]. + const handler = O.ProxyHandler; + // 3. If handler is null, throw a TypeError exception. + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'getOwnPropertyDescriptor'); + } + // 4. Assert: Type(Handler) is Object. + Assert(Type(handler) === 'Object'); + // 5. Let target be O.[[ProxyTarget]]. + const target = O.ProxyTarget; + // 6. Let trap be ? Getmethod(handler, "getOwnPropertyDescriptor"). + const trap = Q(GetMethod(handler, new Value('getOwnPropertyDescriptor'))); + // 7. If trap is undefined, then + if (trap === Value.undefined) { + // a. Return ? target.[[GetOwnProperty]](P). + return Q(target.GetOwnProperty(P)); + } + // 8. Let trapResultObj be ? Call(trap, handler, « target, P »). + const trapResultObj = Q(Call(trap, handler, [target, P])); + // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. + if (Type(trapResultObj) !== 'Object' && Type(trapResultObj) !== 'Undefined') { + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorInvalid', P); + } + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + const targetDesc = Q(target.GetOwnProperty(P)); + // 11. If trapResultObj is undefined, then + if (trapResultObj === Value.undefined) { + // a. If targetDesc is undefined, return undefined. + if (targetDesc === Value.undefined) { + return Value.undefined; + } + // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if (targetDesc.Configurable === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorUndefined', P); + } + // c. Let extensibleTarget be ? IsExtensible(target). + const extensibleTarget = Q(IsExtensible(target)); + // d. If extensibleTarget is false, throw a TypeError exception. + if (extensibleTarget === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorNonExtensible', P); + } + // e. Return undefined. + return Value.undefined; + } + // 12. Let extensibleTarget be ? IsExtensible(target). + const extensibleTarget = Q(IsExtensible(target)); + // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). + const resultDesc = Q(ToPropertyDescriptor(trapResultObj)); + // 14. Call CompletePropertyDescriptor(resultDesc). + CompletePropertyDescriptor(resultDesc); + // 15. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc). + const valid = IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc); + // 16. If valid is false, throw a TypeError exception. + if (valid === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorIncompatible', P); + } + // 17. If resultDesc.[[Configurable]] is false, then + if (resultDesc.Configurable === Value.false) { + // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then + if (targetDesc === Value.undefined || targetDesc.Configurable === Value.true) { + // i. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorNonConfigurable', P); + } + // b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then + if ('Writable' in resultDesc && resultDesc.Writable === Value.false) { + // i. If targetDesc.[[Writable]] is true, throw a TypeError exception. + if (targetDesc.Writable === Value.true) { + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorNonConfigurableWritable', P); + } + } + } + // 18. Return resultDesc. + return resultDesc; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc +function ProxyDefineOwnProperty(P, Desc) { + const O = this; + + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. Let handler be O.[[ProxyHandler]]. + const handler = O.ProxyHandler; + // 3. If handler is null, throw a TypeError exception. + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'defineProperty'); + } + // 4. Assert: Type(handler) is Object. + Assert(Type(handler) === 'Object'); + // 5. Let target be O.[[ProxyTarget]]. + const target = O.ProxyTarget; + // 6. Let trap be ? GetMethod(handler, "defineProperty"). + const trap = Q(GetMethod(handler, new Value('defineProperty'))); + // 7. If trap is undefined, then + if (trap === Value.undefined) { + // a. Return ? target.[[DefineOwnProperty]](P, Desc). + return Q(target.DefineOwnProperty(P, Desc)); + } + // 8. Let descObj be FromPropertyDescriptor(Desc). + const descObj = FromPropertyDescriptor(Desc); + // 9. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)). + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target, P, descObj]))); + // 10. If booleanTrapResult is false, return false. + if (booleanTrapResult === Value.false) { + return Value.false; + } + // 11. Let targetDesc be ? target.[[GetOwnProperty]](P). + const targetDesc = Q(target.GetOwnProperty(P)); + // 12. Let extensibleTarget be ? IsExtensible(target). + const extensibleTarget = Q(IsExtensible(target)); + let settingConfigFalse; + // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then + if (Desc.Configurable !== undefined && Desc.Configurable === Value.false) { + // a. Let settingConfigFalse be true. + settingConfigFalse = true; + } else { + // Else, let settingConfigFalse be false. + settingConfigFalse = false; + } + // 15. If targetDesc is undefined, then + if (targetDesc === Value.undefined) { + // a. If extensibleTarget is false, throw a TypeError exception. + if (extensibleTarget === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyDefinePropertyNonExtensible', P); + } + // b. If settingConfigFalse is true, throw a TypeError exception. + if (settingConfigFalse === true) { + return surroundingAgent.Throw('TypeError', 'ProxyDefinePropertyNonConfigurable', P); + } + } else { + // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception. + if (IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyDefinePropertyIncompatible', P); + } + // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. + if (settingConfigFalse === true && targetDesc.Configurable === Value.true) { + return surroundingAgent.Throw('TypeError', 'ProxyDefinePropertyNonConfigurable', P); + } + // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then + if (IsDataDescriptor(targetDesc) + && targetDesc.Configurable === Value.false + && targetDesc.Writable === Value.true) { + // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception. + if ('Writable' in Desc && Desc.Writable === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyDefinePropertyNonConfigurableWritable', P); + } + } + } + return Value.true; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p +function ProxyHasProperty(P) { + const O = this; + + Assert(IsPropertyKey(P)); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'has'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('has'))); + if (trap === Value.undefined) { + return Q(target.HasProperty(P)); + } + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target, P]))); + if (booleanTrapResult === Value.false) { + const targetDesc = Q(target.GetOwnProperty(P)); + if (targetDesc !== Value.undefined) { + if (targetDesc.Configurable === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyHasNonConfigurable', P); + } + const extensibleTarget = Q(IsExtensible(target)); + if (extensibleTarget === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyHasNonExtensible', P); + } + } + } + return booleanTrapResult; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver +function ProxyGet(P, Receiver) { + const O = this; + + Assert(IsPropertyKey(P)); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'get'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('get'))); + if (trap === Value.undefined) { + return Q(target.Get(P, Receiver)); + } + const trapResult = Q(Call(trap, handler, [target, P, Receiver])); + const targetDesc = Q(target.GetOwnProperty(P)); + if (targetDesc !== Value.undefined && targetDesc.Configurable === Value.false) { + if (IsDataDescriptor(targetDesc) === true && targetDesc.Writable === Value.false) { + if (SameValue(trapResult, targetDesc.Value) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyGetNonConfigurableData', P); + } + } + if (IsAccessorDescriptor(targetDesc) === true && targetDesc.Get === Value.undefined) { + if (trapResult !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ProxyGetNonConfigurableAccessor', P); + } + } + } + return trapResult; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver +function ProxySet(P, V, Receiver) { + const O = this; + + Assert(IsPropertyKey(P)); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'set'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('set'))); + if (trap === Value.undefined) { + return Q(target.Set(P, V, Receiver)); + } + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target, P, V, Receiver]))); + if (booleanTrapResult === Value.false) { + return Value.false; + } + const targetDesc = Q(target.GetOwnProperty(P)); + if (targetDesc !== Value.undefined && targetDesc.Configurable === Value.false) { + if (IsDataDescriptor(targetDesc) === true && targetDesc.Writable === Value.false) { + if (SameValue(V, targetDesc.Value) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxySetFrozenData', P); + } + } + if (IsAccessorDescriptor(targetDesc) === true) { + if (targetDesc.Set === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ProxySetFrozenAccessor', P); + } + } + } + return Value.true; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-delete-p +function ProxyDelete(P) { + const O = this; + + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. Let handler be O.[[ProxyHandler]]. + const handler = O.ProxyHandler; + // 3. If handler is null, throw a TypeError exception. + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'deleteProperty'); + } + // 4. Assert: Type(handler) is Object. + Assert(Type(handler) === 'Object'); + // 5. Let target be O.[[ProxyTarget]]. + const target = O.ProxyTarget; + // 6. Let trap be ? GetMethod(handler, "deleteProperty"). + const trap = Q(GetMethod(handler, new Value('deleteProperty'))); + // 7. If trap is undefined, then + if (trap === Value.undefined) { + // a. Return ? target.[[Delete]](P). + return Q(target.Delete(P)); + } + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + const booleanTrapResult = ToBoolean(Q(Call(trap, handler, [target, P]))); + // 9. If booleanTrapResult is false, return false. + if (booleanTrapResult === Value.false) { + return Value.false; + } + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + const targetDesc = Q(target.GetOwnProperty(P)); + // 11. If targetDesc is undefined, return true. + if (targetDesc === Value.undefined) { + return Value.true; + } + // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if (targetDesc.Configurable === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyDeletePropertyNonConfigurable', P); + } + // 13. Let extensibleTarget be ? IsExtensible(target). + const extensibleTarget = Q(IsExtensible(target)); + // 14. If extensibleTarget is false, throw a TypeError exception. + if (extensibleTarget === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyDeletePropertyNonExtensible', P); + } + // 15. Return true. + return Value.true; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys +function ProxyOwnPropertyKeys() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'ownKeys'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('ownKeys'))); + if (trap === Value.undefined) { + return Q(target.OwnPropertyKeys()); + } + const trapResultArray = Q(Call(trap, handler, [target])); + const trapResult = Q(CreateListFromArrayLike(trapResultArray, ['String', 'Symbol'])); + if (new ValueSet(trapResult).size !== trapResult.length) { + return surroundingAgent.Throw('TypeError', 'ProxyOwnKeysDuplicateEntries'); + } + const extensibleTarget = Q(IsExtensible(target)); + const targetKeys = Q(target.OwnPropertyKeys()); + // Assert: targetKeys is a List containing only String and Symbol values. + // Assert: targetKeys contains no duplicate entries. + const targetConfigurableKeys = []; + const targetNonconfigurableKeys = []; + for (const key of targetKeys) { + const desc = Q(target.GetOwnProperty(key)); + if (desc !== Value.undefined && desc.Configurable === Value.false) { + targetNonconfigurableKeys.push(key); + } else { + targetConfigurableKeys.push(key); + } + } + if (extensibleTarget === Value.true && targetNonconfigurableKeys.length === 0) { + return trapResult; + } + const uncheckedResultKeys = new ValueSet(trapResult); + for (const key of targetNonconfigurableKeys) { + if (!uncheckedResultKeys.has(key)) { + return surroundingAgent.Throw('TypeError', 'ProxyOwnKeysMissing', 'non-configurable key'); + } + uncheckedResultKeys.delete(key); + } + if (extensibleTarget === Value.true) { + return trapResult; + } + for (const key of targetConfigurableKeys) { + if (!uncheckedResultKeys.has(key)) { + return surroundingAgent.Throw('TypeError', 'ProxyOwnKeysMissing', 'configurable key'); + } + uncheckedResultKeys.delete(key); + } + if (uncheckedResultKeys.size > 0) { + return surroundingAgent.Throw('TypeError', 'ProxyOwnKeysNonExtensible'); + } + return trapResult; +} + +// #sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist +function ProxyCall(thisArgument, argumentsList) { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'apply'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + const trap = Q(GetMethod(handler, new Value('apply'))); + if (trap === Value.undefined) { + return Q(Call(target, thisArgument, argumentsList)); + } + const argArray = X(CreateArrayFromList(argumentsList)); + return Q(Call(trap, handler, [target, thisArgument, argArray])); +} + +// #sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget +function ProxyConstruct(argumentsList, newTarget) { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'construct'); + } + Assert(Type(handler) === 'Object'); + const target = O.ProxyTarget; + Assert(IsConstructor(target) === Value.true); + const trap = Q(GetMethod(handler, new Value('construct'))); + if (trap === Value.undefined) { + return Q(Construct(target, argumentsList, newTarget)); + } + const argArray = X(CreateArrayFromList(argumentsList)); + const newObj = Q(Call(trap, handler, [target, argArray, newTarget])); + if (Type(newObj) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', newObj); + } + return newObj; +} + +export function isProxyExoticObject(O) { + return 'ProxyHandler' in O; +} + +// #sec-proxycreate +export function ProxyCreate(target, handler) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'CannotCreateProxyWith', 'non-object', 'target'); + } + // 2. If Type(handler) is not Object, throw a TypeError exception. + if (Type(handler) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'CannotCreateProxyWith', 'non-object', 'handler'); + } + // 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »). + const P = X(MakeBasicObject(['ProxyHandler', 'ProxyTarget'])); + // 4. Set P's essential internal methods, except for [[Call]] and [[Construct]], to the definitions specified in 9.5. + P.GetPrototypeOf = ProxyGetPrototypeOf; + P.SetPrototypeOf = ProxySetPrototypeOf; + P.IsExtensible = ProxyIsExtensible; + P.PreventExtensions = ProxyPreventExtensions; + P.GetOwnProperty = ProxyGetOwnProperty; + P.DefineOwnProperty = ProxyDefineOwnProperty; + P.HasProperty = ProxyHasProperty; + P.Get = ProxyGet; + P.Set = ProxySet; + P.Delete = ProxyDelete; + P.OwnPropertyKeys = ProxyOwnPropertyKeys; + // 5. If IsCallable(target) is true, then + if (IsCallable(target) === Value.true) { + // a. Set P.[[Call]] as specified in #sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist. + P.Call = ProxyCall; + // b. If IsConstructor(target) is true, then + if (IsConstructor(target) === Value.true) { + // i. Set P.[[Construct]] as specified in #sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget. + P.Construct = ProxyConstruct; + } + } + // 6. Set P.[[ProxyTarget]] to target. + P.ProxyTarget = target; + // 7. Set P.[[ProxyHandler]] to handler. + P.ProxyHandler = handler; + // 8. Return P. + return P; +} diff --git a/engine262/src/abstract-ops/realms.mjs b/engine262/src/abstract-ops/realms.mjs new file mode 100644 index 0000000..3711fbf --- /dev/null +++ b/engine262/src/abstract-ops/realms.mjs @@ -0,0 +1,366 @@ +import { + Descriptor, + Value, +} from '../value.mjs'; +import { NewGlobalEnvironment } from '../environment.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapObjectPrototype } from '../intrinsics/ObjectPrototype.mjs'; +import { BootstrapObject } from '../intrinsics/Object.mjs'; +import { BootstrapArrayPrototype } from '../intrinsics/ArrayPrototype.mjs'; +import { BootstrapArray } from '../intrinsics/Array.mjs'; +import { BootstrapBigInt } from '../intrinsics/BigInt.mjs'; +import { BootstrapBigIntPrototype } from '../intrinsics/BigIntPrototype.mjs'; +import { BootstrapBooleanPrototype } from '../intrinsics/BooleanPrototype.mjs'; +import { BootstrapBoolean } from '../intrinsics/Boolean.mjs'; +import { BootstrapNumberPrototype } from '../intrinsics/NumberPrototype.mjs'; +import { BootstrapNumber } from '../intrinsics/Number.mjs'; +import { BootstrapFunctionPrototype } from '../intrinsics/FunctionPrototype.mjs'; +import { BootstrapFunction } from '../intrinsics/Function.mjs'; +import { BootstrapSymbolPrototype } from '../intrinsics/SymbolPrototype.mjs'; +import { BootstrapSymbol } from '../intrinsics/Symbol.mjs'; +import { BootstrapMath } from '../intrinsics/Math.mjs'; +import { BootstrapDatePrototype } from '../intrinsics/DatePrototype.mjs'; +import { BootstrapDate } from '../intrinsics/Date.mjs'; +import { BootstrapRegExpPrototype } from '../intrinsics/RegExpPrototype.mjs'; +import { BootstrapRegExp } from '../intrinsics/RegExp.mjs'; +import { BootstrapPromisePrototype } from '../intrinsics/PromisePrototype.mjs'; +import { BootstrapPromise } from '../intrinsics/Promise.mjs'; +import { BootstrapProxy } from '../intrinsics/Proxy.mjs'; +import { BootstrapReflect } from '../intrinsics/Reflect.mjs'; +import { BootstrapStringPrototype } from '../intrinsics/StringPrototype.mjs'; +import { BootstrapString } from '../intrinsics/String.mjs'; +import { BootstrapErrorPrototype } from '../intrinsics/ErrorPrototype.mjs'; +import { BootstrapError } from '../intrinsics/Error.mjs'; +import { BootstrapNativeError } from '../intrinsics/NativeError.mjs'; +import { BootstrapIteratorPrototype } from '../intrinsics/IteratorPrototype.mjs'; +import { BootstrapAsyncIteratorPrototype } from '../intrinsics/AsyncIteratorPrototype.mjs'; +import { BootstrapArrayIteratorPrototype } from '../intrinsics/ArrayIteratorPrototype.mjs'; +import { BootstrapMapIteratorPrototype } from '../intrinsics/MapIteratorPrototype.mjs'; +import { BootstrapSetIteratorPrototype } from '../intrinsics/SetIteratorPrototype.mjs'; +import { BootstrapStringIteratorPrototype } from '../intrinsics/StringIteratorPrototype.mjs'; +import { BootstrapRegExpStringIteratorPrototype } from '../intrinsics/RegExpStringIteratorPrototype.mjs'; +import { BootstrapForInIteratorPrototype } from '../intrinsics/ForInIteratorPrototype.mjs'; +import { BootstrapMapPrototype } from '../intrinsics/MapPrototype.mjs'; +import { BootstrapMap } from '../intrinsics/Map.mjs'; +import { BootstrapSetPrototype } from '../intrinsics/SetPrototype.mjs'; +import { BootstrapSet } from '../intrinsics/Set.mjs'; +import { BootstrapGenerator } from '../intrinsics/Generator.mjs'; +import { BootstrapGeneratorFunction } from '../intrinsics/GeneratorFunction.mjs'; +import { BootstrapGeneratorPrototype } from '../intrinsics/GeneratorPrototype.mjs'; +import { BootstrapAsyncFunctionPrototype } from '../intrinsics/AsyncFunctionPrototype.mjs'; +import { BootstrapAsyncFunction } from '../intrinsics/AsyncFunction.mjs'; +import { BootstrapAsyncGenerator } from '../intrinsics/AsyncGenerator.mjs'; +import { BootstrapAsyncGeneratorFunction } from '../intrinsics/AsyncGeneratorFunction.mjs'; +import { BootstrapAsyncGeneratorPrototype } from '../intrinsics/AsyncGeneratorPrototype.mjs'; +import { BootstrapAsyncFromSyncIteratorPrototype } from '../intrinsics/AsyncFromSyncIteratorPrototype.mjs'; +import { BootstrapArrayBuffer } from '../intrinsics/ArrayBuffer.mjs'; +import { BootstrapArrayBufferPrototype } from '../intrinsics/ArrayBufferPrototype.mjs'; +import { BootstrapJSON } from '../intrinsics/JSON.mjs'; +import { BootstrapEval } from '../intrinsics/eval.mjs'; +import { BootstrapIsFinite } from '../intrinsics/isFinite.mjs'; +import { BootstrapIsNaN } from '../intrinsics/isNaN.mjs'; +import { BootstrapParseFloat } from '../intrinsics/parseFloat.mjs'; +import { BootstrapParseInt } from '../intrinsics/parseInt.mjs'; +import { BootstrapURIHandling } from '../intrinsics/URIHandling.mjs'; +import { BootstrapThrowTypeError } from '../intrinsics/ThrowTypeError.mjs'; +import { BootstrapTypedArray } from '../intrinsics/TypedArray.mjs'; +import { BootstrapTypedArrayPrototype } from '../intrinsics/TypedArrayPrototype.mjs'; +import { BootstrapTypedArrayConstructors } from '../intrinsics/TypedArrayConstructors.mjs'; +import { BootstrapTypedArrayPrototypes } from '../intrinsics/TypedArrayPrototypes.mjs'; +import { BootstrapDataView } from '../intrinsics/DataView.mjs'; +import { BootstrapDataViewPrototype } from '../intrinsics/DataViewPrototype.mjs'; +import { BootstrapWeakMapPrototype } from '../intrinsics/WeakMapPrototype.mjs'; +import { BootstrapWeakMap } from '../intrinsics/WeakMap.mjs'; +import { BootstrapWeakSetPrototype } from '../intrinsics/WeakSetPrototype.mjs'; +import { BootstrapWeakSet } from '../intrinsics/WeakSet.mjs'; +import { BootstrapAggregateError } from '../intrinsics/AggregateError.mjs'; +import { BootstrapAggregateErrorPrototype } from '../intrinsics/AggregateErrorPrototype.mjs'; +import { BootstrapWeakRefPrototype } from '../intrinsics/WeakRefPrototype.mjs'; +import { BootstrapWeakRef } from '../intrinsics/WeakRef.mjs'; +import { BootstrapFinalizationRegistryPrototype } from '../intrinsics/FinalizationRegistryPrototype.mjs'; +import { BootstrapFinalizationRegistry } from '../intrinsics/FinalizationRegistry.mjs'; +import { + Assert, + DefinePropertyOrThrow, + OrdinaryObjectCreate, +} from './all.mjs'; + +// 8.2 #sec-code-realms +export class Realm { + constructor() { + this.Intrinsics = undefined; + this.GlobalObject = undefined; + this.GlobalEnv = undefined; + this.TemplateMap = undefined; + this.HostDefined = undefined; + + this.randomState = undefined; + } + + mark(m) { + m(this.GlobalObject); + m(this.GlobalEnv); + for (const v of Object.values(this.Intrinsics)) { + m(v); + } + for (const v of Object.values(this.TemplateMap)) { + m(v); + } + } +} + +// 8.2.1 #sec-createrealm +export function CreateRealm() { + const realmRec = new Realm(); + CreateIntrinsics(realmRec); + realmRec.GlobalObject = Value.undefined; + realmRec.GlobalEnv = Value.undefined; + realmRec.TemplateMap = []; + return realmRec; +} + +function AddRestrictedFunctionProperties(F, realm) { + Assert(realm.Intrinsics['%ThrowTypeError%']); + const thrower = realm.Intrinsics['%ThrowTypeError%']; + X(DefinePropertyOrThrow(F, new Value('caller'), Descriptor({ + Get: thrower, + Set: thrower, + Enumerable: Value.false, + Configurable: Value.true, + }))); + X(DefinePropertyOrThrow(F, new Value('arguments'), Descriptor({ + Get: thrower, + Set: thrower, + Enumerable: Value.false, + Configurable: Value.true, + }))); +} + +// #sec-createintrinsics +export function CreateIntrinsics(realmRec) { + const intrinsics = Object.create(null); + realmRec.Intrinsics = intrinsics; + + intrinsics['%Object.prototype%'] = OrdinaryObjectCreate(Value.null); + + BootstrapFunctionPrototype(realmRec); + BootstrapObjectPrototype(realmRec); + BootstrapThrowTypeError(realmRec); + + BootstrapEval(realmRec); + BootstrapIsFinite(realmRec); + BootstrapIsNaN(realmRec); + BootstrapParseFloat(realmRec); + BootstrapParseInt(realmRec); + BootstrapURIHandling(realmRec); + + BootstrapObject(realmRec); + + BootstrapErrorPrototype(realmRec); + BootstrapError(realmRec); + BootstrapNativeError(realmRec); + BootstrapAggregateErrorPrototype(realmRec); + BootstrapAggregateError(realmRec); + + BootstrapFunction(realmRec); + + BootstrapIteratorPrototype(realmRec); + BootstrapAsyncIteratorPrototype(realmRec); + BootstrapArrayIteratorPrototype(realmRec); + BootstrapMapIteratorPrototype(realmRec); + BootstrapSetIteratorPrototype(realmRec); + BootstrapStringIteratorPrototype(realmRec); + BootstrapRegExpStringIteratorPrototype(realmRec); + BootstrapForInIteratorPrototype(realmRec); + + BootstrapStringPrototype(realmRec); + BootstrapString(realmRec); + + BootstrapArrayPrototype(realmRec); + BootstrapArray(realmRec); + + BootstrapBooleanPrototype(realmRec); + BootstrapBoolean(realmRec); + + BootstrapNumberPrototype(realmRec); + BootstrapNumber(realmRec); + + BootstrapBigIntPrototype(realmRec); + BootstrapBigInt(realmRec); + + BootstrapSymbolPrototype(realmRec); + BootstrapSymbol(realmRec); + + BootstrapPromisePrototype(realmRec); + BootstrapPromise(realmRec); + + BootstrapProxy(realmRec); + + BootstrapReflect(realmRec); + + BootstrapMath(realmRec); + + BootstrapDatePrototype(realmRec); + BootstrapDate(realmRec); + + BootstrapRegExpPrototype(realmRec); + BootstrapRegExp(realmRec); + + BootstrapSetPrototype(realmRec); + BootstrapSet(realmRec); + + BootstrapMapPrototype(realmRec); + BootstrapMap(realmRec); + + BootstrapGeneratorPrototype(realmRec); + BootstrapGenerator(realmRec); + BootstrapGeneratorFunction(realmRec); + + BootstrapAsyncFunctionPrototype(realmRec); + BootstrapAsyncFunction(realmRec); + + BootstrapAsyncGeneratorPrototype(realmRec); + BootstrapAsyncGenerator(realmRec); + BootstrapAsyncGeneratorFunction(realmRec); + + BootstrapAsyncFromSyncIteratorPrototype(realmRec); + + BootstrapArrayBufferPrototype(realmRec); + BootstrapArrayBuffer(realmRec); + + BootstrapTypedArrayPrototype(realmRec); + BootstrapTypedArray(realmRec); + BootstrapTypedArrayPrototypes(realmRec); + BootstrapTypedArrayConstructors(realmRec); + + BootstrapDataViewPrototype(realmRec); + BootstrapDataView(realmRec); + + BootstrapJSON(realmRec); + + BootstrapWeakMapPrototype(realmRec); + BootstrapWeakMap(realmRec); + BootstrapWeakSetPrototype(realmRec); + BootstrapWeakSet(realmRec); + + BootstrapWeakRefPrototype(realmRec); + BootstrapWeakRef(realmRec); + + BootstrapFinalizationRegistryPrototype(realmRec); + BootstrapFinalizationRegistry(realmRec); + + AddRestrictedFunctionProperties(intrinsics['%Function.prototype%'], realmRec); + + return intrinsics; +} + +// 8.2.3 #sec-setrealmglobalobject +export function SetRealmGlobalObject(realmRec, globalObj, thisValue) { + const intrinsics = realmRec.Intrinsics; + if (globalObj === Value.undefined) { + globalObj = OrdinaryObjectCreate(intrinsics['%Object.prototype%']); + } + if (thisValue === Value.undefined) { + thisValue = globalObj; + } + realmRec.GlobalObject = globalObj; + const newGlobalEnv = NewGlobalEnvironment(globalObj, thisValue); + realmRec.GlobalEnv = newGlobalEnv; + return realmRec; +} + +// 8.2.4 #sec-setdefaultglobalbindings +export function SetDefaultGlobalBindings(realmRec) { + const global = realmRec.GlobalObject; + + // Value Properties of the Global Object + [ + ['Infinity', new Value(Infinity)], + ['NaN', new Value(NaN)], + ['undefined', Value.undefined], + ].forEach(([name, value]) => { + Q(DefinePropertyOrThrow(global, new Value(name), Descriptor({ + Value: value, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + }); + + Q(DefinePropertyOrThrow(global, new Value('globalThis'), Descriptor({ + Value: realmRec.GlobalEnv.GlobalThisValue, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + [ + // Function Properties of the Global Object + 'eval', + 'isFinite', + 'isNaN', + 'parseFloat', + 'parseInt', + 'decodeURI', + 'decodeURIComponent', + 'encodeURI', + 'encodeURIComponent', + + // Constructor Properties of the Global Object + 'AggregateError', + 'Array', + 'ArrayBuffer', + 'Boolean', + 'BigInt', + 'BigInt64Array', + 'BigUint64Array', + 'DataView', + 'Date', + 'Error', + 'EvalError', + 'FinalizationRegistry', + 'Float32Array', + 'Float64Array', + 'Function', + 'Int8Array', + 'Int16Array', + 'Int32Array', + 'Map', + 'Number', + 'Object', + 'Promise', + 'Proxy', + 'RangeError', + 'ReferenceError', + 'RegExp', + 'Set', + // 'SharedArrayBuffer', + 'String', + 'Symbol', + 'SyntaxError', + 'TypeError', + 'Uint8Array', + 'Uint8ClampedArray', + 'Uint16Array', + 'Uint32Array', + 'URIError', + 'WeakMap', + 'WeakRef', + 'WeakSet', + + // Other Properties of the Global Object + // 'Atomics', + 'JSON', + 'Math', + 'Reflect', + ].forEach((name) => { + Q(DefinePropertyOrThrow(global, new Value(name), Descriptor({ + Value: realmRec.Intrinsics[`%${name}%`], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + }); + + return global; +} diff --git a/engine262/src/abstract-ops/reference-operations.mjs b/engine262/src/abstract-ops/reference-operations.mjs new file mode 100644 index 0000000..9b9fb87 --- /dev/null +++ b/engine262/src/abstract-ops/reference-operations.mjs @@ -0,0 +1,142 @@ +import { surroundingAgent } from '../engine.mjs'; +import { PrimitiveValue, Type, Value } from '../value.mjs'; +import { + NormalCompletion, + Q, + ReturnIfAbrupt, + X, +} from '../completion.mjs'; +import { + Assert, + GetGlobalObject, + ToObject, + Set, +} from './all.mjs'; + + +// 6.2.4.1 #sec-getbase +export function GetBase(V) { + Assert(Type(V) === 'Reference'); + return V.BaseValue; +} + +// 6.2.4.2 #sec-getreferencedname +export function GetReferencedName(V) { + Assert(Type(V) === 'Reference'); + return V.ReferencedName; +} + +// 6.2.4.3 #sec-isstrictreference +export function IsStrictReference(V) { + Assert(Type(V) === 'Reference'); + return V.StrictReference; +} + +// 6.2.4.4 #sec-hasprimitivebase +export function HasPrimitiveBase(V) { + Assert(Type(V) === 'Reference'); + if (V.BaseValue instanceof PrimitiveValue) { + return Value.true; + } + return Value.false; +} + +// 6.2.4.5 #sec-ispropertyreference +export function IsPropertyReference(V) { + Assert(Type(V) === 'Reference'); + if (Type(V.BaseValue) === 'Object' || HasPrimitiveBase(V) === Value.true) { + return Value.true; + } + return Value.false; +} + +// 6.2.4.6 #sec-isunresolvablereference +export function IsUnresolvableReference(V) { + Assert(Type(V) === 'Reference'); + if (V.BaseValue === Value.undefined) { + return Value.true; + } + return Value.false; +} + +// 6.2.4.7 #sec-issuperreference +export function IsSuperReference(V) { + // 1. Assert: Type(V) is Reference. + Assert(Type(V) === 'Reference'); + // 2. If V has a thisValue component, return true; otherwise return false. + return 'thisValue' in V ? Value.true : Value.false; +} + +// 6.2.4.8 #sec-getvalue +export function GetValue(V) { + ReturnIfAbrupt(V); + if (Type(V) !== 'Reference') { + return V; + } + let base = GetBase(V); + if (IsUnresolvableReference(V) === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', GetReferencedName(V)); + } + if (IsPropertyReference(V) === Value.true) { + if (HasPrimitiveBase(V) === Value.true) { + Assert(base !== Value.undefined && base !== Value.null); + base = X(ToObject(base)); + } + return Q(base.Get(GetReferencedName(V), GetThisValue(V))); + } else { + return Q(base.GetBindingValue(GetReferencedName(V), IsStrictReference(V))); + } +} + +// 6.2.4.9 #sec-putvalue +export function PutValue(V, W) { + ReturnIfAbrupt(V); + ReturnIfAbrupt(W); + if (Type(V) !== 'Reference') { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', V); + } + let base = GetBase(V); + if (IsUnresolvableReference(V) === Value.true) { + if (IsStrictReference(V) === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', GetReferencedName(V)); + } + const globalObj = GetGlobalObject(); + return Q(Set(globalObj, GetReferencedName(V), W, Value.false)); + } else if (IsPropertyReference(V) === Value.true) { + if (HasPrimitiveBase(V) === Value.true) { + Assert(Type(base) !== 'Undefined' && Type(base) !== 'Null'); + base = X(ToObject(base)); + } + const succeeded = Q(base.Set(GetReferencedName(V), W, GetThisValue(V))); + if (succeeded === Value.false && IsStrictReference(V) === Value.true) { + return surroundingAgent.Throw('TypeError', 'CannotSetProperty', GetReferencedName(V), base); + } + return NormalCompletion(Value.undefined); + } else { + return Q(base.SetMutableBinding(GetReferencedName(V), W, IsStrictReference(V))); + } +} + +// 6.2.4.10 #sec-getthisvalue +export function GetThisValue(V) { + // 1. Assert: IsPropertyReference(V) is true. + Assert(IsPropertyReference(V) === Value.true); + // 2. If IsSuperReference(V) is true, then + if (IsSuperReference(V) === Value.true) { + // a. Return the value of the thisValue component of the reference V. + return V.thisValue; + } + // 3. Return GetBase(V). + return GetBase(V); +} + +// 6.2.4.11 #sec-initializereferencedbinding +export function InitializeReferencedBinding(V, W) { + ReturnIfAbrupt(V); + ReturnIfAbrupt(W); + Assert(Type(V) === 'Reference'); + Assert(IsUnresolvableReference(V) === Value.false); + const base = GetBase(V); + Assert(Type(base) === 'EnvironmentRecord'); + return base.InitializeBinding(GetReferencedName(V), W); +} diff --git a/engine262/src/abstract-ops/regexp-objects.mjs b/engine262/src/abstract-ops/regexp-objects.mjs new file mode 100644 index 0000000..aa4ad32 --- /dev/null +++ b/engine262/src/abstract-ops/regexp-objects.mjs @@ -0,0 +1,254 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Descriptor, Value, Type } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { Evaluate_Pattern } from '../runtime-semantics/all.mjs'; +import { ParsePattern } from '../parse.mjs'; +import { isLineTerminator } from '../parser/Lexer.mjs'; +import { + ArrayCreate, + Assert, + CreateArrayFromList, + CreateDataPropertyOrThrow, + DefinePropertyOrThrow, + OrdinaryCreateFromConstructor, + OrdinaryObjectCreate, + Set, + ToString, +} from './all.mjs'; + +// #sec-regexpalloc +export function RegExpAlloc(newTarget) { + const obj = Q(OrdinaryCreateFromConstructor(newTarget, '%RegExp.prototype%', ['RegExpMatcher', 'OriginalSource', 'OriginalFlags'])); + X(DefinePropertyOrThrow(obj, new Value('lastIndex'), Descriptor({ + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + return obj; +} + +// #sec-regexpinitialize +export function RegExpInitialize(obj, pattern, flags) { + let P; + // 1. If pattern is undefined, let P be the empty String. + if (pattern === Value.undefined) { + P = new Value(''); + } else { // 2. Else, let P be ? ToString(pattern). + P = Q(ToString(pattern)); + } + let F; + // 3. If flags is undefined, let F be the empty String. + if (flags === Value.undefined) { + F = new Value(''); + } else { // 4. Else, let F be ? ToString(flags). + F = Q(ToString(flags)); + } + const f = F.stringValue(); + // 5. If F contains any code unit other than "g", "i", "m", "s", "u", or "y" or if it contains the same code unit more than once, throw a SyntaxError exception. + if (/^[gimsuy]*$/.test(f) === false || (new globalThis.Set(f).size !== f.length)) { + return surroundingAgent.Throw('SyntaxError', 'InvalidRegExpFlags', f); + } + // 6. If F contains "u", let u be true; else let u be false. + const u = f.includes('u'); + // 7. If u is true, then + // a. Let patternText be ! UTF16DecodeString(P). + // b. Let patternCharacters be a List whose elements are the code points of patternText. + // 8. Else, + // a. Let patternText be the result of interpreting each of P's 16-bit elements as a Unicode BMP code point. UTF-16 decoding is not applied to the elements. + // b. Let patternCharacters be a List whose elements are the code unit elements of P. + // 9. Let parseResult be ParsePattern(patternText, u). + const patternText = P.stringValue(); + const parseResult = ParsePattern(patternText, u); + // 10. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception. + if (Array.isArray(parseResult)) { + return surroundingAgent.Throw(parseResult[0]); + } + obj.parsedPattern = parseResult; + // 11. Assert: parseResult is a Parse Node for Pattern. + Assert(parseResult.type === 'Pattern'); + // 12. Set obj.[[OriginalSource]] to P. + obj.OriginalSource = P; + // 13. Set obj.[[OriginalFlags]] to F. + obj.OriginalFlags = F; + // 14. Set obj.[[RegExpMatcher]] to the Abstract Closure that evaluates parseResult by + // applying the semantics provided in 21.2.2 using patternCharacters as the pattern's + // List of SourceCharacter values and F as the flag parameters. + obj.RegExpMatcher = Evaluate_Pattern(parseResult, F.stringValue()); + // 15. Perform ? Set(obj, "lastIndex", 0, true). + Q(Set(obj, new Value('lastIndex'), new Value(0), Value.true)); + // 16. Return obj. + return obj; +} + +// 21.2.3.2.3 #sec-regexpcreate +export function RegExpCreate(P, F) { + const obj = Q(RegExpAlloc(surroundingAgent.intrinsic('%RegExp%'))); + return Q(RegExpInitialize(obj, P, F)); +} + +// #sec-escaperegexppattern +export function EscapeRegExpPattern(P, _F) { + const source = P.stringValue(); + if (source === '') { + return new Value('(:?)'); + } + let index = 0; + let escaped = ''; + let inClass = false; + while (index < source.length) { + const c = source[index]; + switch (c) { + case '\\': + index += 1; + if (isLineTerminator(source[index])) { + // nothing + } else { + escaped += '\\'; + } + break; + case '/': + index += 1; + if (inClass) { + escaped += '/'; + } else { + escaped += '\\/'; + } + break; + case '[': + inClass = true; + index += 1; + escaped += '['; + break; + case ']': + inClass = false; + index += 1; + escaped += ']'; + break; + case '\n': + index += 1; + escaped += '\\n'; + break; + case '\r': + index += 1; + escaped += '\\r'; + break; + case '\u2028': + index += 1; + escaped += '\\u2028'; + break; + case '\u2029': + index += 1; + escaped += '\\u2029'; + break; + default: + index += 1; + escaped += c; + break; + } + } + return new Value(escaped); +} + +// https://tc39.es/proposal-regexp-match-indices/#sec-getstringindex +export function GetStringIndex(S, Input, e) { + // 1. Assert: Type(S) is String. + Assert(Type(S) === 'String'); + // 2. Assert: Input is a List of the code points of S interpreted as a UTF-16 encoded string. + Assert(Array.isArray(Input)); + // 3. Assert: e is an integer value ≥ 0 and < the number of elements in Input. + Assert(e >= 0); + // 4. Let eUTF be the smallest index into S that corresponds to the character at element e of Input. + // If e is greater than or equal to the number of elements in Input, then eUTF is the number of code units in S. + let eUTF = 0; + if (e >= Input.length) { + eUTF = S.stringValue().length; + } else { + for (let i = 0; i < e; i += 1) { + eUTF += Input[i].length; + } + } + // 5. Return eUTF. + return eUTF; +} + +// https://tc39.es/proposal-regexp-match-indices/#sec-getmatchstring +export function GetMatchString(S, match) { + // 1. Assert: Type(S) is String. + Assert(Type(S) === 'String'); + // 2. Assert: match is a Match Record. + Assert('StartIndex' in match && 'EndIndex' in match); + // 3. Assert: match.[[StartIndex]] is an integer value ≥ 0 and < the length of S. + Assert(match.StartIndex >= 0 && match.StartIndex < S.stringValue().length); + // 4. Assert: match.[[EndIndex]] is an integer value ≥ match.[[StartIndex]] and ≤ the length of S. + Assert(match.EndIndex >= match.StartIndex && match.EndIndex <= S.stringValue().length); + // 5. Return the portion of S between offset match.[[StartIndex]] inclusive and offset match.[[EndIndex]] exclusive. + return new Value(S.stringValue().slice(match.StartIndex, match.EndIndex)); +} + +// https://tc39.es/proposal-regexp-match-indices/#sec-getmatchindicesarray +export function GetMatchIndicesArray(S, match) { + // 1. Assert: Type(S) is String. + Assert(Type(S) === 'String'); + // 2. Assert: match is a Match Record. + Assert('StartIndex' in match && 'EndIndex' in match); + // 3. Assert: match.[[StartIndex]] is an integer value ≥ 0 and < the length of S. + Assert(match.StartIndex >= 0 && match.StartIndex < S.stringValue().length); + // 4. Assert: match.[[EndIndex]] is an integer value ≥ match.[[StartIndex]] and ≤ the length of S. + Assert(match.EndIndex >= match.StartIndex && match.EndIndex <= S.stringValue().length); + // 1. Return CreateArrayFromList(« match.[[StartIndex]], match.[[EndIndex]] »). + return CreateArrayFromList([ + new Value(match.StartIndex), + new Value(match.EndIndex), + ]); +} + +// https://tc39.es/proposal-regexp-match-indices/#sec-makeindicesarray +export function MakeIndicesArray(S, indices, groupNames) { + // 1. Assert: Type(S) is String. + Assert(Type(S) === 'String'); + // 2. Assert: indices is a List. + Assert(Array.isArray(indices)); + // 3. Assert: groupNames is a List or is undefined. + Assert(Array.isArray(indices) || groupNames === Value.undefined); + // 4. Let n be the number of elements in indices. + const n = indices.length; + // 5. Assert: n < 2**32-1. + Assert(n < (2 ** 32) - 1); + // 6. Set A to ! ArrayCreate(n). + // 7. Assert: The value of A's "length" property is n. + const A = X(ArrayCreate(new Value(n))); + // 8. If groupNames is not undefined, then + let groups; + if (groupNames !== Value.undefined) { + // a. Let groups be ! ObjectCreate(null). + groups = X(OrdinaryObjectCreate(Value.null)); + } else { // 9. Else, + // a. Let groups be undefined. + groups = Value.undefined; + } + // 10. Perform ! CreateDataProperty(A, "groups", groups). + X(CreateDataPropertyOrThrow(A, new Value('groups'), groups)); + // 11. For each integer i such that i ≥ 0 and i < n, do + for (let i = 0; i < n; i += 1) { + // a. Let matchIndices be indices[i]. + const matchIndices = indices[i]; + // b. If matchIndices is not undefined, then + let matchIndicesArray; + if (matchIndices !== Value.undefined) { + // i. Let matchIndicesArray be ! GetMatchIndicesArray(S, matchIndices). + matchIndicesArray = X(GetMatchIndicesArray(S, matchIndices)); + } else { // c. Else, + // i. Let matchIndicesArray be undefined. + matchIndicesArray = Value.undefined; + } + // d. Perform ! CreateDataProperty(A, ! ToString(i), matchIndicesArray). + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(i))), matchIndicesArray)); + // e. If groupNames is not undefined and groupNames[i] is not undefined, then + if (groupNames !== Value.undefined && groupNames[i] !== Value.undefined) { + // i. Perform ! CreateDataProperty(groups, groupNames[i], matchIndicesArray). + X(CreateDataPropertyOrThrow(groups, groupNames[i], matchIndicesArray)); + } + } + // 12. Return A. + return A; +} diff --git a/engine262/src/abstract-ops/spec-types.mjs b/engine262/src/abstract-ops/spec-types.mjs new file mode 100644 index 0000000..fcca01e --- /dev/null +++ b/engine262/src/abstract-ops/spec-types.mjs @@ -0,0 +1,210 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + DataBlock, + Descriptor, + Type, + Value, +} from '../value.mjs'; +import { NormalCompletion, Q, X } from '../completion.mjs'; +import { + Assert, + CreateDataProperty, + Get, + HasProperty, + IsCallable, + OrdinaryObjectCreate, + ToBoolean, +} from './all.mjs'; + + +// 6.2.5.1 IsAccessorDescriptor +export function IsAccessorDescriptor(Desc) { + if (Type(Desc) === 'Undefined') { + return false; + } + + if (Desc.Get === undefined && Desc.Set === undefined) { + return false; + } + + return true; +} + +// 6.2.5.2 IsDataDescriptor +export function IsDataDescriptor(Desc) { + if (Type(Desc) === 'Undefined') { + return false; + } + + if (Desc.Value === undefined && Desc.Writable === undefined) { + return false; + } + + return true; +} + +// 6.2.5.3 IsGenericDescriptor +export function IsGenericDescriptor(Desc) { + if (Type(Desc) === 'Undefined') { + return false; + } + + if (!IsAccessorDescriptor(Desc) && !IsDataDescriptor(Desc)) { + return true; + } + + return false; +} + +// 6.2.5.4 #sec-frompropertydescriptor +export function FromPropertyDescriptor(Desc) { + if (Type(Desc) === 'Undefined') { + return Value.undefined; + } + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + if (Desc.Value !== undefined) { + X(CreateDataProperty(obj, new Value('value'), Desc.Value)); + } + if (Desc.Writable !== undefined) { + X(CreateDataProperty(obj, new Value('writable'), Desc.Writable)); + } + if (Desc.Get !== undefined) { + X(CreateDataProperty(obj, new Value('get'), Desc.Get)); + } + if (Desc.Set !== undefined) { + X(CreateDataProperty(obj, new Value('set'), Desc.Set)); + } + if (Desc.Enumerable !== undefined) { + X(CreateDataProperty(obj, new Value('enumerable'), Desc.Enumerable)); + } + if (Desc.Configurable !== undefined) { + X(CreateDataProperty(obj, new Value('configurable'), Desc.Configurable)); + } + // Assert: All of the above CreateDataProperty operations return true. + return obj; +} + +// 6.2.5.5 #sec-topropertydescriptor +export function ToPropertyDescriptor(Obj) { + if (Type(Obj) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', Obj); + } + + const desc = Descriptor({}); + const hasEnumerable = Q(HasProperty(Obj, new Value('enumerable'))); + if (hasEnumerable === Value.true) { + const enumerable = ToBoolean(Q(Get(Obj, new Value('enumerable')))); + desc.Enumerable = enumerable; + } + const hasConfigurable = Q(HasProperty(Obj, new Value('configurable'))); + if (hasConfigurable === Value.true) { + const conf = ToBoolean(Q(Get(Obj, new Value('configurable')))); + desc.Configurable = conf; + } + const hasValue = Q(HasProperty(Obj, new Value('value'))); + if (hasValue === Value.true) { + const value = Q(Get(Obj, new Value('value'))); + desc.Value = value; + } + const hasWritable = Q(HasProperty(Obj, new Value('writable'))); + if (hasWritable === Value.true) { + const writable = ToBoolean(Q(Get(Obj, new Value('writable')))); + desc.Writable = writable; + } + const hasGet = Q(HasProperty(Obj, new Value('get'))); + if (hasGet === Value.true) { + const getter = Q(Get(Obj, new Value('get'))); + if (IsCallable(getter) === Value.false && Type(getter) !== 'Undefined') { + return surroundingAgent.Throw('TypeError', 'NotAFunction', getter); + } + desc.Get = getter; + } + const hasSet = Q(HasProperty(Obj, new Value('set'))); + if (hasSet === Value.true) { + const setter = Q(Get(Obj, new Value('set'))); + if (IsCallable(setter) === Value.false && Type(setter) !== 'Undefined') { + return surroundingAgent.Throw('TypeError', 'NotAFunction', setter); + } + desc.Set = setter; + } + if (desc.Get !== undefined || desc.Set !== undefined) { + if (desc.Value !== undefined || desc.Writable !== undefined) { + return surroundingAgent.Throw('TypeError', 'InvalidPropertyDescriptor'); + } + } + return desc; +} + +// 6.2.5.6 #sec-completepropertydescriptor +export function CompletePropertyDescriptor(Desc) { + Assert(Type(Desc) === 'Descriptor'); + const like = Descriptor({ + Value: Value.undefined, + Writable: false, + Get: Value.undefined, + Set: Value.undefined, + Enumerable: false, + Configurable: false, + }); + if (IsGenericDescriptor(Desc) || IsDataDescriptor(Desc)) { + if (Desc.Value === undefined) { + Desc.Value = like.Value; + } + if (Desc.Writable === undefined) { + Desc.Writable = like.Writable; + } + } else { + if (Desc.Get === undefined) { + Desc.Get = like.Get; + } + if (Desc.Set === undefined) { + Desc.Set = like.Set; + } + } + if (Desc.Enumerable === undefined) { + Desc.Enumerable = like.Enumerable; + } + if (Desc.Configurable === undefined) { + Desc.Configurable = like.Configurable; + } + return Desc; +} + +// 6.2.7.1 #sec-createbytedatablock +export function CreateByteDataBlock(size) { + size = size.numberValue(); + Assert(size >= 0); + let db; + try { + db = new DataBlock(size); + } catch (err) { + return surroundingAgent.Throw('RangeError', 'CannotAllocateDataBlock'); + } + return db; +} + +// 6.2.7.3 #sec-copydatablockbytes +export function CopyDataBlockBytes(toBlock, toIndex, fromBlock, fromIndex, count) { + Assert(fromBlock !== toBlock); + Assert(Type(fromBlock) === 'Data Block' || Type(fromBlock) === 'Shared Data Block'); + Assert(Type(toBlock) === 'Data Block' || Type(toBlock) === 'Shared Data Block'); + Assert(Number.isSafeInteger(fromIndex) && fromIndex >= 0); + Assert(Number.isSafeInteger(toIndex) && toIndex >= 0); + Assert(Number.isSafeInteger(count) && count >= 0); + const fromSize = fromBlock.byteLength; + Assert(fromIndex + count <= fromSize); + const toSize = toBlock.byteLength; + Assert(toIndex + count <= toSize); + while (count > 0) { + if (Type(fromBlock) === 'Shared Data Block') { + Assert(false); + } else { + Assert(Type(toBlock) !== 'Shared Data Block'); + toBlock[toIndex] = fromBlock[fromIndex]; + } + toIndex += 1; + fromIndex += 1; + count -= 1; + } + return NormalCompletion(undefined); +} diff --git a/engine262/src/abstract-ops/string-objects.mjs b/engine262/src/abstract-ops/string-objects.mjs new file mode 100644 index 0000000..79ab987 --- /dev/null +++ b/engine262/src/abstract-ops/string-objects.mjs @@ -0,0 +1,145 @@ +import { + Descriptor, + Type, + Value, +} from '../value.mjs'; +import { X } from '../completion.mjs'; +import { + Assert, + CanonicalNumericIndexString, + DefinePropertyOrThrow, + IsInteger, + IsPropertyKey, + MakeBasicObject, + OrdinaryGetOwnProperty, + OrdinaryDefineOwnProperty, + IsCompatiblePropertyDescriptor, + ToInteger, + isArrayIndex, +} from './all.mjs'; + +function StringExoticGetOwnProperty(P) { + const S = this; + Assert(IsPropertyKey(P)); + const desc = OrdinaryGetOwnProperty(S, P); + if (Type(desc) !== 'Undefined') { + return desc; + } + return X(StringGetOwnProperty(S, P)); +} + +function StringExoticDefineOwnProperty(P, Desc) { + const S = this; + Assert(IsPropertyKey(P)); + const stringDesc = X(StringGetOwnProperty(S, P)); + if (Type(stringDesc) !== 'Undefined') { + const extensible = S.Extensible; + return X(IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc)); + } + return X(OrdinaryDefineOwnProperty(S, P, Desc)); +} + +function StringExoticOwnPropertyKeys() { + const O = this; + const keys = []; + const str = O.StringData; + Assert(Type(str) === 'String'); + const len = str.stringValue().length; + + for (let i = 0; i < len; i += 1) { + keys.push(new Value(`${i}`)); + } + + // For each own property key P of O such that P is an array index and + // ToInteger(P) ≥ len, in ascending numeric index order, do + // Add P as the last element of keys. + for (const P of O.properties.keys()) { + // This is written with two nested ifs to work around https://github.com/devsnek/engine262/issues/24 + if (isArrayIndex(P)) { + if (X(ToInteger(P)).numberValue() >= len) { + keys.push(P); + } + } + } + + // For each own property key P of O such that Type(P) is String and + // P is not an array index, in ascending chronological order of property creation, do + // Add P as the last element of keys. + for (const P of O.properties.keys()) { + if (Type(P) === 'String' && isArrayIndex(P) === false) { + keys.push(P); + } + } + + // For each own property key P of O such that Type(P) is Symbol, + // in ascending chronological order of property creation, do + // Add P as the last element of keys. + for (const P of O.properties.keys()) { + if (Type(P) === 'Symbol') { + keys.push(P); + } + } + + return keys; +} + +// 9.4.3.4 #sec-stringcreate +export function StringCreate(value, prototype) { + // 1. Assert: Type(value) is String. + Assert(Type(value) === 'String'); + // 2. Let S be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[StringData]] »). + const S = X(MakeBasicObject(['Prototype', 'Extensible', 'StringData'])); + // 3. Set S.[[Prototype]] to prototype. + S.Prototype = prototype; + // 4. Set S.[[StringData]] to value. + S.StringData = value; + // 5. Set S.[[GetOwnProperty]] as specified in 9.4.3.1. + S.GetOwnProperty = StringExoticGetOwnProperty; + // 6. Set S.[[DefineOwnProperty]] as specified in 9.4.3.2. + S.DefineOwnProperty = StringExoticDefineOwnProperty; + // 7. Set S.[[OwnPropertyKeys]] as specified in 9.4.3.3. + S.OwnPropertyKeys = StringExoticOwnPropertyKeys; + // 8. Let length be the number of code unit elements in value. + const length = new Value(value.stringValue().length); + // 9. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: length, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(S, new Value('length'), Descriptor({ + Value: length, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 10. Return S. + return S; +} + +// 9.4.3.5 #sec-stringgetownproperty +export function StringGetOwnProperty(S, P) { + Assert(Type(S) === 'Object' && 'StringData' in S); + Assert(IsPropertyKey(P)); + if (Type(P) !== 'String') { + return Value.undefined; + } + const index = X(CanonicalNumericIndexString(P)); + if (Type(index) === 'Undefined') { + return Value.undefined; + } + if (IsInteger(index) === Value.false) { + return Value.undefined; + } + if (Object.is(index.numberValue(), -0)) { + return Value.undefined; + } + const str = S.StringData; + Assert(Type(str) === 'String'); + const len = str.stringValue().length; + if (index.numberValue() < 0 || len <= index.numberValue()) { + return Value.undefined; + } + const resultStr = str.stringValue()[index.numberValue()]; + return Descriptor({ + Value: new Value(resultStr), + Writable: Value.false, + Enumerable: Value.true, + Configurable: Value.false, + }); +} diff --git a/engine262/src/abstract-ops/symbol-objects.mjs b/engine262/src/abstract-ops/symbol-objects.mjs new file mode 100644 index 0000000..455734f --- /dev/null +++ b/engine262/src/abstract-ops/symbol-objects.mjs @@ -0,0 +1,12 @@ +import { Type, Value } from '../value.mjs'; +import { Assert } from './all.mjs'; + +// 19.4.3.3.1 #sec-symboldescriptivestring +export function SymbolDescriptiveString(sym) { + Assert(Type(sym) === 'Symbol'); + let desc = sym.Description; + if (Type(desc) === 'Undefined') { + desc = new Value(''); + } + return new Value(`Symbol(${desc.stringValue()})`); +} diff --git a/engine262/src/abstract-ops/testing-comparison.mjs b/engine262/src/abstract-ops/testing-comparison.mjs new file mode 100644 index 0000000..8d6661a --- /dev/null +++ b/engine262/src/abstract-ops/testing-comparison.mjs @@ -0,0 +1,412 @@ +import { + BigIntValue, + Type, + TypeNumeric, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Q, X } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + Assert, + Get, + ToBoolean, + ToNumber, + ToNumeric, + ToPrimitive, + StringToBigInt, + isProxyExoticObject, + isArrayExoticObject, + isIntegerIndexedExoticObject, +} from './all.mjs'; + +// This file covers abstract operations defined in +// 7.2 #sec-testing-and-comparison-operations + +// 7.2.1 #sec-requireobjectcoercible +export function RequireObjectCoercible(argument) { + const type = Type(argument); + switch (type) { + case 'Undefined': + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'undefined'); + case 'Null': + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'null'); + case 'Boolean': + case 'Number': + case 'String': + case 'Symbol': + case 'BigInt': + case 'Object': + return argument; + default: + throw new OutOfRange('RequireObjectCoercible', { type, argument }); + } +} + +// 7.2.2 #sec-isarray +export function IsArray(argument) { + if (Type(argument) !== 'Object') { + return Value.false; + } + if (isArrayExoticObject(argument)) { + return Value.true; + } + if (isProxyExoticObject(argument)) { + if (argument.ProxyHandler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'IsArray'); + } + const target = argument.ProxyTarget; + return IsArray(target); + } + return Value.false; +} + +// 7.2.3 #sec-iscallable +export function IsCallable(argument) { + if (Type(argument) !== 'Object') { + return Value.false; + } + if ('Call' in argument) { + return Value.true; + } + return Value.false; +} + +// 7.2.4 #sec-isconstructor +export function IsConstructor(argument) { + if (Type(argument) !== 'Object') { + return Value.false; + } + if ('Construct' in argument) { + return Value.true; + } + return Value.false; +} + +// 7.2.5 #sec-isextensible-o +export function IsExtensible(O) { + Assert(Type(O) === 'Object'); + return O.IsExtensible(); +} + +// 7.2.6 #sec-isinteger +export function IsInteger(argument) { + if (Type(argument) !== 'Number') { + return Value.false; + } + if (argument.isNaN() || argument.isInfinity()) { + return Value.false; + } + if (Math.floor(Math.abs(argument.numberValue())) !== Math.abs(argument.numberValue())) { + return Value.false; + } + return Value.true; +} + +// 7.2.7 #sec-ispropertykey +export function IsPropertyKey(argument) { + if (Type(argument) === 'String') { + return true; + } + if (Type(argument) === 'Symbol') { + return true; + } + return false; +} + +// 7.2.8 #sec-isregexp +export function IsRegExp(argument) { + if (Type(argument) !== 'Object') { + return Value.false; + } + const matcher = Q(Get(argument, wellKnownSymbols.match)); + if (matcher !== Value.undefined) { + return ToBoolean(matcher); + } + if ('RegExpMatcher' in argument) { + return Value.true; + } + return Value.false; +} + +// 7.2.9 #sec-isstringprefix +export function IsStringPrefix(p, q) { + Assert(Type(p) === 'String'); + Assert(Type(q) === 'String'); + return q.stringValue().startsWith(p.stringValue()); +} + +// 7.2.10 #sec-samevalue +export function SameValue(x, y) { + // 1. If Type(x) is different from Type(y), return false. + if (Type(x) !== Type(y)) { + return Value.false; + } + // 2. If Type(x) is Number or BigInt, then + if (Type(x) === 'Number' || Type(x) === 'BigInt') { + // a. Return ! Type(x)::sameValue(x, y). + return TypeNumeric(x).sameValue(x, y); + } + // 3. Return ! SameValueNonNumeric(x, y). + return X(SameValueNonNumber(x, y)); +} + +// 7.2.11 #sec-samevaluezero +export function SameValueZero(x, y) { + // 1. If Type(x) is different from Type(y), return false. + if (Type(x) !== Type(y)) { + return Value.false; + } + // 2. If Type(x) is Number or BigInt, then + if (Type(x) === 'Number' || Type(x) === 'BigInt') { + // a. Return ! Type(x)::sameValueZero(x, y). + return TypeNumeric(x).sameValueZero(x, y); + } + // 3. Return ! SameValueNonNumeric(x, y). + return X(SameValueNonNumber(x, y)); +} + +// 7.2.12 #sec-samevaluenonnumber +export function SameValueNonNumber(x, y) { + Assert(Type(x) !== 'Number'); + Assert(Type(x) === Type(y)); + + if (Type(x) === 'Undefined') { + return Value.true; + } + + if (Type(x) === 'Null') { + return Value.true; + } + + if (Type(x) === 'String') { + if (x.stringValue() === y.stringValue()) { + return Value.true; + } + return Value.false; + } + + if (Type(x) === 'Boolean') { + if (x === y) { + return Value.true; + } + return Value.false; + } + + if (Type(x) === 'Symbol') { + return x === y ? Value.true : Value.false; + } + + return x === y ? Value.true : Value.false; +} + +// 7.2.13 #sec-abstract-relational-comparison +export function AbstractRelationalComparison(x, y, LeftFirst = true) { + let px; + let py; + // 1. If the LeftFirst flag is true, then + if (LeftFirst === true) { + // a. Let px be ? ToPrimitive(x, hint Number). + px = Q(ToPrimitive(x, 'Number')); + // b. Let py be ? ToPrimitive(y, hint Number). + py = Q(ToPrimitive(y, 'Number')); + } else { + // a. NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation. + // b. Let py be ? ToPrimitive(y, hint Number). + py = Q(ToPrimitive(y, 'Number')); + // c. Let px be ? ToPrimitive(x, hint Number). + px = Q(ToPrimitive(x, 'Number')); + } + // 3. If Type(px) is String and Type(py) is String, then + if (Type(px) === 'String' && Type(py) === 'String') { + // a. If IsStringPrefix(py, px) is true, return false. + if (IsStringPrefix(py, px)) { + return Value.false; + } + // b. If IsStringPrefix(px, py) is true, return true. + if (IsStringPrefix(px, py)) { + return Value.true; + } + // c. Let k be the smallest nonnegative integer such that the code unit at index k within px + // is different from the code unit at index k within py. (There must be such a k, for + // neither String is a prefix of the other.) + let k = 0; + while (true) { + if (px.stringValue()[k] !== py.stringValue()[k]) { + break; + } + k += 1; + } + // d. Let m be the integer that is the numeric value of the code unit at index k within px. + const m = px.stringValue().charCodeAt(k); + // e. Let n be the integer that is the numeric value of the code unit at index k within py. + const n = py.stringValue().charCodeAt(k); + // f. If m < n, return true. Otherwise, return false. + if (m < n) { + return Value.true; + } else { + return Value.false; + } + } else { + // a. If Type(px) is BigInt and Type(py) is String, then + if (Type(px) === 'BigInt' && Type(py) === 'String') { + // i. Let ny be ! StringToBigInt(py). + const ny = X(StringToBigInt(py)); + // ii. If ny is NaN, return undefined. + if (Number.isNaN(ny)) { + return Value.undefined; + } + // iii. Return BigInt::lessThan(px, ny). + return BigIntValue.lessThan(px, ny); + } + // b. If Type(px) is String and Type(py) is BigInt, then + if (Type(px) === 'String' && Type(py) === 'BigInt') { + // i. Let ny be ! StringToBigInt(py). + const nx = X(StringToBigInt(px)); + // ii. If ny is NaN, return undefined. + if (Number.isNaN(nx)) { + return Value.undefined; + } + // iii. Return BigInt::lessThan(px, ny). + return BigIntValue.lessThan(nx, py); + } + // c. Let nx be ? ToNumeric(px). NOTE: Because px and py are primitive values evaluation order is not important. + const nx = Q(ToNumeric(px)); + // d. Let ny be ? ToNumeric(py). + const ny = Q(ToNumeric(py)); + // e. If Type(nx) is the same as Type(ny), return Type(nx)::lessThan(nx, ny). + if (Type(nx) === Type(ny)) { + return TypeNumeric(nx).lessThan(nx, ny); + } + // f. Assert: Type(nx) is BigInt and Type(ny) is Number, or Type(nx) is Number and Type(ny) is BigInt. + Assert((Type(nx) === 'BigInt' && Type(ny) === 'Number') || (Type(nx) === 'Number' && Type(ny) === 'BigInt')); + // g. If nx or ny is NaN, return undefined. + if ((nx.isNaN && nx.isNaN()) || (ny.isNaN && ny.isNaN())) { + return Value.undefined; + } + // h. If nx is -∞ or ny is +∞, return true. + if ((nx.numberValue && nx.numberValue() === -Infinity) || (ny.numberValue && ny.numberValue() === +Infinity)) { + return Value.true; + } + // i. If nx is +∞ or ny is -∞, return false. + if ((nx.numberValue && nx.numberValue() === +Infinity) || (ny.numberValue && ny.numberValue() === -Infinity)) { + return Value.false; + } + // j. If the mathematical value of nx is less than the mathematical value of ny, return true; otherwise return false. + const a = nx.numberValue ? nx.numberValue() : nx.bigintValue(); + const b = ny.numberValue ? ny.numberValue() : ny.bigintValue(); + return a < b ? Value.true : Value.false; + } +} + +// 7.2.14 #sec-abstract-equality-comparison +export function AbstractEqualityComparison(x, y) { + // 1. If Type(x) is the same as Type(y), then + if (Type(x) === Type(y)) { + // a. Return the result of performing Strict Equality Comparison x === y. + return StrictEqualityComparison(x, y); + } + // 2. If x is null and y is undefined, return true. + if (x === Value.null && y === Value.undefined) { + return Value.true; + } + // 3. If x is undefined and y is null, return true. + if (x === Value.undefined && y === Value.null) { + return Value.true; + } + // 4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). + if (Type(x) === 'Number' && Type(y) === 'String') { + return AbstractEqualityComparison(x, X(ToNumber(y))); + } + // 5. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. + if (Type(x) === 'String' && Type(y) === 'Number') { + return AbstractEqualityComparison(X(ToNumber(x)), y); + } + // 6. If Type(x) is BigInt and Type(y) is String, then + if (Type(x) === 'BigInt' && Type(y) === 'String') { + // a. Let n be ! StringToBigInt(y). + const n = X(StringToBigInt(y)); + // b. If n is NaN, return false. + if (Number.isNaN(n)) { + return Value.false; + } + // c. Return the result of the comparison x == n. + return AbstractEqualityComparison(x, n); + } + // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. + if (Type(x) === 'String' && Type(y) === 'BigInt') { + return AbstractEqualityComparison(y, x); + } + // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. + if (Type(x) === 'Boolean') { + return AbstractEqualityComparison(X(ToNumber(x)), y); + } + // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). + if (Type(y) === 'Boolean') { + return AbstractEqualityComparison(x, X(ToNumber(y))); + } + // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y). + if (['String', 'Number', 'BigInt', 'Symbol'].includes(Type(x)) && Type(y) === 'Object') { + return AbstractEqualityComparison(x, Q(ToPrimitive(y))); + } + // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result of the comparison ToPrimitive(x) == y. + if (Type(x) === 'Object' && ['String', 'Number', 'BigInt', 'Symbol'].includes(Type(y))) { + return AbstractEqualityComparison(Q(ToPrimitive(x)), y); + } + // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then + if ((Type(x) === 'BigInt' && Type(y) === 'Number') || (Type(x) === 'Number' && Type(y) === 'BigInt')) { + // a. If x or y are any of NaN, +∞, or -∞, return false. + if ((x.isNaN && (x.isNaN() || !x.isFinite())) || (y.isNaN && (y.isNaN() || !y.isFinite()))) { + return Value.false; + } + // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. + const a = (x.numberValue ? x.numberValue() : x.bigintValue()); + const b = (y.numberValue ? y.numberValue() : y.bigintValue()); + return a == b ? Value.true : Value.false; // eslint-disable-line eqeqeq + } + // 13. Return false. + return Value.false; +} + +// 7.2.15 #sec-strict-equality-comparison +export function StrictEqualityComparison(x, y) { + // 1. If Type(x) is different from Type(y), return false. + if (Type(x) !== Type(y)) { + return Value.false; + } + // 2. If Type(x) is Number or BigInt, then + if (Type(x) === 'Number' || Type(x) === 'BigInt') { + // a. Return ! Type(x)::equal(x, y). + return X(TypeNumeric(x).equal(x, y)); + } + // 3. Return ! SameValueNonNumeric(x, y). + return SameValueNonNumber(x, y); +} + +// #sec-isvalidintegerindex +export function IsValidIntegerIndex(O, index) { + Assert(isIntegerIndexedExoticObject(O)); + Assert(Type(index) === 'Number'); + if (IsInteger(index) === Value.false) { + return Value.false; + } + index = index.numberValue(); + if (Object.is(index, -0)) { + return Value.false; + } + if (index < 0 || index >= O.ArrayLength.numberValue()) { + return Value.false; + } + return Value.true; +} + +// #sec-isnonnegativeinteger +export function IsNonNegativeInteger(argument) { + // 1. If ! IsInteger(argument) is true and argument ≥ 0, return true. + if (X(IsInteger(argument)) === Value.true && argument.numberValue() >= 0) { + return Value.true; + } + // 2. Otherwise, return false. + return Value.false; +} diff --git a/engine262/src/abstract-ops/type-conversion.mjs b/engine262/src/abstract-ops/type-conversion.mjs new file mode 100644 index 0000000..65a74a2 --- /dev/null +++ b/engine262/src/abstract-ops/type-conversion.mjs @@ -0,0 +1,469 @@ +import { + Type, + Value, + NumberValue, + BigIntValue, + wellKnownSymbols, +} from '../value.mjs'; +import { + surroundingAgent, +} from '../engine.mjs'; +import { Q, X } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { MV_StringNumericLiteral } from '../runtime-semantics/all.mjs'; +import { + Assert, + Call, + Get, + GetMethod, + IsCallable, + OrdinaryObjectCreate, + SameValue, + StringCreate, +} from './all.mjs'; + +// 7.1.1 #sec-toprimitive +export function ToPrimitive(input, PreferredType) { + Assert(input instanceof Value); + if (Type(input) === 'Object') { + let hint; + if (PreferredType === undefined) { + hint = new Value('default'); + } else if (PreferredType === 'String') { + hint = new Value('string'); + } else { + Assert(PreferredType === 'Number'); + hint = new Value('number'); + } + const exoticToPrim = Q(GetMethod(input, wellKnownSymbols.toPrimitive)); + if (exoticToPrim !== Value.undefined) { + const result = Q(Call(exoticToPrim, input, [hint])); + if (Type(result) !== 'Object') { + return result; + } + return surroundingAgent.Throw('TypeError', 'ObjectToPrimitive'); + } + if (hint.stringValue() === 'default') { + hint = new Value('number'); + } + return Q(OrdinaryToPrimitive(input, hint)); + } + return input; +} + +// 7.1.1.1 #sec-ordinarytoprimitive +export function OrdinaryToPrimitive(O, hint) { + Assert(Type(O) === 'Object'); + Assert(Type(hint) === 'String' && (hint.stringValue() === 'string' || hint.stringValue() === 'number')); + let methodNames; + if (hint.stringValue() === 'string') { + methodNames = [new Value('toString'), new Value('valueOf')]; + } else { + methodNames = [new Value('valueOf'), new Value('toString')]; + } + for (const name of methodNames) { + const method = Q(Get(O, name)); + if (IsCallable(method) === Value.true) { + const result = Q(Call(method, O)); + if (Type(result) !== 'Object') { + return result; + } + } + } + return surroundingAgent.Throw('TypeError', 'ObjectToPrimitive'); +} + +// 7.1.2 #sec-toboolean +export function ToBoolean(argument) { + const type = Type(argument); + switch (type) { + case 'Undefined': + return Value.false; + case 'Null': + return Value.false; + case 'Boolean': + return argument; + case 'Number': + if (argument.numberValue() === 0 || argument.isNaN()) { + return Value.false; + } + return Value.true; + case 'String': + if (argument.stringValue().length === 0) { + return Value.false; + } + return Value.true; + case 'Symbol': + return Value.true; + case 'BigInt': + if (argument.bigintValue() === 0n) { + return Value.false; + } + return Value.true; + case 'Object': + return Value.true; + default: + throw new OutOfRange('ToBoolean', { type, argument }); + } +} + +// #sec-tonumeric +export function ToNumeric(value) { + // 1. Let primValue be ? ToPrimitive(value, hint Number). + const primValue = Q(ToPrimitive(value, 'Number')); + // 2. If Type(primValue) is BigInt, return primValue. + if (Type(primValue) === 'BigInt') { + return primValue; + } + // 3. Return ? ToNumber(primValue). + return Q(ToNumber(primValue)); +} + +// 7.1.3 #sec-tonumber +export function ToNumber(argument) { + const type = Type(argument); + switch (type) { + case 'Undefined': + return new Value(NaN); + case 'Null': + return new Value(0); + case 'Boolean': + if (argument === Value.true) { + return new Value(1); + } + return new Value(0); + case 'Number': + return argument; + case 'String': + return MV_StringNumericLiteral(argument.stringValue()); + case 'BigInt': + return surroundingAgent.Throw('TypeError', 'CannotMixBigInts'); + case 'Symbol': + return surroundingAgent.Throw('TypeError', 'CannotConvertSymbol', 'number'); + case 'Object': { + const primValue = Q(ToPrimitive(argument, 'Number')); + return Q(ToNumber(primValue)); + } + default: + throw new OutOfRange('ToNumber', { type, argument }); + } +} + +const mod = (n, m) => { + const r = n % m; + return Math.floor(r >= 0 ? r : r + m); +}; + +// 7.1.4 #sec-tointeger +export function ToInteger(argument) { + // 1. Let number be ? ToNumber(argument). + const number = Q(ToNumber(argument)).numberValue(); + // 2. If number is NaN, +0, or -0, return +0. + if (Number.isNaN(number) || number === 0) { + return new Value(0); + } + // 3. If number is +∞, or -∞, return number. + if (!Number.isFinite(number)) { + return new Value(number); + } + // 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)). + const integer = Math.sign(number) * Math.floor(Math.abs(number)); + // 5. If integer is -0, return +0. + if (Object.is(integer, -0)) { + return new Value(+0); + } + // 6. Return integer. + return new Value(integer); +} + +// 7.1.5 #sec-toint32 +export function ToInt32(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return new Value(0); + } + const int = Math.sign(number) * Math.floor(Math.abs(number)); + const int32bit = mod(int, 2 ** 32); + if (int32bit >= (2 ** 31)) { + return new Value(int32bit - (2 ** 32)); + } + return new Value(int32bit); +} + +// 7.1.6 #sec-touint32 +export function ToUint32(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return new Value(0); + } + const int = Math.sign(number) * Math.floor(Math.abs(number)); + const int32bit = mod(int, 2 ** 32); + return new Value(int32bit); +} + +// 7.1.7 #sec-toint16 +export function ToInt16(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return new Value(0); + } + const int = Math.sign(number) * Math.floor(Math.abs(number)); + const int16bit = mod(int, 2 ** 16); + if (int16bit >= (2 ** 15)) { + return new Value(int16bit - (2 ** 16)); + } + return new Value(int16bit); +} + +// 7.1.8 #sec-touint16 +export function ToUint16(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return new Value(0); + } + const int = Math.sign(number) * Math.floor(Math.abs(number)); + const int16bit = mod(int, 2 ** 16); + return new Value(int16bit); +} + +// 7.1.9 #sec-toint8 +export function ToInt8(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return new Value(0); + } + const int = Math.sign(number) * Math.floor(Math.abs(number)); + const int8bit = mod(int, 2 ** 8); + if (int8bit >= (2 ** 7)) { + return new Value(int8bit - (2 ** 8)); + } + return new Value(int8bit); +} + +// 7.1.10 #sec-touint8 +export function ToUint8(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return new Value(0); + } + const int = Math.sign(number) * Math.floor(Math.abs(number)); + const int8bit = mod(int, 2 ** 8); + return new Value(int8bit); +} + +// 7.1.11 #sec-touint8clamp +export function ToUint8Clamp(argument) { + const number = Q(ToNumber(argument)).numberValue(); + if (Number.isNaN(number)) { + return new Value(0); + } + if (number <= 0) { + return new Value(0); + } + if (number >= 255) { + return new Value(255); + } + const f = Math.floor(number); + if (f + 0.5 < number) { + return new Value(f + 1); + } + if (number < f + 0.5) { + return new Value(f); + } + if (f % 2 === 1) { + return new Value(f + 1); + } + return new Value(f); +} + +// #sec-tobigint +export function ToBigInt(argument) { + // 1. Let prim be ? ToPrimitive(argument, hint Number). + const prim = Q(ToPrimitive(argument, 'Number')); + // 2. Return the value that prim corresponds to in Table 12 (#table-tobigint). + switch (Type(prim)) { + case 'Undefined': + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToBigInt', prim); + case 'Null': + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToBigInt', prim); + case 'Boolean': + // Return 1n if prim is true and 0n if prim is false. + if (prim === Value.true) { + return new Value(1n); + } + return new Value(0n); + case 'BigInt': + // Return prim. + return prim; + case 'Number': + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToBigInt', prim); + case 'String': { + // 1. Let n be ! StringToBigInt(prim). + const n = X(StringToBigInt(prim)); + // 2. If n is NaN, throw a SyntaxError exception. + if (Number.isNaN(n)) { + return surroundingAgent.Throw('SyntaxError', 'CannotConvertToBigInt', prim); + } + // 3. Return n. + return n; + } + case 'Symbol': + return surroundingAgent.Throw('TypeError', 'CannotConvertSymbol', 'bigint'); + default: + throw new OutOfRange('ToBigInt', argument); + } +} + +// #sec-stringtobigint +export function StringToBigInt(argument) { + // Apply the algorithm in 7.1.4.1 (#sec-tonumber-applied-to-the-string-type) with the following changes: + // 1. Replace the StrUnsignedDecimalLiteral production with DecimalDigits to not allow Infinity, decimal points, or exponents. + // 2. If the MV is NaN, return NaN, otherwise return the BigInt which exactly corresponds to the MV, rather than rounding to a Number. + // TODO: Adapt nearley grammar for this. + try { + return new Value(BigInt(argument.stringValue())); + } catch { + return NaN; + } +} + +// #sec-tobigint64 +export function ToBigInt64(argument) { + // 1. Let n be ? ToBigInt(argument). + const n = Q(ToBigInt(argument)); + // 2. Let int64bit be n modulo 2^64. + const int64bit = n.bigintValue() % (2n ** 64n); + // 3. If int64bit ≥ 2^63, return int64bit - 2^64; otherwise return int64bit. + if (int64bit >= 2n ** 63n) { + return new Value(int64bit - (2n ** 64n)); + } + return new Value(int64bit); +} + +// #sec-tobiguint64 +export function ToBigUint64(argument) { + // 1. Let n be ? ToBigInt(argument). + const n = Q(ToBigInt(argument)); + // 2. Let int64bit be n modulo 2^64. + const int64bit = n.bigintValue() % (2n ** 64n); + // 3. Return int64bit. + return new Value(int64bit); +} + +// 7.1.12 #sec-tostring +export function ToString(argument) { + const type = Type(argument); + switch (type) { + case 'Undefined': + return new Value('undefined'); + case 'Null': + return new Value('null'); + case 'Boolean': + return new Value(argument === Value.true ? 'true' : 'false'); + case 'Number': + // Return ! Number::toString(argument). + return X(NumberValue.toString(argument)); + case 'String': + return argument; + case 'Symbol': + return surroundingAgent.Throw('TypeError', 'CannotConvertSymbol', 'string'); + case 'BigInt': + // Return ! BigInt::toString(argument). + return X(BigIntValue.toString(argument)); + case 'Object': { + const primValue = Q(ToPrimitive(argument, 'String')); + return Q(ToString(primValue)); + } + default: + throw new OutOfRange('ToString', { type, argument }); + } +} + +// 7.1.13 #sec-toobject +export function ToObject(argument) { + const type = Type(argument); + switch (type) { + case 'Undefined': + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'undefined'); + case 'Null': + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'null'); + case 'Boolean': { + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Boolean.prototype%')); + obj.BooleanData = argument; + return obj; + } + case 'Number': { + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Number.prototype%')); + obj.NumberData = argument; + return obj; + } + case 'String': + return StringCreate(argument, surroundingAgent.intrinsic('%String.prototype%')); + case 'Symbol': { + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Symbol.prototype%')); + obj.SymbolData = argument; + return obj; + } + case 'BigInt': { + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%BigInt.prototype%')); + obj.BigIntData = argument; + return obj; + } + case 'Object': + return argument; + default: + throw new OutOfRange('ToObject', { type, argument }); + } +} + +// 7.1.14 #sec-topropertykey +export function ToPropertyKey(argument) { + const key = Q(ToPrimitive(argument, 'String')); + if (Type(key) === 'Symbol') { + return key; + } + return X(ToString(key)); +} + +// 7.1.15 #sec-tolength +export function ToLength(argument) { + const len = Q(ToInteger(argument)); + if (len.numberValue() <= 0) { + return new Value(0); + } + return new Value(Math.min(len.numberValue(), (2 ** 53) - 1)); +} + +// 7.1.16 #sec-canonicalnumericindexstring +export function CanonicalNumericIndexString(argument) { + Assert(Type(argument) === 'String'); + if (argument.stringValue() === '-0') { + return new Value(-0); + } + const n = X(ToNumber(argument)); + if (SameValue(X(ToString(n)), argument) === Value.false) { + return Value.undefined; + } + return n; +} + +// 7.1.17 #sec-toindex +export function ToIndex(value) { + let index; + if (Type(value) === 'Undefined') { + index = new Value(0); + } else { + const integerIndex = Q(ToInteger(value)); + if (integerIndex.numberValue() < 0) { + return surroundingAgent.Throw('RangeError', 'NegativeIndex', 'Index'); + } + index = X(ToLength(integerIndex)); + if (X(SameValue(integerIndex, index)) === Value.false) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', 'Index'); + } + } + return index; +} diff --git a/engine262/src/abstract-ops/typedarray-objects.mjs b/engine262/src/abstract-ops/typedarray-objects.mjs new file mode 100644 index 0000000..8af8b76 --- /dev/null +++ b/engine262/src/abstract-ops/typedarray-objects.mjs @@ -0,0 +1,240 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { + Assert, + ToInt8, + ToUint8, + ToUint8Clamp, + ToInt16, + ToUint16, + ToInt32, + ToUint32, + ToBigInt64, + ToBigUint64, + RequireInternalSlot, + Construct, + GetIterator, + IteratorStep, + IteratorValue, + SpeciesConstructor, + IsDetachedBuffer, + IsNonNegativeInteger, + IntegerIndexedObjectCreate, + GetPrototypeFromConstructor, + AllocateArrayBuffer, +} from './all.mjs'; + +export const typedArrayInfoByName = { + Int8Array: { + IntrinsicName: '%Int8Array%', + ElementType: 'Int8', + ElementSize: 1, + ConversionOperation: ToInt8, + }, + Uint8Array: { + IntrinsicName: '%Uint8Array%', + ElementType: 'Uint8', + ElementSize: 1, + ConversionOperation: ToUint8, + }, + Uint8ClampedArray: { + IntrinsicName: '%Uint8ClampedArray%', + ElementType: 'Uint8C', + ElementSize: 1, + ConversionOperation: ToUint8Clamp, + }, + Int16Array: { + IntrinsicName: '%Int16Array%', + ElementType: 'Int16', + ElementSize: 2, + ConversionOperation: ToInt16, + }, + Uint16Array: { + IntrinsicName: '%Uint16Array%', + ElementType: 'Uint16', + ElementSize: 2, + ConversionOperation: ToUint16, + }, + Int32Array: { + IntrinsicName: '%Int32Array%', + ElementType: 'Int32', + ElementSize: 4, + ConversionOperation: ToInt32, + }, + Uint32Array: { + IntrinsicName: '%Uint32Array%', + ElementType: 'Uint32', + ElementSize: 4, + ConversionOperation: ToUint32, + }, + BigInt64Array: { + IntrinsicName: '%BigInt64Array%', + ElementType: 'BigInt64', + ElementSize: 8, + ConversionOperation: ToBigInt64, + }, + BigUint64Array: { + IntrinsicName: '%BigUint64Array%', + ElementType: 'BigUint64', + ElementSize: 8, + ConversionOperation: ToBigUint64, + }, + Float32Array: { + IntrinsicName: '%Float32Array%', + ElementType: 'Float32', + ElementSize: 4, + ConversionOperation: undefined, + }, + Float64Array: { + IntrinsicName: '%Float64Array%', + ElementType: 'Float64', + ElementSize: 8, + ConversionOperation: undefined, + }, +}; + +export const typedArrayInfoByType = {}; +Object.values(typedArrayInfoByName).forEach((v) => { + typedArrayInfoByType[v.ElementType] = v; +}); + +// #sec-validatetypedarray +export function ValidateTypedArray(O) { + // 1. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 2. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 3. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Return buffer. + return buffer; +} + +// #typedarray-create +export function TypedArrayCreate(constructor, argumentList) { + // 1. Let newTypedArray be ? Construct(constructor, argumentList). + const newTypedArray = Q(Construct(constructor, argumentList)); + // 2. Perform ? ValidateTypedArray(newTypedArray). + Q(ValidateTypedArray(newTypedArray)); + // 3. If argumentList is a List of a single Number, then + if (argumentList.length === 1 && Type(argumentList[0]) === 'Number') { + // a. If newTypedArray.[[ArrayLength]] < argumentList[0], throw a TypeError exception. + if (newTypedArray.ArrayLength.numberValue() < argumentList[0].numberValue()) { + return surroundingAgent.Throw('TypeError', 'TypedArrayTooSmall'); + } + } + // 4. Return newTypedArray. + return newTypedArray; +} + +// #sec-allocatetypedarray +export function AllocateTypedArray(constructorName, newTarget, defaultProto, length) { + // 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto). + const proto = Q(GetPrototypeFromConstructor(newTarget, defaultProto)); + // 2. Let obj be ! IntegerIndexedObjectCreate(proto). + const obj = X(IntegerIndexedObjectCreate(proto)); + // 3. Assert: obj.[[ViewedArrayBuffer]] is undefined. + Assert(obj.ViewedArrayBuffer === Value.undefined); + // 4. Set obj.[[TypedArrayName]] to constructorName. + obj.TypedArrayName = constructorName; + // 5. If constructorName is "BigInt64Array" or "BigUint64Array", set obj.[[ContentType]] to BigInt. + // 6. Otherwise, set obj.[[ContentType]] to Number. + if (constructorName.stringValue() === 'BigInt64Array' || constructorName.stringValue() === 'BigUint64Array') { + obj.ContentType = 'BigInt'; + } else { + obj.ContentType = 'Number'; + } + // 7. If length is not present, then + if (length === undefined) { + // 1. Set obj.[[ByteLength]] to 0. + obj.ByteLength = new Value(0); + // 1. Set obj.[[ByteOffset]] to 0. + obj.ByteOffset = new Value(0); + // 1. Set obj.[[ArrayLength]] to 0. + obj.ArrayLength = new Value(0); + } else { + // a. Perform ? AllocateTypedArrayBuffer(obj, length). + Q(AllocateTypedArrayBuffer(obj, length)); + } + // 9. Return obj. + return obj; +} + +// #sec-allocatetypedarraybuffer +export function AllocateTypedArrayBuffer(O, length) { + // 1. Assert: O is an Object that has a [[ViewedArrayBuffer]] internal slot. + Assert(Type(O) === 'Object' && 'ViewedArrayBuffer' in O); + // 2. Assert: O.[[ViewedArrayBuffer]] is undefined. + Assert(O.ViewedArrayBuffer === Value.undefined); + // 3. Assert: ! IsNonNegativeInteger(length) is true. + Assert(X(IsNonNegativeInteger(length)) === Value.true); + // 4. Let constructorName be the String value of O.[[TypedArrayName]]. + const constructorName = O.TypedArrayName.stringValue(); + // 5. Let elementSize be the Element Size value specified in Table 61 for constructorName. + const elementSize = typedArrayInfoByName[constructorName].ElementSize; + // 6. Let byteLength be elementSize × length. + const byteLength = new Value(elementSize * length.numberValue()); + // 7. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + const data = Q(AllocateArrayBuffer(surroundingAgent.intrinsic('%ArrayBuffer%'), byteLength)); + // 8. Set O.[[ViewedArrayBuffer]] to data. + O.ViewedArrayBuffer = data; + // 9. Set O.[[ByteLength]] to byteLength. + O.ByteLength = byteLength; + // 10. Set O.[[ByteOffset]] to 0. + O.ByteOffset = new Value(0); + // 11. Set O.[[ArrayLength]] to length. + O.ArrayLength = length; + // 12. Return O. + return O; +} + +// #typedarray-species-create +export function TypedArraySpeciesCreate(exemplar, argumentList) { + // 1. Assert: exemplar is an Object that has [[TypedArrayName]] and [[ContentType]] internal slots. + Assert(Type(exemplar) === 'Object' + && 'TypedArrayName' in exemplar + && 'ContentType' in exemplar); + // 2. Let defaultConstructor be the intrinsic object listed in column one of Table 61 for exemplar.[[TypedArrayName]]. + const defaultConstructor = surroundingAgent.intrinsic(typedArrayInfoByName[exemplar.TypedArrayName.stringValue()].IntrinsicName); + // 3. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). + const constructor = Q(SpeciesConstructor(exemplar, defaultConstructor)); + // 4. Let result be ? TypedArrayCreate(constructor, argumentList). + const result = Q(TypedArrayCreate(constructor, argumentList)); + // 5. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots. + Assert('TypedArrayName' in result && 'ContentType' in result); + // 6. If result.[[ContentType]] is not equal to exemplar.[[ContentType]], throw a TypeError exception. + if (result.ContentType !== exemplar.ContentType) { + return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch'); + } + // 7. Return result. + return result; +} + +// #sec-iterabletolist +export function IterableToList(items, method) { + // 1. Let iteratorRecord be ? GetIterator(items, sync, method). + const iteratorRecord = Q(GetIterator(items, 'sync', method)); + // 2. Let values be a new empty List. + const values = []; + // 3. Let next be true. + let next = Value.true; + // 4. Repeat, while next is not false + while (next !== Value.false) { + // a. Set next to ? IteratorStep(iteratorRecord). + next = Q(IteratorStep(iteratorRecord)); + // b. If next is not false, then + if (next !== Value.false) { + // i. Let nextValue be ? IteratorValue(next). + const nextValue = Q(IteratorValue(next)); + // ii. Append nextValue to the end of the List values. + values.push(nextValue); + } + } + // 5. Return values. + return values; +} diff --git a/engine262/src/abstract-ops/weak-operations.mjs b/engine262/src/abstract-ops/weak-operations.mjs new file mode 100644 index 0000000..7feb4a5 --- /dev/null +++ b/engine262/src/abstract-ops/weak-operations.mjs @@ -0,0 +1,60 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { NormalCompletion, Q, X } from '../completion.mjs'; +import { Assert, Call } from './all.mjs'; + +// #sec-clear-kept-objects +export function ClearKeptObjects() { + // 1. Let agentRecord be the surrounding agent's Agent Record. + const agentRecord = surroundingAgent.AgentRecord; + // 2. Set agentRecord.[[KeptAlive]] to a new empty List. + agentRecord.KeptAlive = new Set(); +} + +// #sec-addtokeptobjects +export function AddToKeptObjects(object) { + // 1. Let agentRecord be the surrounding agent's Agent Record. + const agentRecord = surroundingAgent.AgentRecord; + // 2. Append object to agentRecord.[[KeptAlive]]. + agentRecord.KeptAlive.add(object); +} + +// #sec-weakrefderef +export function WeakRefDeref(weakRef) { + // 1. Let target be weakRef.[[WeakRefTarget]]. + const target = weakRef.WeakRefTarget; + // 2. If target is not empty, then + if (target !== undefined) { + // a. Perform ! AddToKeptObjects(target). + X(AddToKeptObjects(target)); + // b. Return target. + return target; + } + // 3. Return undefined. + return Value.undefined; +} + +// #sec-cleanup-finalization-registry +export function CleanupFinalizationRegistry(finalizationRegistry, callback) { + // 1. Assert: finalizationRegistry has [[Cells]] and [[CleanupCallback]] internal slots. + Assert('Cells' in finalizationRegistry && 'CleanupCallback' in finalizationRegistry); + // 2. Set callback to finalizationRegistry.[[CleanupCallback]]. + if (callback === undefined || callback === Value.undefined) { + callback = finalizationRegistry.CleanupCallback; + } + // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is empty, an implementation may perform the following steps: + for (let i = 0; i < finalizationRegistry.Cells.length; i += 1) { + // a. Choose any such _cell_. + const cell = finalizationRegistry.Cells[i]; + if (cell.WeakRefTarget !== undefined) { + continue; + } + // b. Remove cell from finalizationRegistry.[[Cells]]. + finalizationRegistry.Cells.splice(i, 1); + i -= 1; + // c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »). + Q(Call(callback, Value.undefined, [cell.HeldValue])); + } + // 4. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} diff --git a/engine262/src/api.mjs b/engine262/src/api.mjs new file mode 100644 index 0000000..ffa676d --- /dev/null +++ b/engine262/src/api.mjs @@ -0,0 +1,269 @@ +import { Value } from './value.mjs'; +import { + surroundingAgent, + ExecutionContext, + HostEnqueueFinalizationRegistryCleanupJob, + ScriptEvaluation, +} from './engine.mjs'; +import { + X, + ThrowCompletion, + AbruptCompletion, + EnsureCompletion, +} from './completion.mjs'; +import { + Realm, + ClearKeptObjects, + CreateIntrinsics, + SetRealmGlobalObject, + SetDefaultGlobalBindings, +} from './abstract-ops/all.mjs'; +import { + ParseScript, + ParseModule, +} from './parse.mjs'; +import { SourceTextModuleRecord } from './modules.mjs'; + +export * from './value.mjs'; +export * from './engine.mjs'; +export * from './completion.mjs'; +export * from './abstract-ops/all.mjs'; +export * from './static-semantics/all.mjs'; +export * from './runtime-semantics/all.mjs'; +export * from './environment.mjs'; +export * from './parse.mjs'; +export * from './modules.mjs'; +export * from './inspect.mjs'; + +export function Throw(...args) { + return surroundingAgent.Throw(...args); +} + +export function gc() { + // #sec-weakref-execution + // At any time, if a set of objects S is not live, an ECMAScript implementation may perform the following steps atomically: + // 1. For each obj of S, do + // a. For each WeakRef ref such that ref.[[WeakRefTarget]] is obj, + // i. Set ref.[[WeakRefTarget]] to empty. + // b. For each FinalizationRegistry fg such that fg.[[Cells]] contains cell, and cell.[[WeakRefTarget]] is obj, + // i. Set cell.[[WeakRefTarget]] to empty. + // ii. Optionally, perform ! HostEnqueueFinalizationRegistryCleanupJob(fg). + // c. For each WeakMap map such that map.WeakMapData contains a record r such that r.Key is obj, + // i. Set r.[[Key]] to empty. + // ii. Set r.[[Value]] to empty. + // d. For each WeakSet set such that set.[[WeakSetData]] contains obj, + // i. Replace the element of set whose value is obj with an element whose value is empty. + + const marked = new Set(); + const weakrefs = new Set(); + const fgs = new Set(); + const weakmaps = new Set(); + const weaksets = new Set(); + const ephemeronQueue = []; + + const markCb = (O) => { + if (typeof O !== 'object' || O === null) { + return; + } + + if (marked.has(O)) { + return; + } + marked.add(O); + + if ('WeakRefTarget' in O && !('HeldValue' in O)) { + weakrefs.add(O); + markCb(O.properties); + markCb(O.Prototype); + } else if ('Cells' in O) { + fgs.add(O); + markCb(O.properties); + markCb(O.Prototype); + O.Cells.forEach((cell) => { + markCb(cell.HeldValue); + }); + } else if ('WeakMapData' in O) { + weakmaps.add(O); + markCb(O.properties); + markCb(O.Prototype); + O.WeakMapData.forEach((r) => { + ephemeronQueue.push(r); + }); + } else if ('WeakSetData' in O) { + weaksets.add(O); + markCb(O.properties); + markCb(O.Prototype); + } else if (O.mark) { + O.mark(markCb); + } + }; + + markCb(surroundingAgent); + + while (ephemeronQueue.length > 0) { + const item = ephemeronQueue.shift(); + if (marked.has(item.Key)) { + markCb(item.Value); + } + } + + weakrefs.forEach((ref) => { + if (!marked.has(ref.WeakRefTarget)) { + ref.WeakRefTarget = undefined; + } + }); + + fgs.forEach((fg) => { + let dirty = false; + fg.Cells.forEach((cell) => { + if (!marked.has(cell.WeakRefTarget)) { + cell.WeakRefTarget = undefined; + dirty = true; + } + }); + if (dirty) { + X(HostEnqueueFinalizationRegistryCleanupJob(fg)); + } + }); + + weakmaps.forEach((map) => { + map.WeakMapData.forEach((r) => { + if (!marked.has(r.Key)) { + r.Key = undefined; + r.Value = undefined; + } + }); + }); + + weaksets.forEach((set) => { + set.WeakSetData.forEach((obj, i) => { + if (!marked.has(obj)) { + set.WeakSetData[i] = undefined; + } + }); + }); +} + +// https://tc39.es/ecma262/#sec-jobs +export function runJobQueue() { + if (surroundingAgent.executionContextStack.some((e) => e.ScriptOrModule !== Value.null)) { + return; + } + + // At some future point in time, when there is no running execution context + // and the execution context stack is empty, the implementation must: + while (surroundingAgent.jobQueue.length > 0) { // eslint-disable-line no-constant-condition + const { + job: abstractClosure, + callerRealm, + callerScriptOrModule, + } = surroundingAgent.jobQueue.shift(); + + // 1. Perform any implementation-defined preparation steps. + const newContext = new ExecutionContext(); + surroundingAgent.executionContextStack.push(newContext); + newContext.Function = Value.null; + newContext.Realm = callerRealm; + newContext.ScriptOrModule = callerScriptOrModule; + // 2. Call the abstract closure. + X(abstractClosure()); + // 3. Perform any host-defined cleanup steps, after which the execution context stack must be empty. + ClearKeptObjects(); + gc(); + surroundingAgent.executionContextStack.pop(newContext); + } +} + +export function evaluateScript(sourceText, realm, hostDefined) { + const s = ParseScript(sourceText, realm, hostDefined); + if (Array.isArray(s)) { + return ThrowCompletion(s[0]); + } + + return EnsureCompletion(ScriptEvaluation(s)); +} + +export class ManagedRealm extends Realm { + constructor(HostDefined = {}) { + super(); + // CreateRealm() + CreateIntrinsics(this); + this.GlobalObject = Value.undefined; + this.GlobalEnv = Value.undefined; + this.TemplateMap = []; + + // InitializeHostDefinedRealm() + const newContext = new ExecutionContext(); + newContext.Function = Value.null; + newContext.Realm = this; + newContext.ScriptOrModule = Value.null; + surroundingAgent.executionContextStack.push(newContext); + SetRealmGlobalObject(this, Value.undefined, Value.undefined); + SetDefaultGlobalBindings(this); + + // misc + surroundingAgent.executionContextStack.pop(newContext); + this.HostDefined = HostDefined; + this.topContext = newContext; + this.active = false; + } + + scope(cb) { + if (this.active) { + return cb(); + } + this.active = true; + surroundingAgent.executionContextStack.push(this.topContext); + const r = cb(); + surroundingAgent.executionContextStack.pop(this.topContext); + this.active = false; + return r; + } + + evaluateScript(sourceText, { specifier } = {}) { + if (typeof sourceText !== 'string') { + throw new TypeError('sourceText must be a string'); + } + + const res = this.scope(() => { + const realm = surroundingAgent.currentRealmRecord; + return evaluateScript(sourceText, realm, { + specifier, + public: { specifier }, + }); + }); + + if (!(res instanceof AbruptCompletion)) { + runJobQueue(); + } + + return res; + } + + createSourceTextModule(specifier, sourceText) { + if (typeof sourceText !== 'string') { + throw new TypeError('sourceText must be a string'); + } + if (typeof specifier !== 'string') { + throw new TypeError('specifier must be a string'); + } + const module = this.scope(() => ParseModule(sourceText, this, { + specifier, + SourceTextModuleRecord: ManagedSourceTextModuleRecord, + })); + if (Array.isArray(module)) { + return ThrowCompletion(module[0]); + } + return module; + } +} + +class ManagedSourceTextModuleRecord extends SourceTextModuleRecord { + Evaluate() { + const r = super.Evaluate(); + if (!(r instanceof AbruptCompletion)) { + runJobQueue(); + } + return r; + } +} diff --git a/engine262/src/completion.mjs b/engine262/src/completion.mjs new file mode 100644 index 0000000..09405a2 --- /dev/null +++ b/engine262/src/completion.mjs @@ -0,0 +1,136 @@ +import { surroundingAgent } from './engine.mjs'; +import { + Assert, + CreateBuiltinFunction, + PerformPromiseThen, + PromiseResolve, + SetFunctionLength, +} from './abstract-ops/all.mjs'; +import { Value } from './value.mjs'; +import { resume } from './helpers.mjs'; + +// #sec-completion-record-specification-type +export function Completion(init) { + if (new.target === Completion) { + this.Type = init.Type; + this.Value = init.Value; + this.Target = init.Target; + } else { + // 1. Assert: completionRecord is a Completion Record. + Assert(init instanceof Completion); + // 2. Return completionRecord as the Completion Record of this abstract operation. + return init; + } +} + +// NON-SPEC +Completion.prototype.mark = function mark(m) { + m(this.Value); +}; + +// #sec-normalcompletion +export function NormalCompletion(argument) { + // 1. Return Completion { [[Type]]: normal, [[Value]]: argument, [[Target]]: empty }. + return new Completion({ Type: 'normal', Value: argument, Target: undefined }); +} + +Object.defineProperty(NormalCompletion, Symbol.hasInstance, { + value: function hasInstance(v) { + return v instanceof Completion && v.Type === 'normal'; + }, + writable: true, + enumerable: false, + configurable: true, +}); + +export class AbruptCompletion { + static [Symbol.hasInstance](v) { + return v instanceof Completion && v.Type !== 'normal'; + } +} + +// #sec-throwcompletion +export function ThrowCompletion(argument) { + // 1. Return Completion { [[Type]]: throw, [[Value]]: argument, [[Target]]: empty }. + return new Completion({ Type: 'throw', Value: argument, Target: undefined }); +} + +// 6.2.3.4 #sec-updateempty +export function UpdateEmpty(completionRecord, value) { + Assert(completionRecord instanceof Completion); + // 1. Assert: If completionRecord.[[Type]] is either return or throw, then completionRecord.[[Value]] is not empty. + Assert(!(completionRecord.Type === 'return' || completionRecord.Type === 'throw') || completionRecord.Value !== undefined); + // 2. If completionRecord.[[Value]] is not empty, return Completion(completionRecord). + if (completionRecord.Value !== undefined) { + return Completion(completionRecord); + } + // 3. Return Completion { [[Type]]: completionRecord.[[Type]], [[Value]]: value, [[Target]]: completionRecord.[[Target]] }. + return new Completion({ Type: completionRecord.Type, Value: value, Target: completionRecord.Target }); +} + +// #sec-returnifabrupt +export function ReturnIfAbrupt() { + /* istanbul skip next */ + throw new TypeError('ReturnIfAbrupt requires build'); +} + +// #sec-returnifabrupt-shorthands ? OperationName() +export const Q = ReturnIfAbrupt; + +// #sec-returnifabrupt-shorthands ! OperationName() +export function X() { + /* istanbul skip next */ + throw new TypeError('X() requires build'); +} + +// 25.6.1.1.1 #sec-ifabruptrejectpromise +export function IfAbruptRejectPromise() { + /* istanbul skip next */ + throw new TypeError('IfAbruptRejectPromise requires build'); +} + +export function EnsureCompletion(val) { + if (val instanceof Completion) { + return val; + } + return NormalCompletion(val); +} + +export function AwaitFulfilledFunctions([value]) { + const F = surroundingAgent.activeFunctionObject; + const asyncContext = F.AsyncContext; + const prevContext = surroundingAgent.runningExecutionContext; + // Suspend prevContext + surroundingAgent.executionContextStack.push(asyncContext); + resume(asyncContext, NormalCompletion(value)); + Assert(surroundingAgent.runningExecutionContext === prevContext); + return Value.undefined; +} + +function AwaitRejectedFunctions([reason]) { + const F = surroundingAgent.activeFunctionObject; + const asyncContext = F.AsyncContext; + const prevContext = surroundingAgent.runningExecutionContext; + // Suspend prevContext + surroundingAgent.executionContextStack.push(asyncContext); + resume(asyncContext, ThrowCompletion(reason)); + Assert(surroundingAgent.runningExecutionContext === prevContext); + return Value.undefined; +} + +export function* Await(value) { + const asyncContext = surroundingAgent.runningExecutionContext; + const promise = Q(PromiseResolve(surroundingAgent.intrinsic('%Promise%'), value)); + const stepsFulfilled = AwaitFulfilledFunctions; + const onFulfilled = X(CreateBuiltinFunction(stepsFulfilled, ['AsyncContext'])); + X(SetFunctionLength(onFulfilled, new Value(1))); + onFulfilled.AsyncContext = asyncContext; + const stepsRejected = AwaitRejectedFunctions; + const onRejected = X(CreateBuiltinFunction(stepsRejected, ['AsyncContext'])); + X(SetFunctionLength(onRejected, new Value(1))); + onRejected.AsyncContext = asyncContext; + X(PerformPromiseThen(promise, onFulfilled, onRejected)); + surroundingAgent.executionContextStack.pop(asyncContext); + const completion = yield Value.undefined; + return completion; +} diff --git a/engine262/src/engine.mjs b/engine262/src/engine.mjs new file mode 100644 index 0000000..08e46f0 --- /dev/null +++ b/engine262/src/engine.mjs @@ -0,0 +1,404 @@ +import { Value } from './value.mjs'; +import { + AbruptCompletion, + EnsureCompletion, + NormalCompletion, + ThrowCompletion, + Q, X, +} from './completion.mjs'; +import { + IsCallable, + Call, Construct, Assert, GetModuleNamespace, + PerformPromiseThen, CreateBuiltinFunction, + GetActiveScriptOrModule, + CleanupFinalizationRegistry, + CreateArrayFromList, +} from './abstract-ops/all.mjs'; +import { GlobalDeclarationInstantiation } from './runtime-semantics/all.mjs'; +import { Evaluate } from './evaluator.mjs'; +import { CyclicModuleRecord } from './modules.mjs'; +import { CallSite, unwind } from './helpers.mjs'; +import * as messages from './messages.mjs'; + +export const FEATURES = Object.freeze([ + { + name: 'Top-Level Await', + flag: 'top-level-await', + url: 'https://github.com/tc39/proposal-top-level-await', + }, + { + name: 'Hashbang Grammar', + flag: 'hashbang', + url: 'https://github.com/tc39/proposal-hashbang', + }, + { + name: 'Numeric Separators', + flag: 'numeric-separators', + url: 'https://github.com/tc39/proposal-numeric-separator', + }, + { + name: 'RegExp Match Indices', + flag: 'regexp-match-indices', + url: 'https://github.com/tc39/proposal-regexp-match-indices', + }, + { + name: 'FinalizationRegistry.prototype.cleanupSome', + flag: 'cleanup-some', + url: 'https://github.com/tc39/proposal-cleanup-some', + }, + { + name: 'Arbitrary Module Namespace Names', + flag: 'arbitrary-module-namespace-names', + url: 'https://github.com/tc39/ecma262/pull/2154', + }, +].map(Object.freeze)); + +let agentSignifier = 0; +// #sec-agents +export class Agent { + constructor(options = {}) { + // #table-agent-record + const Signifier = agentSignifier; + agentSignifier += 1; + this.AgentRecord = { + LittleEndian: Value.true, + CanBlock: Value.true, + Signifier, + IsLockFree1: Value.true, + IsLockFree2: Value.true, + CandidateExecution: undefined, + KeptAlive: new Set(), + }; + + // #execution-context-stack + this.executionContextStack = []; + const stackPop = this.executionContextStack.pop; + this.executionContextStack.pop = function pop(ctx) { + if (!ctx.poppedForTailCall) { + const popped = stackPop.call(this); + Assert(popped === ctx); + } + }; + + // NON-SPEC + this.jobQueue = []; + this.hostDefinedOptions = { + ...options, + features: FEATURES.reduce((acc, { flag }) => { + if (options.features) { + acc[flag] = options.features.includes(flag); + } else { + acc[flag] = false; + } + return acc; + }, {}), + }; + } + + // #running-execution-context + get runningExecutionContext() { + return this.executionContextStack[this.executionContextStack.length - 1]; + } + + // #current-realm + get currentRealmRecord() { + return this.runningExecutionContext.Realm; + } + + // #active-function-object + get activeFunctionObject() { + return this.runningExecutionContext.Function; + } + + // Get an intrinsic by name for the current realm + intrinsic(name) { + return this.currentRealmRecord.Intrinsics[name]; + } + + // Generate a throw completion using message templates + Throw(type, template, ...templateArgs) { + if (type instanceof Value) { + return ThrowCompletion(type); + } + const message = messages[template](...templateArgs); + const cons = this.currentRealmRecord.Intrinsics[`%${type}%`]; + let error; + if (type === 'AggregateError') { + error = X(Construct(cons, [ + X(CreateArrayFromList([])), + new Value(message), + ])); + } else { + error = X(Construct(cons, [new Value(message)])); + } + return ThrowCompletion(error); + } + + queueJob(queueName, job) { + const callerContext = this.runningExecutionContext; + const callerRealm = callerContext.Realm; + const callerScriptOrModule = GetActiveScriptOrModule(); + const pending = { + queueName, + job, + callerRealm, + callerScriptOrModule, + }; + this.jobQueue.push(pending); + } + + // NON-SPEC: Check if a feature is enabled in this agent. + feature(name) { + return this.hostDefinedOptions.features[name]; + } + + // NON-SPEC + mark(m) { + this.AgentRecord.KeptAlive.forEach((v) => { + m(v); + }); + this.executionContextStack.forEach((e) => { + m(e); + }); + this.jobQueue.forEach((j) => { + m(j.callerRealm); + m(j.callerScriptOrModule); + }); + } +} + +export let surroundingAgent; +export function setSurroundingAgent(a) { + surroundingAgent = a; +} + +// #sec-execution-contexts +export class ExecutionContext { + constructor() { + this.codeEvaluationState = undefined; + this.Function = undefined; + this.Realm = undefined; + this.ScriptOrModule = undefined; + this.VariableEnvironment = undefined; + this.LexicalEnvironment = undefined; + + // NON-SPEC + this.callSite = new CallSite(this); + this.promiseCapability = undefined; + this.poppedForTailCall = false; + } + + copy() { + const e = new ExecutionContext(); + e.codeEvaluationState = this.codeEvaluationState; + e.Function = this.Function; + e.Realm = this.Realm; + e.ScriptOrModule = this.ScriptOrModule; + e.VariableEnvironment = this.VariableEnvironment; + e.LexicalEnvironment = this.LexicalEnvironment; + + e.callSite = this.callSite.clone(e); + e.promiseCapability = this.promiseCapability; + return e; + } + + // NON-SPEC + mark(m) { + m(this.Function); + m(this.Realm); + m(this.ScriptOrModule); + m(this.VariableEnvironment); + m(this.LexicalEnvironment); + m(this.promiseCapability); + } +} + +// 15.1.10 #sec-runtime-semantics-scriptevaluation +export function ScriptEvaluation(scriptRecord) { + if (surroundingAgent.hostDefinedOptions.boost) { + return surroundingAgent.hostDefinedOptions.boost.evaluateScript(scriptRecord); + } + + const globalEnv = scriptRecord.Realm.GlobalEnv; + const scriptContext = new ExecutionContext(); + scriptContext.Function = Value.null; + scriptContext.Realm = scriptRecord.Realm; + scriptContext.ScriptOrModule = scriptRecord; + scriptContext.VariableEnvironment = globalEnv; + scriptContext.LexicalEnvironment = globalEnv; + scriptContext.HostDefined = scriptRecord.HostDefined; + // Suspend runningExecutionContext + surroundingAgent.executionContextStack.push(scriptContext); + const scriptBody = scriptRecord.ECMAScriptCode; + let result = EnsureCompletion(GlobalDeclarationInstantiation(scriptBody, globalEnv)); + + if (result.Type === 'normal') { + result = EnsureCompletion(unwind(Evaluate(scriptBody))); + } + + if (result.Type === 'normal' && !result.Value) { + result = NormalCompletion(Value.undefined); + } + + // Suspend scriptCtx + surroundingAgent.executionContextStack.pop(scriptContext); + // Resume(surroundingAgent.runningExecutionContext); + + return result; +} + +// #sec-hostenqueuepromisejob +export function HostEnqueuePromiseJob(job, _realm) { + surroundingAgent.queueJob('PromiseJobs', job); +} + +// #sec-agentsignifier +export function AgentSignifier() { + // 1. Let AR be the Agent Record of the surrounding agent. + const AR = surroundingAgent.AgentRecord; + // 2. Return AR.[[Signifier]]. + return AR.Signifier; +} + +export function HostEnsureCanCompileStrings(callerRealm, calleeRealm) { + if (surroundingAgent.hostDefinedOptions.ensureCanCompileStrings !== undefined) { + Q(surroundingAgent.hostDefinedOptions.ensureCanCompileStrings(callerRealm, calleeRealm)); + } + return NormalCompletion(undefined); +} + +export function HostPromiseRejectionTracker(promise, operation) { + const realm = surroundingAgent.currentRealmRecord; + if (realm && realm.HostDefined.promiseRejectionTracker) { + X(realm.HostDefined.promiseRejectionTracker(promise, operation)); + } +} + +export function HostHasSourceTextAvailable(func) { + if (surroundingAgent.hostDefinedOptions.hasSourceTextAvailable) { + return X(surroundingAgent.hostDefinedOptions.hasSourceTextAvailable(func)); + } + return Value.true; +} + +export function HostResolveImportedModule(referencingScriptOrModule, specifier) { + const realm = referencingScriptOrModule.Realm || surroundingAgent.currentRealmRecord; + if (realm.HostDefined.resolveImportedModule) { + specifier = specifier.stringValue(); + if (referencingScriptOrModule !== Value.null) { + if (!referencingScriptOrModule.HostDefined.moduleMap) { + referencingScriptOrModule.HostDefined.moduleMap = new Map(); + } + if (referencingScriptOrModule.HostDefined.moduleMap.has(specifier)) { + return referencingScriptOrModule.HostDefined.moduleMap.get(specifier); + } + } + const resolved = Q(realm.HostDefined.resolveImportedModule(referencingScriptOrModule, specifier)); + if (referencingScriptOrModule !== Value.null) { + referencingScriptOrModule.HostDefined.moduleMap.set(specifier, resolved); + } + return resolved; + } + return surroundingAgent.Throw('Error', 'CouldNotResolveModule', specifier); +} + +function FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, completion) { + // 1. If completion is an abrupt completion, then perform ! Call(promiseCapability.[[Reject]], undefined, « completion.[[Value]] »). + if (completion instanceof AbruptCompletion) { + X(Call(promiseCapability.Reject, Value.undefined, [completion.Value])); + } else { // 2. Else, + // a. Assert: completion is a normal completion and completion.[[Value]] is undefined. + Assert(completion instanceof NormalCompletion); + // b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier). + const moduleRecord = X(HostResolveImportedModule(referencingScriptOrModule, specifier)); + // c. Assert: Evaluate has already been invoked on moduleRecord and successfully completed. + // d. Let namespace be GetModuleNamespace(moduleRecord). + const namespace = EnsureCompletion(GetModuleNamespace(moduleRecord)); + // e. If namespace is an abrupt completion, perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »). + if (namespace instanceof AbruptCompletion) { + X(Call(promiseCapability.Reject, Value.undefined, [namespace.Value])); + } else { + // f. Else, perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »). + X(Call(promiseCapability.Resolve, Value.undefined, [namespace.Value])); + } + } +} + +export function HostImportModuleDynamically(referencingScriptOrModule, specifier, promiseCapability) { + surroundingAgent.queueJob('ImportModuleDynamicallyJobs', () => { + const finish = (c) => FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, c); + const c = (() => { + const module = Q(HostResolveImportedModule(referencingScriptOrModule, specifier)); + Q(module.Link()); + const maybePromise = Q(module.Evaluate()); + if (module instanceof CyclicModuleRecord) { + const onFulfilled = CreateBuiltinFunction(([v = Value.undefined]) => { + finish(NormalCompletion(v)); + return Value.undefined; + }, []); + const onRejected = CreateBuiltinFunction(([r = Value.undefined]) => { + finish(ThrowCompletion(r)); + return Value.undefined; + }, []); + PerformPromiseThen(maybePromise, onFulfilled, onRejected); + } else { + finish(NormalCompletion(undefined)); + } + })(); + if (c instanceof AbruptCompletion) { + finish(c); + } + }); + return NormalCompletion(Value.undefined); +} + +// #sec-hostgetimportmetaproperties +export function HostGetImportMetaProperties(moduleRecord) { + const realm = surroundingAgent.currentRealmRecord; + if (realm.HostDefined.getImportMetaProperties) { + return X(realm.HostDefined.getImportMetaProperties(moduleRecord.HostDefined.public)); + } + return []; +} + +// #sec-hostfinalizeimportmeta +export function HostFinalizeImportMeta(importMeta, moduleRecord) { + const realm = surroundingAgent.currentRealmRecord; + if (realm.HostDefined.finalizeImportMeta) { + return X(realm.HostDefined.finalizeImportMeta(importMeta, moduleRecord.HostDefined.public)); + } + return Value.undefined; +} + +// #sec-host-cleanup-finalization-registry +const scheduledForCleanup = new Set(); +export function HostEnqueueFinalizationRegistryCleanupJob(fg) { + if (surroundingAgent.hostDefinedOptions.cleanupFinalizationRegistry !== undefined) { + Q(surroundingAgent.hostDefinedOptions.cleanupFinalizationRegistry(fg)); + } else { + if (!scheduledForCleanup.has(fg)) { + scheduledForCleanup.add(fg); + surroundingAgent.queueJob('FinalizationCleanup', () => { + scheduledForCleanup.delete(fg); + CleanupFinalizationRegistry(fg); + }); + } + } + return NormalCompletion(undefined); +} + +// #sec-hostmakejobcallback +export function HostMakeJobCallback(callback) { + // 1. Assert: IsCallable(callback) is true. + Assert(IsCallable(callback) === Value.true); + // 2. Return the JobCallback Record { [[Callback]]: callback, [[HostDefined]]: empty }. + return { Callback: callback, HostDefined: undefined }; +} + +// #sec-hostcalljobcallback +export function HostCallJobCallback(jobCallback, V, argumentsList) { + // 1. Assert: IsCallable(jobCallback.[[Callback]]) is true. + Assert(IsCallable(jobCallback.Callback) === Value.true); + // 1. Return ? Call(jobCallback.[[Callback]], V, argumentsList). + return Q(Call(jobCallback.Callback, V, argumentsList)); +} diff --git a/engine262/src/environment.mjs b/engine262/src/environment.mjs new file mode 100644 index 0000000..c02cf9d --- /dev/null +++ b/engine262/src/environment.mjs @@ -0,0 +1,982 @@ +import { AbstractModuleRecord } from './modules.mjs'; +import { + Descriptor, + Reference, + Type, + Value, + wellKnownSymbols, +} from './value.mjs'; +import { surroundingAgent } from './engine.mjs'; +import { + Assert, + DefinePropertyOrThrow, + Get, + HasOwnProperty, + HasProperty, + IsDataDescriptor, + IsExtensible, + IsPropertyKey, + Set, + ToBoolean, + isECMAScriptFunctionObject, +} from './abstract-ops/all.mjs'; +import { NormalCompletion, Q, X } from './completion.mjs'; +import { ValueMap } from './helpers.mjs'; + +// #sec-environment-records +export class EnvironmentRecord { + constructor() { + this.OuterEnv = undefined; + } + + // NON-SPEC + mark(m) { + m(this.OuterEnv); + } +} + +// #sec-declarative-environment-records +export class DeclarativeEnvironmentRecord extends EnvironmentRecord { + constructor() { + super(); + this.bindings = new ValueMap(); + } + + // #sec-declarative-environment-records-hasbinding-n + HasBinding(N) { + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. If envRec has a binding for the name that is the value of N, return true. + if (envRec.bindings.has(N)) { + return Value.true; + } + // 3. Return false. + return Value.false; + } + + // #sec-declarative-environment-records-createmutablebinding-n-d + CreateMutableBinding(N, D) { + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec does not already have a binding for N. + Assert(!envRec.bindings.has(N)); + // 3. Create a mutable binding in envRec for N and record that it is uninitialized. If D + // is true, record that the newly created binding may be delted by a subsequent + // DeleteBinding call. + this.bindings.set(N, { + indirect: false, + initialized: false, + mutable: true, + strict: undefined, + deletable: D === Value.true, + value: undefined, + mark(m) { + m(this.value); + }, + }); + // 4. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + // #sec-declarative-environment-records-createimmutablebinding-n-s + CreateImmutableBinding(N, S) { + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec does not already have a binding for N. + Assert(!envRec.bindings.has(N)); + // 3. Create an immutable binding in envRec for N and record that it is uninitialized. If + // S is true, record that the newly created binding is a strict binding. + this.bindings.set(N, { + indirect: false, + initialized: false, + mutable: false, + strict: S === Value.true, + deletable: false, + value: undefined, + mark(m) { + m(this.value); + }, + }); + // 4. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + // #sec-declarative-environment-records-initializebinding-n-v + InitializeBinding(N, V) { + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec must have an uninitialized binding for N. + const binding = envRec.bindings.get(N); + Assert(binding !== undefined && binding.initialized === false); + // 3. Set the bound value for N in envRec to V. + binding.value = V; + // 4. Record that the binding for N in envRec has been initialized. + binding.initialized = true; + // 5. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + // #sec-declarative-environment-records-setmutablebinding-n-v-s + SetMutableBinding(N, V, S) { + Assert(IsPropertyKey(N)); + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. If envRec does not have a binding for N, then + if (!envRec.bindings.has(N)) { + // a. If S is true, throw a ReferenceError exception. + if (S === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', N); + } + // b. Perform envRec.CreateMutableBinding(N, true). + envRec.CreateMutableBinding(N, true); + // c. Perform envRec.InitializeBinding(N, V). + envRec.InitializeBinding(N, V); + // d. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + const binding = this.bindings.get(N); + // 3. If the binding for N in envRec is a strict binding, set S to true. + if (binding.strict === true) { + S = Value.true; + } + // 4. If the binding for N in envRec has not yet been initialized, throw a ReferenceError exception. + if (binding.initialized === false) { + return surroundingAgent.Throw('ReferenceError', 'NotInitialized', N); + } + // 5. Else if the binding for N in envRec is a mutable binding, change its bound value to V. + if (binding.mutable === true) { + binding.value = V; + } else { + // a. Assert: This is an attempt to change the value of an immutable binding. + // b. If S is true, throw a TypeError exception. + if (S === Value.true) { + return surroundingAgent.Throw('TypeError', 'AssignmentToConstant', N); + } + } + // 7. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + // #sec-declarative-environment-records-getbindingvalue-n-s + GetBindingValue(N) { + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec has a binding for N. + const binding = envRec.bindings.get(N); + Assert(binding !== undefined); + // 3. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. + if (binding.initialized === false) { + return surroundingAgent.Throw('ReferenceError', 'NotInitialized', N); + } + // 4. Return the value currently bound to N in envRec. + return binding.value; + } + + // #sec-declarative-environment-records-deletebinding-n + DeleteBinding(N) { + // 1. Let envRec be the declarative Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec has a binding for the name that is the value of N. + const binding = envRec.bindings.get(N); + Assert(binding !== undefined); + // 3. If the binding for N in envRec cannot be deleted, return false. + if (binding.deletable === false) { + return Value.false; + } + // 4. Remove the binding for N from envRec. + envRec.bindings.delete(N); + // 5. Return true. + return Value.true; + } + + // #sec-declarative-environment-records-hasthisbinding + HasThisBinding() { + // 1. Return false. + return Value.false; + } + + // #sec-declarative-environment-records-hassuperbinding + HasSuperBinding() { + // 1. Return false. + return Value.false; + } + + // #sec-declarative-environment-records-withbaseobject + WithBaseObject() { + // 1. Return undefined. + return Value.undefined; + } + + // NON-SPEC + mark(m) { + m(this.bindings); + } +} + +// #sec-object-environment-records +export class ObjectEnvironmentRecord extends EnvironmentRecord { + constructor(BindingObject) { + super(); + this.bindingObject = BindingObject; + this.withEnvironment = false; + } + + // #sec-object-environment-records-hasbinding-n + HasBinding(N) { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. Let bindings be the binding object for envRec. + const bindings = envRec.bindingObject; + // 3. Let foundBinding be ? HasProperty(bindings, N). + const foundBinding = Q(HasProperty(bindings, N)); + // 4. If foundBinding is false, return false. + if (foundBinding === Value.false) { + return Value.false; + } + // 5. If the withEnvironment flag of envRec i s false, return true. + if (envRec.withEnvironment === false) { + return Value.true; + } + // 6. Let unscopables be ? Get(bindings, @@unscopables). + const unscopables = Q(Get(bindings, wellKnownSymbols.unscopables)); + // 7. If Type(unscopables) is Object, then + if (Type(unscopables) === 'Object') { + // a. Let blocked be ! ToBoolean(? Get(unscopables, N)). + const blocked = X(ToBoolean(Q(Get(unscopables, N)))); + // b. If blocked is true, return false. + if (blocked === Value.true) { + return Value.false; + } + } + // 8. Return true. + return Value.true; + } + + // #sec-object-environment-records-createmutablebinding-n-d + CreateMutableBinding(N, D) { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. Let envRec be the object Environment Record for which the method was invoked. + const bindings = envRec.bindingObject; + // 3. Return ? DefinePropertyOrThrow(bindings, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }). + return Q(DefinePropertyOrThrow(bindings, N, Descriptor({ + Value: Value.undefined, + Writable: Value.true, + Enumerable: Value.true, + Configurable: D, + }))); + } + + // #sec-object-environment-records-createimmutablebinding-n-s + CreateImmutableBinding(_N, _S) { + Assert(false, 'CreateImmutableBinding called on an Object Environment Record'); + } + + // #sec-object-environment-records-initializebinding-n-v + InitializeBinding(N, V) { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec must have an uninitialized binding for N. + // 3. Record that the binding for N in envRec has been initialized. + // 4. Return ? envRec.SetMutableBinding(N, V, false). + return Q(envRec.SetMutableBinding(N, V, Value.false)); + } + + // #sec-object-environment-records-setmutablebinding-n-v-s + SetMutableBinding(N, V, S) { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. Let bindings be the binding object for envRec. + const bindings = envRec.bindingObject; + // 3. Let stillExists be ? HasProperty(bindings, N). + const stillExists = Q(HasProperty(bindings, N)); + // 4. If stillExists is false and S is true, throw a ReferenceError exception. + if (stillExists === Value.false && S === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', N); + } + // 5. Return ? Set(bindings, N, V, S). + return Q(Set(bindings, N, V, S)); + } + + // #sec-object-environment-records-getbindingvalue-n-s + GetBindingValue(N, S) { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. Let bindings be the binding object for envRec. + const bindings = envRec.bindingObject; + // 3. Let value be ? HasProperty(bindings, N). + const value = Q(HasProperty(bindings, N)); + // 4. If value is false, then + if (value === Value.false) { + // a. If S is false, return the value undefined; otherwise throw a ReferenceError exception. + if (S === Value.false) { + return Value.undefined; + } else { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', N); + } + } + // 5. Return ? Get(bindings, N). + return Q(Get(bindings, N)); + } + + // #sec-object-environment-records-deletebinding-n + DeleteBinding(N) { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. Let bindings be the binding object for envRec. + const bindings = envRec.bindingObject; + // 3. Return ? bindings.[[Delete]](N). + return Q(bindings.Delete(N)); + } + + // #sec-object-environment-records-hasthisbinding + HasThisBinding() { + // 1. Return false. + return Value.false; + } + + // #sec-object-environment-records-hassuperbinding + HasSuperBinding() { + // 1. Return falase. + return Value.false; + } + + // #sec-object-environment-records-withbaseobject + WithBaseObject() { + // 1. Let envRec be the object Environment Record for which the method was invoked. + const envRec = this; + // 2. If the withEnvironment flag of envRec is true, return the binding object for envRec. + if (envRec.withEnvironment === true) { + return envRec.bindingObject; + } + // 3. Otherwise, return undefined. + return Value.undefined; + } + + // NON-SPEC + mark(m) { + m(this.bindingObject); + } +} + +// #sec-function-environment-records +export class FunctionEnvironmentRecord extends DeclarativeEnvironmentRecord { + constructor() { + super(); + this.ThisValue = undefined; + this.ThisBindingValue = undefined; + this.FunctionObject = undefined; + this.HomeObject = Value.undefined; + this.NewTarget = undefined; + } + + // #sec-bindthisvalue + BindThisValue(V) { + // 1. Let envRec be the function Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec.[[ThisBindingStatus]] is not lexical. + Assert(envRec.ThisBindingStatus !== 'lexical'); + // 3. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception. + if (envRec.ThisBindingStatus === 'initialized') { + return surroundingAgent.Throw('ReferenceError', 'InvalidThis'); + } + // 4. Set envRec.[[ThisValue]] to V. + envRec.ThisValue = V; + // 5. Set envRec.[[ThisBindingStatus]] to initialized. + envRec.ThisBindingStatus = 'initialized'; + // 6. Return V. + return V; + } + + // #sec-function-environment-records-hasthisbinding + HasThisBinding() { + // 1. Let envRec be the function Environment Record for which the method was invoked. + const envRec = this; + // 2. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true. + if (envRec.ThisBindingStatus === 'lexical') { + return Value.false; + } else { + return Value.true; + } + } + + // #sec-function-environment-records-hassuperbinding + HasSuperBinding() { + // 1. Let envRec be the function Environment Record for which the method was invoked. + const envRec = this; + // 2. If envRec.[[ThisBindingStatus]] is lexical, return false. + if (envRec.ThisBindingStatus === 'lexical') { + return Value.false; + } + // 3. If envRec.[[HomeObject]] has the value undefined, return false; otherwise, return true. + if (Type(envRec.HomeObject) === 'Undefined') { + return Value.false; + } else { + return Value.true; + } + } + + // #sec-function-environment-records-getthisbinding + GetThisBinding() { + // 1. Let envRec be the function Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec.[[ThisBindingStatus]] is not lexical. + Assert(envRec.ThisBindingStatus !== 'lexical'); + // 3. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception. + if (envRec.ThisBindingStatus === 'uninitialized') { + return surroundingAgent.Throw('ReferenceError', 'InvalidThis'); + } + // 4. Return envRec.[[ThisValue]]. + return envRec.ThisValue; + } + + // #sec-getsuperbase + GetSuperBase() { + // 1. Let envRec be the function Environment Record for which the method was invoked. + const envRec = this; + // 2. Let home be envRec.[[HomeObject]]. + const home = envRec.HomeObject; + // 3. If home has the value undefined, return undefined. + if (Type(home) === 'Undefined') { + return Value.undefined; + } + // 4. Assert: Type(home) is Object. + Assert(Type(home) === 'Object'); + // 5. Return ? home.[[GetPrototypeOf]](). + return Q(home.GetPrototypeOf()); + } + + mark(m) { + super.mark(m); + m(this.ThisValue); + m(this.ThisBindingValue); + m(this.FunctionObject); + m(this.HomeObject); + m(this.NewTarget); + } +} + +// #sec-global-environment-records +export class GlobalEnvironmentRecord extends EnvironmentRecord { + constructor() { + super(); + this.ObjectRecord = undefined; + this.GlobalThisValue = undefined; + this.DeclarativeRecord = undefined; + this.VarNames = undefined; + } + + // #sec-global-environment-records-hasbinding-n + HasBinding(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = envRec.DeclarativeRecord; + // 3. If DclRec.HasBinding(N) is true, return true. + if (DclRec.HasBinding(N) === Value.true) { + return Value.true; + } + // 4. If DclRec.HasBinding(N) is true, return true. + const ObjRec = envRec.ObjectRecord; + // 5. Let ObjRec be envRec.[[ObjectRecord]]. + return ObjRec.HasBinding(N); + } + + // #sec-global-environment-records-createmutablebinding-n-d + CreateMutableBinding(N, D) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = envRec.DeclarativeRecord; + // 3. If DclRec.HasBinding(N) is true, throw a TypeError exception. + if (DclRec.HasBinding(N) === Value.true) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', N); + } + // 4. Return DclRec.CreateMutableBinding(N, D). + return DclRec.CreateMutableBinding(N, D); + } + + // #sec-global-environment-records-createimmutablebinding-n-s + CreateImmutableBinding(N, S) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = envRec.DeclarativeRecord; + // 3. If DclRec.HasBinding(N) is true, throw a TypeError exception. + if (DclRec.HasBinding(N) === Value.true) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', N); + } + // Return DclRec.CreateImmutableBinding(N, S). + return DclRec.CreateImmutableBinding(N, S); + } + + // #sec-global-environment-records-initializebinding-n-v + InitializeBinding(N, V) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = envRec.DeclarativeRecord; + // 3. If DclRec.HasBinding(N) is true, then + if (DclRec.HasBinding(N) === Value.true) { + // a. Return DclRec.InitializeBinding(N, V). + return DclRec.InitializeBinding(N, V); + } + // 4. Assert: If the binding exists, it must be in the object Environment Record. + // 5. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 6. Return ? ObjRec.InitializeBinding(N, V). + return ObjRec.InitializeBinding(N, V); + } + + // #sec-global-environment-records-setmutablebinding-n-v-s + SetMutableBinding(N, V, S) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = envRec.DeclarativeRecord; + // 3. If DclRec.HasBinding(N) is true, then + if (DclRec.HasBinding(N) === Value.true) { + // a. Return DclRec.SetMutableBinding(N, V, S). + return DclRec.SetMutableBinding(N, V, S); + } + // 4. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 5. Return ? ObjRec.SetMutableBinding(N, V, S). + return Q(ObjRec.SetMutableBinding(N, V, S)); + } + + // #sec-global-environment-records-getbindingvalue-n-s + GetBindingValue(N, S) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = envRec.DeclarativeRecord; + // 3. If DclRec.HasBinding(N) is true, then + if (DclRec.HasBinding(N) === Value.true) { + // a. Return DclRec.GetBindingValue(N, S). + return DclRec.GetBindingValue(N, S); + } + // 4. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 5. Return ? ObjRec.GetBindingValue(N, S). + return Q(ObjRec.GetBindingValue(N, S)); + } + + // #sec-global-environment-records-deletebinding-n + DeleteBinding(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let DclRec be envRec.[[DeclarativeRecord]]. + const DclRec = this.DeclarativeRecord; + // 3. Let DclRec be envRec.[[DeclarativeRecord]]. + if (DclRec.HasBinding(N) === Value.true) { + // a. Return DclRec.DeleteBinding(N). + return Q(DclRec.DeleteBinding(N)); + } + // 4. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 5. Let globalObject be the binding object for ObjRec. + const globalObject = ObjRec.bindingObject; + // 6. Let existingProp be ? HasOwnProperty(globalObject, N). + const existingProp = Q(HasOwnProperty(globalObject, N)); + // 7. If existingProp is true, then + if (existingProp === Value.true) { + // a. Let status be ? ObjRec.DeleteBinding(N). + const status = Q(ObjRec.DeleteBinding(N)); + // b. If status is true, then + if (status === Value.true) { + // i. Let varNames be envRec.[[VarNames]]. + const varNames = envRec.VarNames; + // ii. If N is an element of varNames, remove that element from the varNames. + if (varNames.includes(N)) { + varNames.splice(varNames.indexOf(N), 1); + } + } + // c. Return status. + return status; + } + // 8. Return true. + return Value.true; + } + + // #sec-global-environment-records-hasthisbinding + HasThisBinding() { + // Return true. + return Value.true; + } + + // #sec-global-environment-records-hassuperbinding + HasSuperBinding() { + // 1. Return false. + return Value.false; + } + + // #sec-global-environment-records-withbaseobject + WithBaseObject() { + // 1. Return undefined. + return Value.undefined; + } + + // #sec-global-environment-records-getthisbinding + GetThisBinding() { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Return envRec.[[GlobalThisValue]]. + return envRec.GlobalThisValue; + } + + // #sec-hasvardeclaration + HasVarDeclaration(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let varDeclaredNames be envRec.[[VarNames]]. + const varDeclaredNames = envRec.VarNames; + // 3. If varDeclaredNames contains N, return true. + if (varDeclaredNames.includes(N)) { + return Value.true; + } + // 4. Return false. + return Value.false; + } + + // #sec-haslexicaldeclaration + HasLexicalDeclaration(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let envRec be the global Environment Record for which the method was invoked. + const DclRec = envRec.DeclarativeRecord; + // 3. Let DclRec be envRec.[[DeclarativeRecord]]. + return DclRec.HasBinding(N); + } + + // #sec-hasrestrictedglobalproperty + HasRestrictedGlobalProperty(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 3. Let globalObject be the binding object for ObjRec. + const globalObject = ObjRec.bindingObject; + // 4. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + const existingProp = Q(globalObject.GetOwnProperty(N)); + // 5. If existingProp is undefined, return false. + if (existingProp === Value.undefined) { + return Value.false; + } + // 6. If existingProp.[[Configurable]] is true, return false. + if (existingProp.Configurable === Value.true) { + return Value.false; + } + // Return true. + return Value.true; + } + + // #sec-candeclareglobalvar + CanDeclareGlobalVar(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 3. Let globalObject be the binding object for ObjRec. + const globalObject = ObjRec.bindingObject; + // 4. Let hasProperty be ? HasOwnProperty(globalObject, N). + const hasProperty = Q(HasOwnProperty(globalObject, N)); + // 5. If hasProperty is true, return true. + if (hasProperty === Value.true) { + return Value.true; + } + // 6. Return ? IsExtensible(globalObject). + return Q(IsExtensible(globalObject)); + } + + // #sec-candeclareglobalfunction + CanDeclareGlobalFunction(N) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 3. Let globalObject be the binding object for ObjRec. + const globalObject = ObjRec.bindingObject; + // 4. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + const existingProp = Q(globalObject.GetOwnProperty(N)); + // 5. If existingProp is undefined, return ? IsExtensible(globalObject). + if (existingProp === Value.undefined) { + return Q(IsExtensible(globalObject)); + } + // 6. If existingProp.[[Configurable]] is true, return true. + if (existingProp.Configurable === Value.true) { + return Value.true; + } + // 7. If IsDataDescriptor(existingProp) is true and existingProp has attribute values + // { [[Writable]]: true, [[Enumerable]]: true }, return true. + if (IsDataDescriptor(existingProp) === true + && existingProp.Writable === Value.true + && existingProp.Enumerable === Value.true) { + return Value.true; + } + // 8. Return false. + return Value.false; + } + + // #sec-createglobalvarbinding + CreateGlobalVarBinding(N, D) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 3. Let globalObject be the binding object for ObjRec. + const globalObject = ObjRec.bindingObject; + // 4. Let hasProperty be ? HasOwnProperty(globalObject, N). + const hasProperty = Q(HasOwnProperty(globalObject, N)); + // 5. Let extensible be ? IsExtensible(globalObject). + const extensible = Q(IsExtensible(globalObject)); + // 6. If hasProperty is false and extensible is true, then + if (hasProperty === Value.false && extensible === Value.true) { + // a. Perform ? ObjRec.CreateMutableBinding(N, D). + Q(ObjRec.CreateMutableBinding(N, D)); + // b. Perform ? ObjRec.InitializeBinding(N, undefined). + Q(ObjRec.InitializeBinding(N, Value.undefined)); + } + // 7. Let varDeclaredNames be envRec.[[VarNames]]. + const varDeclaredNames = envRec.VarNames; + // 8. If varDeclaredNames does not contain N, then + if (!varDeclaredNames.includes(N)) { + // a. Append N to varDeclaredNames. + varDeclaredNames.push(N); + } + // return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + // #sec-createglobalfunctionbinding + CreateGlobalFunctionBinding(N, V, D) { + // 1. Let envRec be the global Environment Record for which the method was invoked. + const envRec = this; + // 2. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 3. Let globalObject be the binding object for ObjRec. + const globalObject = ObjRec.bindingObject; + // 4. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + const existingProp = Q(globalObject.GetOwnProperty(N)); + // 5. If existingProp is undefined or existingProp.[[Configurable]] is true, then + let desc; + if (existingProp === Value.undefined || existingProp.Configurable === Value.true) { + // a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }. + desc = Descriptor({ + Value: V, + Writable: Value.true, + Enumerable: Value.true, + Configurable: D, + }); + } else { + // a. Let desc be the PropertyDescriptor { [[Value]]: V }. + desc = Descriptor({ + Value: V, + }); + } + // 7. Perform ? DefinePropertyOrThrow(globalObject, N, desc). + Q(DefinePropertyOrThrow(globalObject, N, desc)); + // 8. Record that the binding for N in ObjRec has been initialized. + // 9. Perform ? Set(globalObject, N, V, false). + Q(Set(globalObject, N, V, Value.false)); + // 10. Let varDeclaredNames be envRec.[[VarNames]]. + const varDeclaredNames = envRec.VarNames; + // 11. If varDeclaredNames does not contain N, then + if (!varDeclaredNames.includes(N)) { + // a. Append N to varDeclaredNames. + varDeclaredNames.push(N); + } + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + mark(m) { + m(this.ObjectRecord); + m(this.GlobalThisValue); + m(this.DeclarativeRecord); + } +} + +// #sec-module-environment-records +export class ModuleEnvironmentRecord extends DeclarativeEnvironmentRecord { + // #sec-module-environment-records-getbindingvalue-n-s + GetBindingValue(N, S) { + // 1. Assert: S is true. + Assert(S === Value.true); + // 2. Let envRec be the module Environment Record for which the method was invoked. + const envRec = this; + // 3. Assert: envRec has a binding for N. + const binding = envRec.bindings.get(N); + Assert(binding !== undefined); + // 4. If the binding for N is an indirect binding, then + if (binding.indirect === true) { + // a. Let M and N2 be the indirection values provided when this binding for N was created. + const [M, N2] = binding.target; + // b.Let targetEnv be M.[[Environment]]. + const targetEnv = M.Environment; + // c. If targetEnv is undefined, throw a ReferenceError exception. + if (targetEnv === Value.undefined) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', N); + } + // d. Return ? targetEnv.GetBindingValue(N2, true). + return Q(targetEnv.GetBindingValue(N2, Value.true)); + } + // 5. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. + if (binding.initialized === false) { + return surroundingAgent.Throw('ReferenceError', 'NotInitialized', N); + } + // 6. Return the value currently bound to N in envRec. + return binding.value; + } + + // #sec-module-environment-records-deletebinding-n + DeleteBinding() { + Assert(false, 'This method is never invoked. See #sec-delete-operator-static-semantics-early-errors'); + } + + // #sec-module-environment-records-hasthisbinding + HasThisBinding() { + // Return true. + return Value.true; + } + + // #sec-module-environment-records-getthisbinding + GetThisBinding() { + // Return undefined. + return Value.undefined; + } + + // #sec-createimportbinding + CreateImportBinding(N, M, N2) { + // 1. Let envRec be the module Environment Record for which the method was invoked. + const envRec = this; + // 2. Assert: envRec does not already have a binding for N. + Assert(envRec.HasBinding(N) === Value.false); + // 3. Assert: M is a Module Record. + Assert(M instanceof AbstractModuleRecord); + // 4. Assert: When M.[[Environment]] is instantiated it will have a direct binding for N2. + // 5. Create an immutable indirect binding in envRec for N that references M and N2 as its target binding and record that the binding is initialized. + envRec.bindings.set(N, { + indirect: true, + target: [M, N2], + initialized: true, + mark(m) { + m(this.target[0]); + m(this.target[1]); + }, + }); + // 6. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } +} + +// 8.1.2.1 #sec-getidentifierreference +export function GetIdentifierReference(env, name, strict) { + // 1. If lex is the value null, then + if (env === Value.null) { + // a. Return a value of type Reference whose base value component is undefined, whose + // referenced name component is name, and whose strict reference flag is strict. + return new Reference({ + BaseValue: Value.undefined, + ReferencedName: name, + StrictReference: strict, + }); + } + // 2. Let exists be ? envRec.HasBinding(name). + const exists = Q(env.HasBinding(name)); + // 3. If exists is true, then + if (exists === Value.true) { + // a. Return a value of type Reference whose base value component is envRec, whose + // referenced name component is name, and whose strict reference flag is strict. + return new Reference({ + BaseValue: env, + ReferencedName: name, + StrictReference: strict, + }); + } else { + // a. Let outer be env.[[OuterEnv]]. + const outer = env.OuterEnv; + // b. Return ? GetIdentifierReference(outer, name, strict). + return Q(GetIdentifierReference(outer, name, strict)); + } +} + +// #sec-newdeclarativeenvironment +export function NewDeclarativeEnvironment(E) { + // 1. Let env be a new declarative Environment Record containing O as the binding object. + const env = new DeclarativeEnvironmentRecord(); + // 2. Set env.[[OuterEnv]] to E. + env.OuterEnv = E; + // 3. Return env. + return env; +} + +// #sec-newobjectenvironment +export function NewObjectEnvironment(O, E) { + // 1. Let env be a new object Environment Record containing O as the binding object. + const env = new ObjectEnvironmentRecord(O); + // 2. Set env.[[OuterEnv]] to E. + env.OuterEnv = E; + // 3. Return env. + return env; +} + +// #sec-newfunctionenvironment +export function NewFunctionEnvironment(F, newTarget) { + // 1. Assert: F is an ECMAScript function. + Assert(isECMAScriptFunctionObject(F)); + // 2. Assert: Type(newTarget) is Undefined or Object. + Assert(Type(newTarget) === 'Undefined' || Type(newTarget) === 'Object'); + // 3. Let env be a new function Environment Record containing no bindings. + const env = new FunctionEnvironmentRecord(); + // 4. Set env.[[FunctionObject]] to F. + env.FunctionObject = F; + // 5. If F.[[ThisMode]] is lexical, set env.[[ThisBindingStatus]] to lexical. + if (F.ThisMode === 'lexical') { + env.ThisBindingStatus = 'lexical'; + } else { // 6. Else, set env.[[ThisBindingStatus]] to uninitialized. + env.ThisBindingStatus = 'uninitialized'; + } + // 7. Let home be F.[[HomeObject]]. + const home = F.HomeObject; + // 8. Set env.[[HomeObject]] to home. + env.HomeObject = home; + // 9. Set env.[[NewTarget]] to newTarget. + env.NewTarget = newTarget; + // 10. Set env.[[OuterEnv]] to F.[[Environment]]. + env.OuterEnv = F.Environment; + // 11. Return env. + return env; +} + +// #sec-newglobalenvironment +export function NewGlobalEnvironment(G, thisValue) { + // 1. Let objRec be a new object Environment Record containing G as the binding object. + const objRec = new ObjectEnvironmentRecord(G); + // 2. Let dclRec be a new declarative Environment Record containing no bindings. + const dclRec = new DeclarativeEnvironmentRecord(); + // 3. Let env be a new global Environment Record. + const env = new GlobalEnvironmentRecord(); + // 4. Set env.[[ObjectRecord]] to objRec. + env.ObjectRecord = objRec; + // 5. Set env.[[GlobalThisValue]] to thisValue. + env.GlobalThisValue = thisValue; + // 6. Set env.[[DeclarativeRecord]] to dclRec. + env.DeclarativeRecord = dclRec; + // 7. Set env.[[VarNames]] to a new empty List. + env.VarNames = []; + // 8. Set env.[[OuterEnv]] to null. + env.OuterEnv = Value.null; + // 9. Return env. + return env; +} + +// #sec-newmoduleenvironment +export function NewModuleEnvironment(E) { + // 1. Let env be a new module Environment Record containing no bindings. + const env = new ModuleEnvironmentRecord(); + // 2. Set env.[[OuterEnv]] to E. + env.OuterEnv = E; + // 3. Return env. + return env; +} diff --git a/engine262/src/evaluator.mjs b/engine262/src/evaluator.mjs new file mode 100644 index 0000000..da77a62 --- /dev/null +++ b/engine262/src/evaluator.mjs @@ -0,0 +1,247 @@ +import { surroundingAgent } from './engine.mjs'; +import { OutOfRange } from './helpers.mjs'; +import { + Evaluate_Script, + Evaluate_ScriptBody, + Evaluate_Module, + Evaluate_ModuleBody, + Evaluate_ImportDeclaration, + Evaluate_ExportDeclaration, + Evaluate_ClassDeclaration, + Evaluate_LexicalDeclaration, + Evaluate_FunctionDeclaration, + Evaluate_HoistableDeclaration, + Evaluate_Block, + Evaluate_VariableStatement, + Evaluate_ExpressionStatement, + Evaluate_EmptyStatement, + Evaluate_IfStatement, + Evaluate_ReturnStatement, + Evaluate_TryStatement, + Evaluate_ThrowStatement, + Evaluate_DebuggerStatement, + Evaluate_BreakableStatement, + Evaluate_LabelledStatement, + Evaluate_ForBinding, + Evaluate_CaseClause, + Evaluate_BreakStatement, + Evaluate_ContinueStatement, + Evaluate_WithStatement, + Evaluate_IdentifierReference, + Evaluate_CommaOperator, + Evaluate_This, + Evaluate_Literal, + Evaluate_ArrayLiteral, + Evaluate_ObjectLiteral, + Evaluate_TemplateLiteral, + Evaluate_ClassExpression, + Evaluate_FunctionExpression, + Evaluate_GeneratorExpression, + Evaluate_AsyncFunctionExpression, + Evaluate_AsyncGeneratorExpression, + Evaluate_AdditiveExpression, + Evaluate_MultiplicativeExpression, + Evaluate_ExponentiationExpression, + Evaluate_UpdateExpression, + Evaluate_ShiftExpression, + Evaluate_LogicalORExpression, + Evaluate_LogicalANDExpression, + Evaluate_BinaryBitwiseExpression, + Evaluate_RelationalExpression, + Evaluate_CoalesceExpression, + Evaluate_EqualityExpression, + Evaluate_CallExpression, + Evaluate_NewExpression, + Evaluate_MemberExpression, + Evaluate_OptionalExpression, + Evaluate_TaggedTemplateExpression, + Evaluate_SuperCall, + Evaluate_SuperProperty, + Evaluate_NewTarget, + Evaluate_ImportMeta, + Evaluate_ImportCall, + Evaluate_AwaitExpression, + Evaluate_YieldExpression, + Evaluate_ParenthesizedExpression, + Evaluate_AssignmentExpression, + Evaluate_UnaryExpression, + Evaluate_ArrowFunction, + Evaluate_AsyncArrowFunction, + Evaluate_ConditionalExpression, + Evaluate_RegularExpressionLiteral, + Evaluate_AnyFunctionBody, + Evaluate_ExpressionBody, +} from './runtime-semantics/all.mjs'; + +export function* Evaluate(node) { + surroundingAgent.runningExecutionContext.callSite.setLocation(node); + + if (surroundingAgent.hostDefinedOptions.onNodeEvaluation) { + surroundingAgent.hostDefinedOptions.onNodeEvaluation(node, surroundingAgent.currentRealmRecord); + } + + switch (node.type) { + // Language + case 'Script': + return yield* Evaluate_Script(node); + case 'ScriptBody': + return yield* Evaluate_ScriptBody(node); + case 'Module': + return yield* Evaluate_Module(node); + case 'ModuleBody': + return yield* Evaluate_ModuleBody(node); + // Statements + case 'Block': + return yield* Evaluate_Block(node); + case 'VariableStatement': + return yield* Evaluate_VariableStatement(node); + case 'EmptyStatement': + return Evaluate_EmptyStatement(node); + case 'IfStatement': + return yield* Evaluate_IfStatement(node); + case 'ExpressionStatement': + return yield* Evaluate_ExpressionStatement(node); + case 'WhileStatement': + case 'DoWhileStatement': + case 'SwitchStatement': + case 'ForStatement': + case 'ForInStatement': + case 'ForOfStatement': + case 'ForAwaitStatement': + return yield* Evaluate_BreakableStatement(node); + case 'ForBinding': + return Evaluate_ForBinding(node); + case 'CaseClause': + case 'DefaultClause': + return yield* Evaluate_CaseClause(node); + case 'BreakStatement': + return Evaluate_BreakStatement(node); + case 'ContinueStatement': + return Evaluate_ContinueStatement(node); + case 'LabelledStatement': + return yield* Evaluate_LabelledStatement(node); + case 'ReturnStatement': + return yield* Evaluate_ReturnStatement(node); + case 'ThrowStatement': + return yield* Evaluate_ThrowStatement(node); + case 'TryStatement': + return yield* Evaluate_TryStatement(node); + case 'DebuggerStatement': + return Evaluate_DebuggerStatement(node); + case 'WithStatement': + return yield* Evaluate_WithStatement(node); + // Declarations + case 'ImportDeclaration': + return Evaluate_ImportDeclaration(node); + case 'ExportDeclaration': + return yield* Evaluate_ExportDeclaration(node); + case 'ClassDeclaration': + return yield* Evaluate_ClassDeclaration(node); + case 'LexicalDeclaration': + return yield* Evaluate_LexicalDeclaration(node); + case 'FunctionDeclaration': + return Evaluate_FunctionDeclaration(node); + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + return Evaluate_HoistableDeclaration(node); + // Expressions + case 'CommaOperator': + return yield* Evaluate_CommaOperator(node); + case 'ThisExpression': + return Evaluate_This(node); + case 'IdentifierReference': + return Evaluate_IdentifierReference(node); + case 'NullLiteral': + case 'BooleanLiteral': + case 'NumericLiteral': + case 'StringLiteral': + return Evaluate_Literal(node); + case 'ArrayLiteral': + return yield* Evaluate_ArrayLiteral(node); + case 'ObjectLiteral': + return yield* Evaluate_ObjectLiteral(node); + case 'FunctionExpression': + return yield* Evaluate_FunctionExpression(node); + case 'ClassExpression': + return yield* Evaluate_ClassExpression(node); + case 'GeneratorExpression': + return yield* Evaluate_GeneratorExpression(node); + case 'AsyncFunctionExpression': + return yield* Evaluate_AsyncFunctionExpression(node); + case 'AsyncGeneratorExpression': + return yield* Evaluate_AsyncGeneratorExpression(node); + case 'TemplateLiteral': + return yield* Evaluate_TemplateLiteral(node); + case 'ParenthesizedExpression': + return yield* Evaluate_ParenthesizedExpression(node); + case 'AdditiveExpression': + return yield* Evaluate_AdditiveExpression(node); + case 'MultiplicativeExpression': + return yield* Evaluate_MultiplicativeExpression(node); + case 'ExponentiationExpression': + return yield* Evaluate_ExponentiationExpression(node); + case 'UpdateExpression': + return yield* Evaluate_UpdateExpression(node); + case 'ShiftExpression': + return yield* Evaluate_ShiftExpression(node); + case 'LogicalORExpression': + return yield* Evaluate_LogicalORExpression(node); + case 'LogicalANDExpression': + return yield* Evaluate_LogicalANDExpression(node); + case 'BitwiseANDExpression': + case 'BitwiseXORExpression': + case 'BitwiseORExpression': + return yield* Evaluate_BinaryBitwiseExpression(node); + case 'RelationalExpression': + return yield* Evaluate_RelationalExpression(node); + case 'CoalesceExpression': + return yield* Evaluate_CoalesceExpression(node); + case 'EqualityExpression': + return yield* Evaluate_EqualityExpression(node); + case 'CallExpression': + return yield* Evaluate_CallExpression(node); + case 'NewExpression': + return yield* Evaluate_NewExpression(node); + case 'MemberExpression': + return yield* Evaluate_MemberExpression(node); + case 'OptionalExpression': + return yield* Evaluate_OptionalExpression(node); + case 'TaggedTemplateExpression': + return yield* Evaluate_TaggedTemplateExpression(node); + case 'SuperProperty': + return yield* Evaluate_SuperProperty(node); + case 'SuperCall': + return yield* Evaluate_SuperCall(node); + case 'NewTarget': + return Evaluate_NewTarget(node); + case 'ImportMeta': + return Evaluate_ImportMeta(node); + case 'ImportCall': + return yield* Evaluate_ImportCall(node); + case 'AssignmentExpression': + return yield* Evaluate_AssignmentExpression(node); + case 'YieldExpression': + return yield* Evaluate_YieldExpression(node); + case 'AwaitExpression': + return yield* Evaluate_AwaitExpression(node); + case 'UnaryExpression': + return yield* Evaluate_UnaryExpression(node); + case 'ArrowFunction': + return yield* Evaluate_ArrowFunction(node); + case 'AsyncArrowFunction': + return yield* Evaluate_AsyncArrowFunction(node); + case 'ConditionalExpression': + return yield* Evaluate_ConditionalExpression(node); + case 'RegularExpressionLiteral': + return Evaluate_RegularExpressionLiteral(node); + case 'AsyncFunctionBody': + case 'GeneratorBody': + case 'AsyncGeneratorBody': + return yield* Evaluate_AnyFunctionBody(node); + case 'ExpressionBody': + return yield* Evaluate_ExpressionBody(node); + default: + throw new OutOfRange('Evaluate', node); + } +} diff --git a/engine262/src/helpers.mjs b/engine262/src/helpers.mjs new file mode 100644 index 0000000..8e44a5c --- /dev/null +++ b/engine262/src/helpers.mjs @@ -0,0 +1,349 @@ +import { surroundingAgent } from './engine.mjs'; +import { Type, Value, Descriptor } from './value.mjs'; +import { ToString, DefinePropertyOrThrow, CreateBuiltinFunction } from './abstract-ops/all.mjs'; +import { X, AwaitFulfilledFunctions } from './completion.mjs'; + +function convertValueForKey(key) { + if (typeof key === 'string') { + return Symbol.for(`engine262_helper_key_${key}`); + } + switch (Type(key)) { + case 'String': + return key.stringValue(); + case 'Number': + if (key.numberValue() === 0 && Object.is(key.numberValue(), -0)) { + return key; + } + return key.numberValue(); + default: + return key; + } +} + +export class ValueMap { + constructor() { + this.map = new Map(); + } + + get size() { + return this.map.size; + } + + get(key) { + return this.map.get(convertValueForKey(key)); + } + + set(key, value) { + this.map.set(convertValueForKey(key), value); + return this; + } + + has(key) { + return this.map.has(convertValueForKey(key)); + } + + delete(key) { + return this.map.delete(convertValueForKey(key)); + } + + * keys() { + for (const [key] of this.entries()) { + yield key; + } + } + + entries() { + return this[Symbol.iterator](); + } + + forEach(cb) { + for (const [key, value] of this.entries()) { + cb(value, key, this); + } + } + + * [Symbol.iterator]() { + for (const [key, value] of this.map.entries()) { + if (typeof key === 'string' || typeof key === 'number') { + yield [new Value(key), value]; + } else { + yield [key, value]; + } + } + } + + mark(m) { + for (const [k, v] of this.entries()) { + m(k); + m(v); + } + } +} + +export class ValueSet { + constructor(init) { + this.set = new Set(); + if (init !== undefined && init !== null) { + for (const item of init) { + this.add(item); + } + } + } + + get size() { + return this.set.size; + } + + add(item) { + this.set.add(convertValueForKey(item)); + return this; + } + + has(item) { + return this.set.has(convertValueForKey(item)); + } + + delete(item) { + return this.set.delete(convertValueForKey(item)); + } + + values() { + return this[Symbol.iterator](); + } + + * [Symbol.iterator]() { + for (const key of this.set.values()) { + if (typeof key === 'string' || typeof key === 'number') { + yield new Value(key); + } else { + yield key; + } + } + } + + mark(m) { + for (const v of this.values()) { + m(v); + } + } +} + +export class OutOfRange extends RangeError { + /* istanbul ignore next */ + constructor(fn, detail) { + super(`${fn}() argument out of range`); + this.detail = detail; + } +} + +export function unwind(iterator, maxSteps = 1) { + let steps = 0; + while (true) { + const { done, value } = iterator.next('Unwind'); + if (done) { + return value; + } + /* istanbul ignore next */ + steps += 1; + if (steps > maxSteps) { + throw new RangeError('Max steps exceeded'); + } + } +} + +const kSafeToResume = Symbol('kSameToResume'); + +export function handleInResume(fn, ...args) { + const bound = () => fn(...args); + bound[kSafeToResume] = true; + return bound; +} + +export function resume(context, completion) { + const { value } = context.codeEvaluationState.next(completion); + if (typeof value === 'function' && value[kSafeToResume] === true) { + return X(value()); + } + return value; +} + +export class CallSite { + constructor(context) { + this.context = context; + this.lastNode = null; + this.constructCall = false; + } + + clone(context = this.context) { + const c = new CallSite(context); + c.lastNode = this.lastNode; + c.constructCall = this.constructCall; + return c; + } + + isTopLevel() { + return this.context.Function === Value.null; + } + + isConstructCall() { + return this.constructCall; + } + + isAsync() { + if (this.context.Function !== Value.null && this.context.Function.ECMAScriptCode) { + const code = this.context.Function.ECMAScriptCode; + return code.type === 'AsyncFunctionBody' || code.type === 'AsyncGeneratorBody'; + } + return false; + } + + isNative() { + return !!this.context.Function.nativeFunction; + } + + getFunctionName() { + if (this.context.Function !== Value.null) { + const name = this.context.Function.properties.get(new Value('name')); + if (name) { + return X(ToString(name.Value)).stringValue(); + } + } + return null; + } + + getSpecifier() { + if (this.context.ScriptOrModule !== Value.null) { + return this.context.ScriptOrModule.HostDefined.specifier; + } + return null; + } + + setLocation(node) { + this.lastNode = node; + } + + get lineNumber() { + if (this.lastNode) { + return this.lastNode.location.start.line; + } + return null; + } + + get columnNumber() { + if (this.lastNode) { + return this.lastNode.location.start.column; + } + return null; + } + + loc() { + if (this.isNative()) { + return 'native'; + } + let out = ''; + const specifier = this.getSpecifier(); + if (specifier) { + out += specifier; + } else { + out += ''; + } + if (this.lineNumber !== null) { + out += `:${this.lineNumber}`; + if (this.columnNumber !== null) { + out += `:${this.columnNumber}`; + } + } + return out.trim(); + } + + toString() { + const isAsync = this.isAsync(); + const functionName = this.getFunctionName(); + const isConstructCall = this.isConstructCall(); + const isMethodCall = !isConstructCall && !this.isTopLevel(); + + let string = isAsync ? 'async ' : ''; + + if (isConstructCall) { + string += 'new '; + } + + if (isMethodCall || isConstructCall) { + if (functionName) { + string += functionName; + } else { + string += ''; + } + } else if (functionName) { + string += functionName; + } else { + return `${string}${this.loc()}`; + } + + return `${string} (${this.loc()})`; + } +} + +function captureAsyncStack(stack) { + let promise = stack[0].context.promiseCapability.Promise; + for (let i = 0; i < 10; i += 1) { + if (promise.PromiseFulfillReactions.length !== 1) { + return; + } + const [reaction] = promise.PromiseFulfillReactions; + if (reaction.Handler && reaction.Handler.Callback.nativeFunction === AwaitFulfilledFunctions) { + const asyncContext = reaction.Handler.Callback.AsyncContext; + stack.push(asyncContext.callSite.clone()); + if ('PromiseState' in asyncContext.promiseCapability.Promise) { + promise = asyncContext.promiseCapability.Promise; + } else { + return; + } + } else if (reaction.Capability !== Value.undefined) { + if ('PromiseState' in reaction.Capability.Promise) { + promise = reaction.Capability.Promise; + } else { + return; + } + } + } +} + +export function captureStack(O) { + const stack = []; + for (let i = surroundingAgent.executionContextStack.length - 2; i >= 0; i -= 1) { + const e = surroundingAgent.executionContextStack[i]; + if (e.VariableEnvironment === undefined && e.Function === Value.null) { + break; + } + stack.push(e.callSite.clone()); + if (e.callSite.isAsync()) { + i -= 1; // skip original execution context which has no useful information. + } + } + + if (stack.length > 0 && stack[0].context.promiseCapability) { + captureAsyncStack(stack); + } + + let cache = null; + + X(DefinePropertyOrThrow(O, new Value('stack'), Descriptor({ + Get: CreateBuiltinFunction(() => { + if (cache === null) { + let errorString = X(ToString(O)).stringValue(); + stack.forEach((s) => { + errorString = `${errorString}\n at ${s.toString()}`; + }); + cache = new Value(errorString); + } + return cache; + }, []), + Set: CreateBuiltinFunction(([value = Value.undefined]) => { + cache = value; + return Value.undefined; + }, []), + Enumerable: Value.false, + Configurable: Value.true, + }))); +} diff --git a/engine262/src/inspect.mjs b/engine262/src/inspect.mjs new file mode 100644 index 0000000..9199d92 --- /dev/null +++ b/engine262/src/inspect.mjs @@ -0,0 +1,205 @@ +import { surroundingAgent } from './engine.mjs'; +import { Type, Value, wellKnownSymbols } from './value.mjs'; +import { + Call, IsArray, Get, LengthOfArrayLike, + EscapeRegExpPattern, +} from './abstract-ops/all.mjs'; +import { Q, X } from './completion.mjs'; + +const bareKeyRe = /^[a-zA-Z_][a-zA-Z_0-9]*$/; + +const getObjectTag = (value, wrap) => { + let s; + try { + s = X(Get(value, wellKnownSymbols.toStringTag)).stringValue(); + } catch {} + try { + const c = X(Get(value, new Value('constructor'))); + s = X(Get(c, new Value('name'))).stringValue(); + } catch {} + if (s) { + if (wrap) { + return `[${s}] `; + } + return s; + } + return ''; +}; + +const compactObject = (realm, value) => { + try { + const toString = X(Get(value, new Value('toString'))); + const objectToString = realm.Intrinsics['%Object.prototype.toString%']; + if (toString.nativeFunction === objectToString.nativeFunction) { + return X(Call(toString, value)).stringValue(); + } else { + const tag = getObjectTag(value, false) || 'Unknown'; + const ctor = X(Get(value, new Value('constructor'))); + if (Type(ctor) === 'Object') { + const ctorName = X(Get(ctor, new Value('name'))).stringValue(); + if (ctorName !== '') { + return `#<${ctorName}>`; + } + return `[object ${tag}]`; + } + return `[object ${tag}]`; + } + } catch (e) { + return '[object Unknown]'; + } +}; + +const INSPECTORS = { + Completion: (v, ctx, i) => i(v.Value), + Null: () => 'null', + Undefined: () => 'undefined', + Boolean: (v) => v.boolean.toString(), + Number: (v) => { + const n = v.numberValue(); + if (n === 0 && Object.is(n, -0)) { + return '-0'; + } + return n.toString(); + }, + BigInt: (v) => `${v.bigintValue()}n`, + String: (v) => { + const s = JSON.stringify(v.stringValue()).slice(1, -1); + return `'${s}'`; + }, + Symbol: (v) => `Symbol(${v.Description === Value.undefined ? '' : v.Description.stringValue()})`, + Object: (v, ctx, i) => { + if (ctx.inspected.includes(v)) { + return '[Circular]'; + } + if ('PromiseState' in v) { + ctx.indent += 1; + const result = v.PromiseState === 'pending' ? 'undefined' : i(v.PromiseResult); + ctx.indent -= 1; + return `Promise { + [[PromiseState]]: ${v.PromiseState} + [[PromiseResult]]: ${result} +}`; + } + + if ('Call' in v) { + const name = v.properties.get(new Value('name')); + if (name !== undefined && name.Value.stringValue() !== '') { + return `[Function: ${name.Value.stringValue()}]`; + } + return '[Function]'; + } + + if ('ErrorData' in v) { + let e = Q(Get(v, new Value('stack'))); + if (!e.stringValue) { + const toString = Q(Get(v, new Value('toString'))); + e = X(Call(toString, v)); + } + return e.stringValue(); + } + + if ('RegExpMatcher' in v) { + const P = EscapeRegExpPattern(v.OriginalSource, v.OriginalFlags).stringValue(); + const F = v.OriginalFlags.stringValue(); + return `/${P}/${F}`; + } + + if ('DateValue' in v) { + const d = new Date(v.DateValue.numberValue()); + if (Number.isNaN(d.getTime())) { + return '[Date Invalid]'; + } + return `[Date ${d.toISOString()}]`; + } + + if ('BooleanData' in v) { + return `[Boolean ${i(v.BooleanData)}]`; + } + if ('NumberData' in v) { + return `[Number ${i(v.NumberData)}]`; + } + if ('BigIntData' in v) { + return `[BigInt ${i(v.BigIntData)}]`; + } + if ('StringData' in v) { + return `[String ${i(v.StringData)}]`; + } + if ('SymbolData' in v) { + return `[Symbol ${i(v.SymbolData)}]`; + } + + ctx.indent += 1; + ctx.inspected.push(v); + + try { + const isArray = IsArray(v) === Value.true; + const isTypedArray = 'TypedArrayName' in v; + if (isArray || isTypedArray) { + const length = X(LengthOfArrayLike(v)).numberValue(); + let holes = 0; + const out = []; + for (let j = 0; j < length; j += 1) { + const elem = X(v.GetOwnProperty(new Value(j.toString()))); + if (elem === Value.undefined) { + holes += 1; + } else { + if (holes > 0) { + out.push(`<${holes} empty items>`); + holes = 0; + } + if (elem.Value) { + out.push(i(elem.Value)); + } else { + out.push(''); + } + } + } + return `${isTypedArray ? `${v.TypedArrayName.stringValue()} ` : ''}[${out.join(', ')}]`; + } + + const keys = X(v.OwnPropertyKeys()); + const cache = []; + for (const key of keys) { + const C = X(v.GetOwnProperty(key)); + if (C.Enumerable === Value.true) { + cache.push([ + Type(key) === 'String' && bareKeyRe.test(key.stringValue()) ? key.stringValue() : i(key), + C.Value ? i(C.Value) : '', + ]); + } + } + + const tag = getObjectTag(v); + let out = tag && tag !== 'Object' ? `${tag} {` : '{'; + if (cache.length > 5) { + cache.forEach((c) => { + out = `${out}\n${' '.repeat(ctx.indent)}${c[0]}: ${c[1]},`; + }); + return `${out}\n${' '.repeat(ctx.indent - 1)}}`; + } else { + const oc = ctx.compact; + ctx.compact = true; + cache.forEach((c, index) => { + out = `${out}${index === 0 ? '' : ','} ${c[0]}: ${c[1]}`; + }); + ctx.compact = oc; + return `${out} }`; + } + } catch { + return compactObject(ctx, v); + } finally { + ctx.indent -= 1; + ctx.inspected.pop(); + } + }, +}; + +export function inspect(value) { + const context = { + realm: surroundingAgent.currentRealmRecord, + indent: 0, + inspected: [], + }; + const inner = (v) => INSPECTORS[Type(v)](v, context, inner); + return inner(value); +} diff --git a/engine262/src/intrinsics/AggregateError.mjs b/engine262/src/intrinsics/AggregateError.mjs new file mode 100644 index 0000000..8e48f8d --- /dev/null +++ b/engine262/src/intrinsics/AggregateError.mjs @@ -0,0 +1,56 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Descriptor } from '../value.mjs'; +import { + CreateMethodProperty, + ToString, + IterableToList, + OrdinaryCreateFromConstructor, + DefinePropertyOrThrow, + CreateArrayFromList, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { captureStack } from '../helpers.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-aggregate-error-constructor +function AggregateErrorConstructor([errors = Value.undefined, message = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. + let newTarget; + if (NewTarget === Value.undefined) { + newTarget = surroundingAgent.activeFunctionObject; + } else { + newTarget = NewTarget; + } + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »). + const O = Q(OrdinaryCreateFromConstructor(newTarget, '%AggregateError.prototype%', [ + 'ErrorData', + ])); + // 3. If message is not undefined, then + if (message !== Value.undefined) { + // a. Let msg be ? ToString(message). + const msg = Q(ToString(message)); + // b. Perform ! CreateMethodProperty(O, "message", msg). + X(CreateMethodProperty(O, new Value('message'), msg)); + } + // 4. Let errorsList be ? IterableToList(errors). + const errorsList = Q(IterableToList(errors)); + // 5. Perform ! DefinePropertyOrThrow(O, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: ! CreateArrayFromList(errorsList) }). + X(DefinePropertyOrThrow(O, new Value('errors'), Descriptor({ + Configurable: Value.true, + Enumerable: Value.false, + Writable: Value.true, + Value: X(CreateArrayFromList(errorsList)), + }))); + + // NON-SPEC + X(captureStack(O)); + + // 6. Return O. + return O; +} + +export function BootstrapAggregateError(realmRec) { + const c = BootstrapConstructor(realmRec, AggregateErrorConstructor, 'AggregateError', 2, realmRec.Intrinsics['%AggregateError.prototype%'], []); + c.Prototype = realmRec.Intrinsics['%Error%']; + realmRec.Intrinsics['%AggregateError%'] = c; +} diff --git a/engine262/src/intrinsics/AggregateErrorPrototype.mjs b/engine262/src/intrinsics/AggregateErrorPrototype.mjs new file mode 100644 index 0000000..d2e56d1 --- /dev/null +++ b/engine262/src/intrinsics/AggregateErrorPrototype.mjs @@ -0,0 +1,11 @@ +import { Value } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +export function BootstrapAggregateErrorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['name', new Value('AggregateError')], + ['message', new Value('')], + ], realmRec.Intrinsics['%Error.prototype%'], 'AggregateError'); + + realmRec.Intrinsics['%AggregateError.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Array.mjs b/engine262/src/intrinsics/Array.mjs new file mode 100644 index 0000000..5280d2f --- /dev/null +++ b/engine262/src/intrinsics/Array.mjs @@ -0,0 +1,215 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + AbruptCompletion, + Q, + ThrowCompletion, X, +} from '../completion.mjs'; +import { + ArrayCreate, + Assert, + Call, + Construct, + CreateDataProperty, + CreateDataPropertyOrThrow, + Get, + GetIterator, + GetMethod, + GetPrototypeFromConstructor, + IsArray, + IsCallable, + IsConstructor, + IteratorClose, + IteratorStep, + IteratorValue, + Set, + LengthOfArrayLike, + ToObject, + ToString, + ToUint32, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// 22.1.1 #sec-array-constructor +function ArrayConstructor(argumentsList, { NewTarget }) { + const numberOfArgs = argumentsList.length; + if (numberOfArgs === 0) { + // 22.1.1.1 #sec-array-constructor-array + Assert(numberOfArgs === 0); + if (Type(NewTarget) === 'Undefined') { + NewTarget = surroundingAgent.activeFunctionObject; + } + const proto = GetPrototypeFromConstructor(NewTarget, '%Array.prototype%'); + return ArrayCreate(new Value(0), proto); + } else if (numberOfArgs === 1) { + // 22.1.1.2 #sec-array-len + const [len] = argumentsList; + Assert(numberOfArgs === 1); + if (Type(NewTarget) === 'Undefined') { + NewTarget = surroundingAgent.activeFunctionObject; + } + const proto = GetPrototypeFromConstructor(NewTarget, '%Array.prototype%'); + const array = ArrayCreate(new Value(0), proto); + let intLen; + if (Type(len) !== 'Number') { + const defineStatus = X(CreateDataProperty(array, new Value('0'), len)); + Assert(defineStatus === Value.true); + intLen = new Value(1); + } else { + intLen = ToUint32(len); + if (intLen.numberValue() !== len.numberValue()) { + return surroundingAgent.Throw('RangeError', 'InvalidArrayLength', len); + } + } + Set(array, new Value('length'), intLen, Value.true); + return array; + } else if (numberOfArgs >= 2) { + // 22.1.1.3 #sec-array-items + const items = argumentsList; + Assert(numberOfArgs >= 2); + if (Type(NewTarget) === 'Undefined') { + NewTarget = surroundingAgent.activeFunctionObject; + } + const proto = GetPrototypeFromConstructor(NewTarget, '%Array.prototype%'); + const array = ArrayCreate(new Value(0), proto); + let k = 0; + while (k < numberOfArgs) { + const Pk = ToString(new Value(k)); + const itemK = items[k]; + const defineStatus = X(CreateDataProperty(array, Pk, itemK)); + Assert(defineStatus === Value.true); + k += 1; + } + Assert(X(Get(array, new Value('length'))).numberValue() === numberOfArgs); + return array; + } + + throw new OutOfRange('ArrayConstructor', numberOfArgs); +} + +// 22.1.2.1 #sec-array.from +function Array_from([items = Value.undefined, mapfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + const C = thisValue; + let mapping; + let A; + if (mapfn === Value.undefined) { + mapping = false; + } else { + if (IsCallable(mapfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', mapfn); + } + mapping = true; + } + const usingIterator = Q(GetMethod(items, wellKnownSymbols.iterator)); + if (usingIterator !== Value.undefined) { + if (IsConstructor(C) === Value.true) { + A = Q(Construct(C)); + } else { + A = X(ArrayCreate(new Value(0))); + } + const iteratorRecord = Q(GetIterator(items, 'sync', usingIterator)); + let k = 0; + while (true) { // eslint-disable-line no-constant-condition + if (k >= (2 ** 53) - 1) { + const error = ThrowCompletion(surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength').Value); + return Q(IteratorClose(iteratorRecord, error)); + } + const Pk = X(ToString(new Value(k))); + const next = Q(IteratorStep(iteratorRecord)); + if (next === Value.false) { + Q(Set(A, new Value('length'), new Value(k), Value.true)); + return A; + } + const nextValue = Q(IteratorValue(next)); + let mappedValue; + if (mapping) { + mappedValue = Call(mapfn, thisArg, [nextValue, new Value(k)]); + if (mappedValue instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, mappedValue)); + } + mappedValue = mappedValue.Value; + } else { + mappedValue = nextValue; + } + const defineStatus = CreateDataPropertyOrThrow(A, Pk, mappedValue); + if (defineStatus instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, defineStatus)); + } + k += 1; + } + } + const arrayLike = X(ToObject(items)); + const len = Q(LengthOfArrayLike(arrayLike)); + if (IsConstructor(C) === Value.true) { + A = Q(Construct(C, [len])); + } else { + A = Q(ArrayCreate(len)); + } + let k = 0; + while (k < len.numberValue()) { + const Pk = X(ToString(new Value(k))); + const kValue = Q(Get(arrayLike, Pk)); + let mappedValue; + if (mapping === true) { + mappedValue = Q(Call(mapfn, thisArg, [kValue, new Value(k)])); + } else { + mappedValue = kValue; + } + Q(CreateDataPropertyOrThrow(A, Pk, mappedValue)); + k += 1; + } + Q(Set(A, new Value('length'), len, Value.true)); + return A; +} + +// 22.1.2.2 #sec-array.isarray +function Array_isArray([arg = Value.undefined]) { + return Q(IsArray(arg)); +} + +// 22.1.2.3 #sec-array.of +function Array_of(items, { thisValue }) { + const len = items.length; + // Let items be the List of arguments passed to this function. + const C = thisValue; + let A; + if (IsConstructor(C) === Value.true) { + A = Q(Construct(C, [new Value(len)])); + } else { + A = Q(ArrayCreate(new Value(len))); + } + let k = 0; + while (k < len) { + const kValue = items[k]; + const Pk = X(ToString(new Value(k))); + Q(CreateDataPropertyOrThrow(A, Pk, kValue)); + k += 1; + } + Q(Set(A, new Value('length'), new Value(len), Value.true)); + return A; +} + +// 22.1.2.5 #sec-get-array-@@species +function Array_speciesGetter(args, { thisValue }) { + return thisValue; +} + +export function BootstrapArray(realmRec) { + const proto = realmRec.Intrinsics['%Array.prototype%']; + + const cons = BootstrapConstructor(realmRec, ArrayConstructor, 'Array', 1, proto, [ + ['from', Array_from, 1], + ['isArray', Array_isArray, 1], + ['of', Array_of, 0], + [wellKnownSymbols.species, [Array_speciesGetter]], + ]); + + realmRec.Intrinsics['%Array%'] = cons; +} diff --git a/engine262/src/intrinsics/ArrayBuffer.mjs b/engine262/src/intrinsics/ArrayBuffer.mjs new file mode 100644 index 0000000..28178ad --- /dev/null +++ b/engine262/src/intrinsics/ArrayBuffer.mjs @@ -0,0 +1,44 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value, wellKnownSymbols } from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { ToIndex, AllocateArrayBuffer } from '../abstract-ops/all.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-arraybuffer-length +function ArrayBufferConstructor([length = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let byteLength be ? ToIndex(length). + const byteLength = Q(ToIndex(length)); + // 3. Return ? AllocateArrayBuffer(NewTarget, byteLength). + return Q(AllocateArrayBuffer(NewTarget, byteLength)); +} + +// #sec-arraybuffer.isview +function ArrayBuffer_isView([arg = Value.undefined]) { + // 1. If Type(arg) is not Object, return false. + if (Type(arg) !== 'Object') { + return Value.false; + } + // 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true. + if ('ViewedArrayBuffer' in arg) { + return Value.true; + } + // 3. Return false. + return Value.false; +} + +// #sec-get-arraybuffer-@@species +function ArrayBuffer_species(a, { thisValue }) { + return thisValue; +} + +export function BootstrapArrayBuffer(realmRec) { + const c = BootstrapConstructor(realmRec, ArrayBufferConstructor, 'ArrayBuffer', 1, realmRec.Intrinsics['%ArrayBuffer.prototype%'], [ + ['isView', ArrayBuffer_isView, 1], + [wellKnownSymbols.species, [ArrayBuffer_species]], + ]); + realmRec.Intrinsics['%ArrayBuffer%'] = c; +} diff --git a/engine262/src/intrinsics/ArrayBufferPrototype.mjs b/engine262/src/intrinsics/ArrayBufferPrototype.mjs new file mode 100644 index 0000000..c4cf0f4 --- /dev/null +++ b/engine262/src/intrinsics/ArrayBufferPrototype.mjs @@ -0,0 +1,115 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { + RequireInternalSlot, IsDetachedBuffer, IsSharedArrayBuffer, + SpeciesConstructor, Construct, ToInteger, SameValue, CopyDataBlockBytes, +} from '../abstract-ops/all.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-get-arraybuffer.prototype.bytelength +function ArrayBufferProto_byteLength(args, { thisValue }) { + // 1. Let O be this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + Q(RequireInternalSlot(O, 'ArrayBufferData')); + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + if (IsSharedArrayBuffer(O) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferShared'); + } + // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if (IsDetachedBuffer(O) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Let length be O.[[ArrayBufferByteLength]]. + const length = O.ArrayBufferByteLength; + // 6. Return length. + return length; +} + +// #sec-arraybuffer.prototype.slice +function ArrayBufferProto_slice([start = Value.undefined, end = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + Q(RequireInternalSlot(O, 'ArrayBufferData')); + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + if (IsSharedArrayBuffer(O) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferShared'); + } + // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if (IsDetachedBuffer(O) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Let len be O.[[ArrayBufferByteLength]]. + const len = O.ArrayBufferByteLength.numberValue(); + // 6. Let relativeStart be ? ToInteger(start). + const relativeStart = Q(ToInteger(start)).numberValue(); + let first; + // 7. If relativeStart < 0, let first be max((len + relativeStart), 0); else let first be min(relativeStart, len). + if (relativeStart < 0) { + first = Math.max(len + relativeStart, 0); + } else { + first = Math.min(relativeStart, len); + } + let relativeEnd; + // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToInteger(end). + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + let final; + // 9. If relativeEnd < 0, let final be max((len + relativeEnd), 0); else let final be min(relativeEnd, len). + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + // 10. Let newLen be max(final - first, 0). + const newLen = Math.max(final - first, 0); + // 11. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). + const ctor = Q(SpeciesConstructor(O, surroundingAgent.intrinsic('%ArrayBuffer%'))); + // 12. Let new be ? Construct(ctor, « newLen »). + const newO = Q(Construct(ctor, [new Value(newLen)])); + // 13. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). + Q(RequireInternalSlot(newO, 'ArrayBufferData')); + // 14. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + if (IsSharedArrayBuffer(newO) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferShared'); + } + // 15. If IsDetachedBuffer(new) is true, throw a TypeError exception. + if (IsDetachedBuffer(newO) === Value.true) { + return surroundingAgent.Throe('TypeError', 'ArrayBufferDetached'); + } + // 16. If SameValue(new, O) is true, throw a TypeError exception. + if (SameValue(newO, O) === Value.true) { + return surroundingAgent.Throw('TypeError', 'SubclassSameValue', newO); + } + // 17. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. + if (newO.ArrayBufferByteLength.numberValue() < newLen) { + return surroundingAgent.Throw('TypeError', 'SubclassLengthTooSmall', newO); + } + // 18. NOTE: Side-effects of the above steps may have detached O. + // 19. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if (IsDetachedBuffer(O) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 20. Let fromBuf be O.[[ArrayBufferData]]. + const fromBuf = O.ArrayBufferData; + // 21. Let toBuf be new.[[ArrayBufferData]]. + const toBuf = newO.ArrayBufferData; + // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). + CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen); + // 23. Return new. + return newO; +} + +export function BootstrapArrayBufferPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['byteLength', [ArrayBufferProto_byteLength]], + ['slice', ArrayBufferProto_slice, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'ArrayBuffer'); + + realmRec.Intrinsics['%ArrayBuffer.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/ArrayIteratorPrototype.mjs b/engine262/src/intrinsics/ArrayIteratorPrototype.mjs new file mode 100644 index 0000000..745bc50 --- /dev/null +++ b/engine262/src/intrinsics/ArrayIteratorPrototype.mjs @@ -0,0 +1,95 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Type, + Value, +} from '../value.mjs'; +import { + Assert, + CreateArrayFromList, + CreateIterResultObject, + Get, + IsDetachedBuffer, + LengthOfArrayLike, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-%arrayiteratorprototype%-object +function ArrayIteratorPrototype_next(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Array Iterator', O); + } + // 3. If O does not have all of the internal slots of an Array Iterator Instance (22.1.5.3), throw a TypeError exception. + if (!('IteratedArrayLike' in O) + || !('ArrayLikeNextIndex' in O) + || !('ArrayLikeIterationKind' in O)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Array Iterator', O); + } + // 4. Let a be O.[[IteratedArrayLike]]. + const a = O.IteratedArrayLike; + // 5. If a is undefined, return CreateIterResultObject(undefined, true). + if (a === Value.undefined) { + return CreateIterResultObject(Value.undefined, Value.true); + } + // 6. Let index be O.[[ArrayLikeNextIndex]]. + const index = O.ArrayLikeNextIndex; + // 7. Let itemKind be O.[[ArrayLikeIterationKind]]. + const itemKind = O.ArrayLikeIterationKind; + let len; + // 8. If a has a [[TypedArrayName]] internal slot, then + if ('TypedArrayName' in a) { + // a. If IsDetachedBuffer(a.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. + if (IsDetachedBuffer(a.ViewedArrayBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // b. Let len be a.[[ArrayLength]]. + len = a.ArrayLength; + } else { // 9. Else, + // a. Let len be ? LengthOfArrayLike(a). + len = Q(LengthOfArrayLike(a)); + } + // 10. If index ≥ len, then + if (index >= len.numberValue()) { + // a. Set O.[[IteratedArrayLike]] to undefined. + O.IteratedArrayLike = Value.undefined; + // b. Return CreateIterResultObject(undefined, true). + return CreateIterResultObject(Value.undefined, Value.true); + } + // 11. Set O.[[ArrayLikeNextIndex]] to index + 1. + O.ArrayLikeNextIndex = index + 1; + // 12. If itemKind is key, return CreateIterResultObject(index, false). + if (itemKind === 'key') { + return CreateIterResultObject(new Value(index), Value.false); + } + // 13. Let elementKey be ! ToString(index). + const elementKey = X(ToString(new Value(index))); + // 14. Let elementValue be ? Get(a, elementKey). + const elementValue = Q(Get(a, elementKey)); + // 15. If itemKind is value, let result be elementValue. + let result; + // 15. If itemKind is value, let result be elementValue. + if (itemKind === 'value') { + result = elementValue; + } else { // 16. Else, + // a. Assert: itemKind is key+value. + Assert(itemKind === 'key+value'); + // b. Let result be ! CreateArrayFromList(« index, elementValue »). + result = X(CreateArrayFromList([new Value(index), elementValue])); + } + // 17. Return CreateIterResultObject(result, false). + return CreateIterResultObject(result, Value.false); +} + +export function BootstrapArrayIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', ArrayIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%IteratorPrototype%'], 'Array Iterator'); + + realmRec.Intrinsics['%ArrayIterator.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/ArrayPrototype.mjs b/engine262/src/intrinsics/ArrayPrototype.mjs new file mode 100644 index 0000000..1ec7ded --- /dev/null +++ b/engine262/src/intrinsics/ArrayPrototype.mjs @@ -0,0 +1,591 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Descriptor, + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + ArrayCreate, + ArraySpeciesCreate, + Assert, + Call, + CreateArrayIterator, + CreateDataProperty, + CreateDataPropertyOrThrow, + DeletePropertyOrThrow, + Get, + HasProperty, + IsArray, + IsCallable, + IsConcatSpreadable, + Set, + SortCompare, + LengthOfArrayLike, + OrdinaryObjectCreate, + ToBoolean, + ToInteger, + ToObject, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { assignProps } from './Bootstrap.mjs'; +import { ArrayProto_sortBody, BootstrapArrayPrototypeShared } from './ArrayPrototypeShared.mjs'; + +// 22.1.3.1 #sec-array.prototype.concat +function ArrayProto_concat(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + const A = Q(ArraySpeciesCreate(O, new Value(0))); + let n = 0; + const items = [O, ...args]; + while (items.length > 0) { + const E = items.shift(); + const spreadable = Q(IsConcatSpreadable(E)); + if (spreadable === Value.true) { + let k = 0; + const len = Q(LengthOfArrayLike(E)).numberValue(); + if (n + len > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + while (k < len) { + const P = X(ToString(new Value(k))); + const exists = Q(HasProperty(E, P)); + if (exists === Value.true) { + const subElement = Q(Get(E, P)); + const nStr = X(ToString(new Value(n))); + Q(CreateDataPropertyOrThrow(A, nStr, subElement)); + } + n += 1; + k += 1; + } + } else { + if (n >= (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + const nStr = X(ToString(new Value(n))); + Q(CreateDataPropertyOrThrow(A, nStr, E)); + n += 1; + } + } + Q(Set(A, new Value('length'), new Value(n), Value.true)); + return A; +} + +// 22.1.3.3 #sec-array.prototype.copywithin +function ArrayProto_copyWithin([target = Value.undefined, start = Value.undefined, end = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)); + const relativeTarget = Q(ToInteger(target)); + let to; + if (relativeTarget.numberValue() < 0) { + to = Math.max(len.numberValue() + relativeTarget.numberValue(), 0); + } else { + to = Math.min(relativeTarget.numberValue(), len.numberValue()); + } + const relativeStart = Q(ToInteger(start)); + let from; + if (relativeStart.numberValue() < 0) { + from = Math.max(len.numberValue() + relativeStart.numberValue(), 0); + } else { + from = Math.min(relativeStart.numberValue(), len.numberValue()); + } + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)); + } + let final; + if (relativeEnd.numberValue() < 0) { + final = Math.max(len.numberValue() + relativeEnd.numberValue(), 0); + } else { + final = Math.min(relativeEnd.numberValue(), len.numberValue()); + } + let count = Math.min(final - from, len.numberValue() - to); + let direction; + if (from < to && to < from + count) { + direction = -1; + from += count - 1; + to += count - 1; + } else { + direction = 1; + } + while (count > 0) { + const fromKey = X(ToString(new Value(from))); + const toKey = X(ToString(new Value(to))); + const fromPresent = Q(HasProperty(O, fromKey)); + if (fromPresent === Value.true) { + const fromVal = Q(Get(O, fromKey)); + Q(Set(O, toKey, fromVal, Value.true)); + } else { + Q(DeletePropertyOrThrow(O, toKey)); + } + from += direction; + to += direction; + count -= 1; + } + return O; +} + +// 22.1.3.4 #sec-array.prototype.entries +function ArrayProto_entries(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + return CreateArrayIterator(O, 'key+value'); +} + +// 22.1.3.6 #sec-array.prototype.fill +function ArrayProto_fill([value = Value.undefined, start = Value.undefined, end = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + const relativeStart = Q(ToInteger(start)).numberValue(); + let k; + if (relativeStart < 0) { + k = Math.max(len + relativeStart, 0); + } else { + k = Math.min(relativeStart, len); + } + let relativeEnd; + if (Type(end) === 'Undefined') { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + while (k < final) { + const Pk = X(ToString(new Value(k))); + Q(Set(O, Pk, value, Value.true)); + k += 1; + } + return O; +} + +// 22.1.3.7 #sec-array.prototype.filter +function ArrayProto_filter([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + const A = Q(ArraySpeciesCreate(O, new Value(0))); + let k = 0; + let to = 0; + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + const selected = ToBoolean(Q(Call(callbackfn, thisArg, [kValue, new Value(k), O]))); + if (selected === Value.true) { + Q(CreateDataPropertyOrThrow(A, ToString(new Value(to)), kValue)); + to += 1; + } + } + k += 1; + } + return A; +} + +// 22.1.3.10.1 #sec-flattenintoarray +function FlattenIntoArray(target, source, sourceLen, start, depth, mapperFunction, thisArg) { + Assert(Type(target) === 'Object'); + Assert(Type(source) === 'Object'); + Assert(sourceLen >= 0); + Assert(start >= 0); + // Assert: _depth_ is an integer Number, *+∞*, or *-∞*. + // Assert(mapperFunction === undefined || (X(IsCallable(mapperFunction)) === Value.true && thisArg !== undefined && depth === 1)); + let targetIndex = start; + let sourceIndex = 0; + while (sourceIndex < sourceLen) { + const P = X(ToString(new Value(sourceIndex))); + const exists = Q(HasProperty(source, P)); + if (exists === Value.true) { + let element = Q(Get(source, P)); + if (mapperFunction) { + Assert(thisArg); + element = Q(Call(mapperFunction, thisArg, [element, new Value(sourceIndex), source])); + } + let shouldFlatten = Value.false; + if (depth > 0) { + shouldFlatten = Q(IsArray(element)); + } + if (shouldFlatten === Value.true) { + const elementLen = Q(LengthOfArrayLike(element)).numberValue(); + targetIndex = Q(FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1)); + } else { + if (targetIndex >= (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'OutOfRange', targetIndex); + } + Q(CreateDataPropertyOrThrow(target, X(ToString(new Value(targetIndex))), element)); + targetIndex += 1; + } + } + sourceIndex += 1; + } + return targetIndex; +} + +// 22.1.3.10 #sec-array.prototype.flat +function ArrayProto_flat([depth = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const sourceLen = Q(LengthOfArrayLike(O)).numberValue(); + let depthNum = 1; + if (depth !== Value.undefined) { + depthNum = Q(ToInteger(depth)).numberValue(); + } + const A = Q(ArraySpeciesCreate(O, new Value(0))); + Q(FlattenIntoArray(A, O, sourceLen, 0, depthNum)); + return A; +} + +// 22.1.3.11 #sec-array.prototype.flatmap +function ArrayProto_flatMap([mapperFunction = Value.undefined, thisArg = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const sourceLen = Q(LengthOfArrayLike(O)).numberValue(); + if (X(IsCallable(mapperFunction)) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', mapperFunction); + } + const A = Q(ArraySpeciesCreate(O, new Value(0))); + Q(FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg)); + return A; +} + +// 22.1.3.16 #sec-array.prototype.keys +function ArrayProto_keys(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + return CreateArrayIterator(O, 'key'); +} + +// 22.1.3.18 #sec-array.prototype.map +function ArrayProto_map([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)); + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + const A = Q(ArraySpeciesCreate(O, len)); + let k = 0; + while (k < len.numberValue()) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + const mappedValue = Q(Call(callbackfn, thisArg, [kValue, new Value(k), O])); + Q(CreateDataPropertyOrThrow(A, Pk, mappedValue)); + } + k += 1; + } + return A; +} + +// 22.1.3.19 #sec-array.prototype.pop +function ArrayProto_pop(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + if (len === 0) { + Q(Set(O, new Value('length'), new Value(0), Value.true)); + return Value.undefined; + } else { + const newLen = len - 1; + const index = Q(ToString(new Value(newLen))); + const element = Q(Get(O, index)); + Q(DeletePropertyOrThrow(O, index)); + Q(Set(O, new Value('length'), new Value(newLen), Value.true)); + return element; + } +} + +// 22.1.3.20 #sec-array.prototype.push +function ArrayProto_push(items, { thisValue }) { + const O = Q(ToObject(thisValue)); + let len = Q(LengthOfArrayLike(O)).numberValue(); + const argCount = items.length; + if (len + argCount > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + while (items.length > 0) { + const E = items.shift(); + Q(Set(O, X(ToString(new Value(len))), E, Value.true)); + len += 1; + } + Q(Set(O, new Value('length'), new Value(len), Value.true)); + return new Value(len); +} + +// 22.1.3.24 #sec-array.prototype.shift +function ArrayProto_shift(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + if (len === 0) { + Q(Set(O, new Value('length'), new Value(0), Value.true)); + return Value.undefined; + } + const first = Q(Get(O, new Value('0'))); + let k = 1; + while (k < len) { + const from = X(ToString(new Value(k))); + const to = X(ToString(new Value(k - 1))); + const fromPresent = Q(HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromVal = Q(Get(O, from)); + Q(Set(O, to, fromVal, Value.true)); + } else { + Q(DeletePropertyOrThrow(O, to)); + } + k += 1; + } + Q(DeletePropertyOrThrow(O, X(ToString(new Value(len - 1))))); + Q(Set(O, new Value('length'), new Value(len - 1), Value.true)); + return first; +} + +// 22.1.3.25 #sec-array.prototype.slice +function ArrayProto_slice([start = Value.undefined, end = Value.undefined], { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + const relativeStart = Q(ToInteger(start)).numberValue(); + let k; + if (relativeStart < 0) { + k = Math.max(len + relativeStart, 0); + } else { + k = Math.min(relativeStart, len); + } + let relativeEnd; + if (Type(end) === 'Undefined') { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + const count = Math.max(final - k, 0); + const A = Q(ArraySpeciesCreate(O, new Value(count))); + let n = 0; + while (k < final) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + const nStr = X(ToString(new Value(n))); + Q(CreateDataPropertyOrThrow(A, nStr, kValue)); + } + k += 1; + n += 1; + } + Q(Set(A, new Value('length'), new Value(n), Value.true)); + return A; +} + +// 22.1.3.27 #sec-array.prototype.sort +function ArrayProto_sort([comparefn = Value.undefined], { thisValue }) { + if (comparefn !== Value.undefined && IsCallable(comparefn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', comparefn); + } + const obj = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(obj)); + + return ArrayProto_sortBody(obj, len, (x, y) => SortCompare(x, y, comparefn)); +} + +// 22.1.3.28 #sec-array.prototype.splice +function ArrayProto_splice(args, { thisValue }) { + const [start = Value.undefined, deleteCount = Value.undefined, ...items] = args; + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + const relativeStart = Q(ToInteger(start)).numberValue(); + let actualStart; + if (relativeStart < 0) { + actualStart = Math.max(len + relativeStart, 0); + } else { + actualStart = Math.min(relativeStart, len); + } + let insertCount; + let actualDeleteCount; + if (args.length === 0) { + insertCount = 0; + actualDeleteCount = 0; + } else if (args.length === 1) { + insertCount = 0; + actualDeleteCount = len - actualStart; + } else { + insertCount = args.length - 2; + const dc = Q(ToInteger(deleteCount)).numberValue(); + actualDeleteCount = Math.min(Math.max(dc, 0), len - actualStart); + } + if (len + insertCount - actualDeleteCount > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + const A = Q(ArraySpeciesCreate(O, new Value(actualDeleteCount))); + let k = 0; + while (k < actualDeleteCount) { + const from = X(ToString(new Value(actualStart + k))); + const fromPresent = Q(HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(Get(O, from)); + Q(CreateDataPropertyOrThrow(A, X(ToString(new Value(k))), fromValue)); + } + k += 1; + } + Q(Set(A, new Value('length'), new Value(actualDeleteCount), Value.true)); + const itemCount = items.length; + if (itemCount < actualDeleteCount) { + k = actualStart; + while (k < len - actualDeleteCount) { + const from = X(ToString(new Value(k + actualDeleteCount))); + const to = X(ToString(new Value(k + itemCount))); + const fromPresent = Q(HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(Get(O, from)); + Q(Set(O, to, fromValue, Value.true)); + } else { + Q(DeletePropertyOrThrow(O, to)); + } + k += 1; + } + k = len; + while (k > len - actualDeleteCount + itemCount) { + Q(DeletePropertyOrThrow(O, X(ToString(new Value(k - 1))))); + k -= 1; + } + } else if (itemCount > actualDeleteCount) { + k = len - actualDeleteCount; + while (k > actualStart) { + const from = X(ToString(new Value(k + actualDeleteCount - 1))); + const to = X(ToString(new Value(k + itemCount - 1))); + const fromPresent = Q(HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(Get(O, from)); + Q(Set(O, to, fromValue, Value.true)); + } else { + Q(DeletePropertyOrThrow(O, to)); + } + k -= 1; + } + } + k = actualStart; + while (items.length > 0) { + const E = items.shift(); + Q(Set(O, X(ToString(new Value(k))), E, Value.true)); + k += 1; + } + Q(Set(O, new Value('length'), new Value(len - actualDeleteCount + itemCount), Value.true)); + return A; +} + +// 22.1.3.30 #sec-array.prototype.tostring +function ArrayProto_toString(a, { thisValue }) { + const array = Q(ToObject(thisValue)); + let func = Q(Get(array, new Value('join'))); + if (IsCallable(func) === Value.false) { + func = surroundingAgent.intrinsic('%Object.prototype.toString%'); + } + return Q(Call(func, array)); +} + +// 22.1.3.31 #sec-array.prototype.unshift +function ArrayProto_unshift(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + const len = Q(LengthOfArrayLike(O)).numberValue(); + const argCount = args.length; + if (argCount > 0) { + if (len + argCount > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + let k = len; + while (k > 0) { + const from = X(ToString(new Value(k - 1))); + const to = X(ToString(new Value(k + argCount - 1))); + const fromPresent = Q(HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(Get(O, from)); + Q(Set(O, to, fromValue, Value.true)); + } else { + Q(DeletePropertyOrThrow(O, to)); + } + k -= 1; + } + let j = 0; + const items = args; + while (items.length !== 0) { + const E = items.shift(); + const jStr = X(ToString(new Value(j))); + Q(Set(O, jStr, E, Value.true)); + j += 1; + } + } + Q(Set(O, new Value('length'), new Value(len + argCount), Value.true)); + return new Value(len + argCount); +} + +// 22.1.3.32 #sec-array.prototype.values +function ArrayProto_values(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + return CreateArrayIterator(O, 'value'); +} + +export function BootstrapArrayPrototype(realmRec) { + const proto = X(ArrayCreate(new Value(0), realmRec.Intrinsics['%Object.prototype%'])); + + assignProps(realmRec, proto, [ + ['concat', ArrayProto_concat, 1], + ['copyWithin', ArrayProto_copyWithin, 2], + ['entries', ArrayProto_entries, 0], + ['fill', ArrayProto_fill, 1], + ['filter', ArrayProto_filter, 1], + ['flat', ArrayProto_flat, 0], + ['flatMap', ArrayProto_flatMap, 1], + ['keys', ArrayProto_keys, 0], + ['map', ArrayProto_map, 1], + ['pop', ArrayProto_pop, 0], + ['push', ArrayProto_push, 1], + ['shift', ArrayProto_shift, 0], + ['slice', ArrayProto_slice, 2], + ['sort', ArrayProto_sort, 1], + ['splice', ArrayProto_splice, 2], + ['toString', ArrayProto_toString, 0], + ['unshift', ArrayProto_unshift, 1], + ['values', ArrayProto_values, 0], + ]); + + BootstrapArrayPrototypeShared( + realmRec, + proto, + () => {}, + (O) => Get(O, new Value('length')), + ); + + proto.DefineOwnProperty(wellKnownSymbols.iterator, proto.GetOwnProperty(new Value('values'))); + + { + const unscopableList = OrdinaryObjectCreate(Value.null); + Assert(X(CreateDataProperty(unscopableList, new Value('copyWithin'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('entries'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('fill'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('find'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('findIndex'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('flat'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('flatMap'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('includes'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('keys'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, new Value('values'), Value.true)) === Value.true); + X(proto.DefineOwnProperty(wellKnownSymbols.unscopables, Descriptor({ + Value: unscopableList, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + + // Used in `arguments` objects. + realmRec.Intrinsics['%Array.prototype.values%'] = X(Get(proto, new Value('values'))); + + realmRec.Intrinsics['%Array.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/ArrayPrototypeShared.mjs b/engine262/src/intrinsics/ArrayPrototypeShared.mjs new file mode 100644 index 0000000..d54daf7 --- /dev/null +++ b/engine262/src/intrinsics/ArrayPrototypeShared.mjs @@ -0,0 +1,561 @@ +import { + Assert, + Call, + DeletePropertyOrThrow, + Get, + HasOwnProperty, + HasProperty, + Invoke, + IsCallable, + SameValueZero, + Set, + StrictEqualityComparison, + ToBoolean, + ToInteger, + ToLength, + ToObject, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { assignProps } from './Bootstrap.mjs'; + +// Algorithms and methods shared between %Array.prototype% and +// %TypedArray.prototype%. + +// 22.1.3.27 #sec-array.prototype.sort +// 22.2.3.26 #sec-%typedarray%.prototype.sort +// +// If internalMethodsRestricted is true, then Asserts are used to ensure that +// "The only internal methods of the this object that the algorithm may call +// are [[Get]] and [[Set]]," a requirement of %TypedArray%.prototype.sort. +export function ArrayProto_sortBody(obj, len, SortCompare, internalMethodsRestricted = false) { + len = len.numberValue(); + + // Collect all elements. Count how many holes we have for error checking. + const collected = []; + let holes = 0; + for (let k = 0; k < len; k += 1) { + const curProp = X(ToString(new Value(k))); + const prop = Q(Get(obj, curProp)); + if (prop === Value.undefined) { + Assert(!internalMethodsRestricted); + const hasOwn = Q(HasOwnProperty(obj, curProp)); + if (hasOwn === Value.false) { + holes += 1; + } else { + collected.push(prop); + } + } else { + collected.push(prop); + } + } + if (internalMethodsRestricted) { + Assert(holes === 0); + } + Assert(collected.length + holes === len); + + // Get rid of holes by deleting properties at the end. + // See Note 1: Because non-existent property values always compare greater + // than undefined property values, and undefined always compares greater + // than any other value, undefined property values always sort to the end + // of the result, followed by non-existent property values. + for (let k = collected.length; k < len; k += 1) { + const curProp = X(ToString(new Value(k))); + Q(DeletePropertyOrThrow(obj, curProp)); + } + + // Mergesort. + const lBuffer = []; + const rBuffer = []; + for (let step = 1; step < collected.length; step *= 2) { + for (let start = 0; start < collected.length - 1; start += 2 * step) { + const sizeLeft = step; + const mid = start + sizeLeft; + const sizeRight = Math.min(step, collected.length - mid); + if (sizeRight < 0) { + continue; + } + + // Merge. + for (let l = 0; l < sizeLeft; l += 1) { + lBuffer[l] = collected[start + l]; + } + for (let r = 0; r < sizeRight; r += 1) { + rBuffer[r] = collected[mid + r]; + } + + { + let l = 0; + let r = 0; + let o = start; + while (l < sizeLeft && r < sizeRight) { + const cmp = Q(SortCompare(lBuffer[l], rBuffer[r])).numberValue(); + if (cmp <= 0) { + collected[o] = lBuffer[l]; + o += 1; + l += 1; + } else { + collected[o] = rBuffer[r]; + o += 1; + r += 1; + } + } + while (l < sizeLeft) { + collected[o] = lBuffer[l]; + o += 1; + l += 1; + } + while (r < sizeRight) { + collected[o] = rBuffer[r]; + o += 1; + r += 1; + } + } + } + } + + // Copy the sorted results back to the array. + for (let k = 0; k < collected.length; k += 1) { + const curProp = X(ToString(new Value(k))); + Q(Set(obj, curProp, collected[k], Value.true)); + } + + return obj; +} + +export function BootstrapArrayPrototypeShared(realmRec, proto, priorToEvaluatingAlgorithm, objectToLength) { + // 22.1.3.5 #sec-array.prototype.every + // 22.2.3.7 #sec-%typedarray%.prototype.every + function ArrayProto_every([callbackFn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)); + if (IsCallable(callbackFn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackFn); + } + let k = 0; + while (k < len.numberValue()) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + const testResult = ToBoolean(Q(Call(callbackFn, thisArg, [kValue, new Value(k), O]))); + if (testResult === Value.false) { + return Value.false; + } + } + k += 1; + } + return Value.true; + } + + // 22.1.3.8 #sec-array.prototype.find + // 22.2.3.10 #sec-%typedarray%.prototype.find + function ArrayProto_find([predicate = Value.undefined, thisArg = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (IsCallable(predicate) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kValue = Q(Get(O, Pk)); + const testResult = ToBoolean(Q(Call(predicate, thisArg, [kValue, new Value(k), O]))); + if (testResult === Value.true) { + return kValue; + } + k += 1; + } + return Value.undefined; + } + + // 22.1.3.9 #sec-array.prototype.findindex + // 22.2.3.11 #sec-%typedarray%.prototype.findindex + function ArrayProto_findIndex([predicate = Value.undefined, thisArg = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (IsCallable(predicate) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kValue = Q(Get(O, Pk)); + const testResult = ToBoolean(Q(Call(predicate, thisArg, [kValue, new Value(k), O]))); + if (testResult === Value.true) { + return new Value(k); + } + k += 1; + } + return new Value(-1); + } + + // 22.1.3.12 #sec-array.prototype.foreach + // 22.2.3.12 #sec-%typedarray%.prototype.foreach + function ArrayProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + Q(Call(callbackfn, thisArg, [kValue, new Value(k), O])); + } + k += 1; + } + return Value.undefined; + } + + // 22.1.3.13 #sec-array.prototype.includes + // 22.2.3.13 #sec-%typedarray%.prototype.includes + function ArrayProto_includes([searchElement = Value.undefined, fromIndex = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (len === 0) { + return Value.false; + } + const n = Q(ToInteger(fromIndex)).numberValue(); + if (fromIndex === Value.undefined) { + Assert(n === 0); + } + let k; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } + } + while (k < len) { + const kStr = X(ToString(new Value(k))); + const elementK = Q(Get(O, kStr)); + if (SameValueZero(searchElement, elementK) === Value.true) { + return Value.true; + } + k += 1; + } + return Value.false; + } + + // 22.1.3.14 #sec-array.prototype.indexof + // 22.2.3.14 #sec-%typedarray%.prototype.indexof + function ArrayProto_indexOf([searchElement = Value.undefined, fromIndex = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (len === 0) { + return new Value(-1); + } + const n = Q(ToInteger(fromIndex)).numberValue(); + if (fromIndex === Value.undefined) { + Assert(n === 0); + } + if (n >= len) { + return new Value(-1); + } + let k; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } + } + while (k < len) { + const kStr = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, kStr)); + if (kPresent === Value.true) { + const elementK = Q(Get(O, kStr)); + const same = StrictEqualityComparison(searchElement, elementK); + if (same === Value.true) { + return new Value(k); + } + } + k += 1; + } + return new Value(-1); + } + + // 22.1.3.15 #sec-array.prototype.join + // 22.2.3.15 #sec-%typedarray%.prototype.join + function ArrayProto_join([separator = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + let sep; + if (Type(separator) === 'Undefined') { + sep = ','; + } else { + sep = Q(ToString(separator)).stringValue(); + } + let R = ''; + let k = 0; + while (k < len) { + if (k > 0) { + R = `${R}${sep}`; + } + const kStr = X(ToString(new Value(k))); + const element = Q(Get(O, kStr)); + let next; + if (Type(element) === 'Undefined' || Type(element) === 'Null') { + next = ''; + } else { + next = Q(ToString(element)).stringValue(); + } + R = `${R}${next}`; + k += 1; + } + return new Value(R); + } + + // 22.1.3.17 #sec-array.prototype.lastindexof + // 22.2.3.17 #sec-%typedarray%.prototype.lastindexof + function ArrayProto_lastIndexOf([searchElement = Value.undefined, fromIndex], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (len === 0) { + return new Value(-1); + } + let n; + if (fromIndex !== undefined) { + n = Q(ToInteger(fromIndex)).numberValue(); + } else { + n = len - 1; + } + let k; + if (n >= 0) { + k = Math.min(n, len - 1); + } else { + k = len + n; + } + while (k >= 0) { + const kStr = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, kStr)); + if (kPresent === Value.true) { + const elementK = Q(Get(O, kStr)); + const same = StrictEqualityComparison(searchElement, elementK); + if (same === Value.true) { + return new Value(k); + } + } + k -= 1; + } + return new Value(-1); + } + + // 22.1.3.21 #sec-array.prototype.reduce + // 22.2.3.20 #sec-%typedarray%.prototype.reduce + function ArrayProto_reduce([callbackfn = Value.undefined, initialValue], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + if (len === 0 && initialValue === undefined) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + let k = 0; + let accumulator = Value.undefined; + if (initialValue !== undefined) { + accumulator = initialValue; + } else { + let kPresent = false; + while (kPresent === false && k < len) { + const Pk = X(ToString(new Value(k))); + kPresent = Q(HasProperty(O, Pk)) === Value.true; + if (kPresent === true) { + accumulator = Q(Get(O, Pk)); + } + k += 1; + } + if (kPresent === false) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + } + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + accumulator = Q(Call(callbackfn, Value.undefined, [accumulator, kValue, new Value(k), O])); + } + k += 1; + } + return accumulator; + } + + // 22.1.3.22 #sec-array.prototype.reduceright + // 22.2.3.21 #sec-%typedarray%.prototype.reduceright + function ArrayProto_reduceRight([callbackfn = Value.undefined, initialValue], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + if (len === 0 && initialValue === undefined) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + let k = len - 1; + let accumulator = Value.undefined; + if (initialValue !== undefined) { + accumulator = initialValue; + } else { + let kPresent = false; + while (kPresent === false && k >= 0) { + const Pk = X(ToString(new Value(k))); + kPresent = Q(HasProperty(O, Pk)) === Value.true; + if (kPresent === true) { + accumulator = Q(Get(O, Pk)); + } + k -= 1; + } + if (kPresent === false) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + } + while (k >= 0) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + accumulator = Q(Call(callbackfn, Value.undefined, [accumulator, kValue, new Value(k), O])); + } + k -= 1; + } + return accumulator; + } + + // 22.1.3.23 #sec-array.prototype.reverse + // 22.2.3.22 #sec-%typedarray%.prototype.reverse + function ArrayProto_reverse(args, { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + const middle = Math.floor(len / 2); + let lower = 0; + while (lower !== middle) { + const upper = len - lower - 1; + const upperP = X(ToString(new Value(upper))); + const lowerP = X(ToString(new Value(lower))); + const lowerExists = Q(HasProperty(O, lowerP)); + let lowerValue; + let upperValue; + if (lowerExists === Value.true) { + lowerValue = Q(Get(O, lowerP)); + } + const upperExists = Q(HasProperty(O, upperP)); + if (upperExists === Value.true) { + upperValue = Q(Get(O, upperP)); + } + if (lowerExists === Value.true && upperExists === Value.true) { + Q(Set(O, lowerP, upperValue, Value.true)); + Q(Set(O, upperP, lowerValue, Value.true)); + } else if (lowerExists === Value.false && upperExists === Value.true) { + Q(Set(O, lowerP, upperValue, Value.true)); + Q(DeletePropertyOrThrow(O, upperP)); + } else if (lowerExists === Value.true && upperExists === Value.false) { + Q(DeletePropertyOrThrow(O, lowerP)); + Q(Set(O, upperP, lowerValue, Value.true)); + } else { + // no further action is required + } + lower += 1; + } + return O; + } + + // 22.1.3.26 #sec-array.prototype.some + // 22.2.3.25 #sec-%typedarray%.prototype.some + function ArrayProto_some([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const O = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(O)); + const len = Q(ToLength(lenProp)).numberValue(); + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kPresent = Q(HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(Get(O, Pk)); + const testResult = ToBoolean(Q(Call(callbackfn, thisArg, [kValue, new Value(k), O]))); + if (testResult === Value.true) { + return Value.true; + } + } + k += 1; + } + return Value.false; + } + + // 22.1.3.29 #sec-array.prototype.tolocalestring + // 22.2.3.28 #sec-%typedarray%.prototype.tolocalestring + function ArrayProto_toLocaleString(args, { thisValue }) { + Q(priorToEvaluatingAlgorithm(thisValue)); + const array = Q(ToObject(thisValue)); + const lenProp = Q(objectToLength(array)); + const len = Q(ToLength(lenProp)).numberValue(); + const separator = ', '; + let R = ''; + let k = 0; + while (k < len) { + if (k > 0) { + R = `${R}${separator}`; + } + const kStr = X(ToString(new Value(k))); + const nextElement = Q(Get(array, kStr)); + if (nextElement !== Value.undefined && nextElement !== Value.null) { + const S = Q(ToString(Q(Invoke(nextElement, new Value('toLocaleString'))))).stringValue(); + R = `${R}${S}`; + } + k += 1; + } + return new Value(R); + } + + assignProps(realmRec, proto, [ + ['every', ArrayProto_every, 1], + ['find', ArrayProto_find, 1], + ['findIndex', ArrayProto_findIndex, 1], + ['forEach', ArrayProto_forEach, 1], + ['includes', ArrayProto_includes, 1], + ['indexOf', ArrayProto_indexOf, 1], + ['join', ArrayProto_join, 1], + ['lastIndexOf', ArrayProto_lastIndexOf, 1], + ['reduce', ArrayProto_reduce, 1], + ['reduceRight', ArrayProto_reduceRight, 1], + ['reverse', ArrayProto_reverse, 0], + ['some', ArrayProto_some, 1], + ['toLocaleString', ArrayProto_toLocaleString, 0], + ]); +} diff --git a/engine262/src/intrinsics/AsyncFromSyncIteratorPrototype.mjs b/engine262/src/intrinsics/AsyncFromSyncIteratorPrototype.mjs new file mode 100644 index 0000000..db99a56 --- /dev/null +++ b/engine262/src/intrinsics/AsyncFromSyncIteratorPrototype.mjs @@ -0,0 +1,140 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + AsyncFromSyncIteratorContinuation, + Call, + CreateIterResultObject, + GetMethod, + IteratorNext, + NewPromiseCapability, + Assert, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { IfAbruptRejectPromise, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-%asyncfromsynciteratorprototype%.next +function AsyncFromSyncIteratorPrototype_next([value], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot. + Assert(Type(O) === 'Object' && 'SyncIteratorRecord' in O); + // 3. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]]. + const syncIteratorRecord = O.SyncIteratorRecord; + // 5. If value is present, then + let result; + if (value !== undefined) { + // a. Let result be IteratorNext(syncIteratorRecord, value). + result = IteratorNext(syncIteratorRecord, value); + } else { // 6. Else, + // a. Let result be IteratorNext(syncIteratorRecord). + result = IteratorNext(syncIteratorRecord); + } + // 7. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + // 8. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability). + return X(AsyncFromSyncIteratorContinuation(result, promiseCapability)); +} + +// #sec-%asyncfromsynciteratorprototype%.return +function AsyncFromSyncIteratorPrototype_return([value], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot. + Assert(Type(O) === 'Object' && 'SyncIteratorRecord' in O); + // 3. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. + const syncIterator = O.SyncIteratorRecord.Iterator; + // 5. Let return be GetMethod(syncIterator, "return"). + const ret = GetMethod(syncIterator, new Value('return')); + // 6. IfAbruptRejectPromise(return, promiseCapability). + IfAbruptRejectPromise(ret, promiseCapability); + // 7. If return is undefined, then + if (ret === Value.undefined) { + // a. Let iterResult be ! CreateIterResultObject(value, true). + const iterResult = X(CreateIterResultObject(value, Value.true)); + // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iterResult »). + X(Call(promiseCapability.Resolve, Value.undefined, [iterResult])); + // c. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // 8. If value is present, then + let result; + if (value !== undefined) { + // a. Let result be Call(return, syncIterator, « value »). + result = Call(ret, syncIterator, [value]); + } else { // 9. Else, + // a. Let result be Call(return, syncIterator). + result = Call(ret, syncIterator); + } + // 10. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + // 11. If Type(result) is not Object, then + if (Type(result) !== 'Object') { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + X(Call(promiseCapability.Reject, Value.undefined, [ + surroundingAgent.Throw('TypeError', 'NotAnObject', result).Value, + ])); + // b. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // 12. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability). + return X(AsyncFromSyncIteratorContinuation(result, promiseCapability)); +} + +// #sec-%asyncfromsynciteratorprototype%.throw +function AsyncFromSyncIteratorPrototype_throw([value], { thisValue }) { + // 1. Let O be this value. + const O = thisValue; + // 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot. + Assert(Type(O) === 'Object' && 'SyncIteratorRecord' in O); + // 3. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. + const syncIterator = O.SyncIteratorRecord.Iterator; + // 5. Let throw be GetMethod(syncIterator, "throw"). + const thr = GetMethod(syncIterator, new Value('throw')); + // 6. IfAbruptRejectPromise(throw, promiseCapability). + IfAbruptRejectPromise(thr, promiseCapability); + // 7. If throw is undefined, then + if (thr === Value.undefined) { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). + X(Call(promiseCapability.Reject, Value.undefined, [value])); + // b. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // 8. If value is present, then + let result; + if (value !== undefined) { + // a. Let result be Call(throw, syncIterator, « value »). + result = Call(thr, syncIterator, [value]); + } else { // 9. Else, + // a. Let result be Call(throw, syncIterator). + result = Call(thr, syncIterator); + } + // 10. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + // 11. If Type(result) is not Object, then + if (Type(result) !== 'Object') { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + X(Call(promiseCapability.Reject, Value.undefined, [ + surroundingAgent.Throw('TypeError', 'NotAnObject', result).Value, + ])); + // b. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // 12. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability). + return X(AsyncFromSyncIteratorContinuation(result, promiseCapability)); +} + +export function BootstrapAsyncFromSyncIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', AsyncFromSyncIteratorPrototype_next, 0], + ['return', AsyncFromSyncIteratorPrototype_return, 0], + ['throw', AsyncFromSyncIteratorPrototype_throw, 0], + ], realmRec.Intrinsics['%AsyncIteratorPrototype%']); + + realmRec.Intrinsics['%AsyncFromSyncIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/AsyncFunction.mjs b/engine262/src/intrinsics/AsyncFunction.mjs new file mode 100644 index 0000000..a478fd0 --- /dev/null +++ b/engine262/src/intrinsics/AsyncFunction.mjs @@ -0,0 +1,28 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Q } from '../completion.mjs'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-async-function-constructor-arguments +function AsyncFunctionConstructor(args, { NewTarget }) { + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject; + // 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]]. + // 3. Return CreateDynamicFunction(C, NewTarget, async, args). + return Q(CreateDynamicFunction(C, NewTarget, 'async', args)); +} + +export function BootstrapAsyncFunction(realmRec) { + const cons = BootstrapConstructor(realmRec, AsyncFunctionConstructor, 'AsyncFunction', 1, realmRec.Intrinsics['%AsyncFunction.prototype%'], []); + + cons.DefineOwnProperty(new Value('prototype'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + })); + + cons.Prototype = realmRec.Intrinsics['%Function%']; + + realmRec.Intrinsics['%AsyncFunction%'] = cons; +} diff --git a/engine262/src/intrinsics/AsyncFunctionPrototype.mjs b/engine262/src/intrinsics/AsyncFunctionPrototype.mjs new file mode 100644 index 0000000..f76de66 --- /dev/null +++ b/engine262/src/intrinsics/AsyncFunctionPrototype.mjs @@ -0,0 +1,7 @@ +import { BootstrapPrototype } from './Bootstrap.mjs'; + +export function BootstrapAsyncFunctionPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [], realmRec.Intrinsics['%Function.prototype%'], 'AsyncFunction'); + + realmRec.Intrinsics['%AsyncFunction.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/AsyncGenerator.mjs b/engine262/src/intrinsics/AsyncGenerator.mjs new file mode 100644 index 0000000..d7db3cd --- /dev/null +++ b/engine262/src/intrinsics/AsyncGenerator.mjs @@ -0,0 +1,18 @@ +import { X } from '../completion.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +export function BootstrapAsyncGenerator(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['prototype', realmRec.Intrinsics['%AsyncGenerator.prototype%'], undefined, { Writable: Value.false }], + ], realmRec.Intrinsics['%Function.prototype%'], 'AsyncGeneratorFunction'); + + X(realmRec.Intrinsics['%AsyncGenerator.prototype%'].DefineOwnProperty(new Value('constructor'), Descriptor({ + Value: proto, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/AsyncGeneratorFunction.mjs b/engine262/src/intrinsics/AsyncGeneratorFunction.mjs new file mode 100644 index 0000000..5a772da --- /dev/null +++ b/engine262/src/intrinsics/AsyncGeneratorFunction.mjs @@ -0,0 +1,34 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Q, X } from '../completion.mjs'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-asyncgeneratorfunction +function AsyncGeneratorFunctionConstructor(args, { NewTarget }) { + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject; + // 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]]. + // 3. Return ? CreateDynamicFunction(C, NewTarget, asyncGenerator, args). + return Q(CreateDynamicFunction(C, NewTarget, 'asyncGenerator', args)); +} + +export function BootstrapAsyncGeneratorFunction(realmRec) { + const cons = BootstrapConstructor(realmRec, AsyncGeneratorFunctionConstructor, 'AsyncGeneratorFunction', 1, realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'], []); + + X(cons.DefineOwnProperty(new Value('prototype'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + X(realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'].DefineOwnProperty(new Value('constructor'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + cons.Prototype = realmRec.Intrinsics['%Function%']; + + realmRec.Intrinsics['%AsyncGeneratorFunction%'] = cons; +} diff --git a/engine262/src/intrinsics/AsyncGeneratorPrototype.mjs b/engine262/src/intrinsics/AsyncGeneratorPrototype.mjs new file mode 100644 index 0000000..3e5767e --- /dev/null +++ b/engine262/src/intrinsics/AsyncGeneratorPrototype.mjs @@ -0,0 +1,49 @@ +import { + X, + Completion, + NormalCompletion, + ThrowCompletion, +} from '../completion.mjs'; +import { Value } from '../value.mjs'; +import { AsyncGeneratorEnqueue } from '../abstract-ops/all.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-asyncgenerator-prototype-next +function AsyncGeneratorPrototype_next([value = Value.undefined], { thisValue }) { + // 1. Let generator be the this value. + const generator = thisValue; + // 2. Let completion be NormalCompletion(value). + const completion = NormalCompletion(value); + // 3. Return ! AsyncGeneratorEnqueue(generator, completion). + return X(AsyncGeneratorEnqueue(generator, completion)); +} + +// #sec-asyncgenerator-prototype-return +function AsyncGeneratorPrototype_return([value = Value.undefined], { thisValue }) { + // 1. Let generator be the this value. + const generator = thisValue; + // 2. Let completion be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + const completion = new Completion({ Type: 'return', Value: value, Target: undefined }); + // 3. Return ! AsyncGeneratorEnqueue(generator, completion). + return X(AsyncGeneratorEnqueue(generator, completion)); +} + +// #sec-asyncgenerator-prototype-throw +function AsyncGeneratorPrototype_throw([exception = Value.undefined], { thisValue }) { + // 1. Let generator be the this value. + const generator = thisValue; + // 2. Let completion be ThrowCompletion(exception). + const completion = ThrowCompletion(exception); + // 3. Return ! AsyncGeneratorEnqueue(generator, completion). + return X(AsyncGeneratorEnqueue(generator, completion)); +} + +export function BootstrapAsyncGeneratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', AsyncGeneratorPrototype_next, 1], + ['return', AsyncGeneratorPrototype_return, 1], + ['throw', AsyncGeneratorPrototype_throw, 1], + ], realmRec.Intrinsics['%AsyncIteratorPrototype%'], 'AsyncGenerator'); + + realmRec.Intrinsics['%AsyncGenerator.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/AsyncIteratorPrototype.mjs b/engine262/src/intrinsics/AsyncIteratorPrototype.mjs new file mode 100644 index 0000000..84d758d --- /dev/null +++ b/engine262/src/intrinsics/AsyncIteratorPrototype.mjs @@ -0,0 +1,16 @@ +import { wellKnownSymbols } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-asynciteratorprototype-asynciterator +function AsyncIteratorPrototype_asyncIterator(args, { thisValue }) { + // 1. Return the this value. + return thisValue; +} + +export function BootstrapAsyncIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + [wellKnownSymbols.asyncIterator, AsyncIteratorPrototype_asyncIterator, 0], + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%AsyncIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/BigInt.mjs b/engine262/src/intrinsics/BigInt.mjs new file mode 100644 index 0000000..6d4fc58 --- /dev/null +++ b/engine262/src/intrinsics/BigInt.mjs @@ -0,0 +1,53 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { ToPrimitive, ToBigInt, ToIndex } from '../abstract-ops/all.mjs'; +import { NumberToBigInt } from '../runtime-semantics/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-bigint-constructor +function BigIntConstructor([value], { NewTarget }) { + // 1. If NewTarget is not undefined, throw a TypeError exception. + if (NewTarget !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', 'BigInt'); + } + // 2. Let prim be ? ToPrimitive(value, hint Number). + const prim = Q(ToPrimitive(value, 'Number')); + // 3. If Type(prim) is Number, return ? NumberToBigInt(prim). + // 4. Otherwise, return ? ToBigInt(value). + if (Type(prim) === 'Number') { + return Q(NumberToBigInt(prim)); + } else { + return Q(ToBigInt(value)); + } +} + +// #sec-bigint.asintn +function BigInt_asIntN([bits = Value.undefined, bigint = Value.undefined]) { + // 1. Set bits to ? ToIndex(bits). + bits = Q(ToIndex(bits)); + // 2. Set bigint to ? ToBigInt(bigint). + bigint = Q(ToBigInt(bigint)); + // 3. Let mod be the BigInt value that represents bigint modulo 2bits. + // 4. If mod ≥ 2^bits - 1, return mod - 2^bits; otherwise, return mod. + return new Value(BigInt.asIntN(bits.numberValue(), bigint.bigintValue())); +} + +// #sec-bigint.asuintn +function BigInt_asUintN([bits = Value.undefined, bigint = Value.undefined]) { + // 1. Set bits to ? ToIndex(bits). + bits = Q(ToIndex(bits)); + // 2. Set bigint to ? ToBigInt(bigint). + bigint = Q(ToBigInt(bigint)); + // 3. Return the BigInt value that represents bigint modulo 2^bits. + return new Value(BigInt.asUintN(bits.numberValue(), bigint.bigintValue())); +} + +export function BootstrapBigInt(realmRec) { + const bigintConstructor = BootstrapConstructor(realmRec, BigIntConstructor, 'BigInt', 1, realmRec.Intrinsics['%BigInt.prototype%'], [ + ['asIntN', BigInt_asIntN, 2], + ['asUintN', BigInt_asUintN, 2], + ]); + + realmRec.Intrinsics['%BigInt%'] = bigintConstructor; +} diff --git a/engine262/src/intrinsics/BigIntPrototype.mjs b/engine262/src/intrinsics/BigIntPrototype.mjs new file mode 100644 index 0000000..e2df0b7 --- /dev/null +++ b/engine262/src/intrinsics/BigIntPrototype.mjs @@ -0,0 +1,74 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { Assert, ToInteger, ToString } from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-thisbigintvalue +function thisBigIntValue(value) { + // 1. If Type(value) is BigInt, return value. + if (Type(value) === 'BigInt') { + return value; + } + // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then + if (Type(value) === 'Object' && 'BigIntData' in value) { + // a. Assert: Type(value.[[BigIntData]]) is BigInt. + Assert(Type(value.BigIntData) === 'BigInt'); + // b. Return value.[[BigIntData]]. + return value.BigIntData; + } + // 3. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'BigInt', value); +} + +// #sec-bigint.prototype.tolocalestring +function BigIntProto_toLocalString(args, { thisValue }) { + return BigIntProto_toString(args, { thisValue }); +} + +// #sec-bigint.prototype.tostring +function BigIntProto_toString([radix], { thisValue }) { + // 1. Let x be ? thisBigIntValue(this value). + const x = Q(thisBigIntValue(thisValue)); + // 2. If radix is not present, let radixNumber be 10. + let radixNumber; + if (radix === undefined) { + radixNumber = 10; + } else if (radix === Value.undefined) { + // 3. Else if radix is undefined, let radixNumber be 10. + radixNumber = 10; + } else { + // 4. Else, let radixNumber be ? ToInteger(radix). + radixNumber = Q(ToInteger(radix)).numberValue(); + } + // 5. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. + if (radixNumber < 2 || radixNumber > 36) { + return surroundingAgent.Throw('RangeError', 'InvalidRadix'); + } + // 6. If radixNumber = 10, return ! ToString(x). + if (radixNumber === 10) { + return X(ToString(x)); + } + // 7. Return the String representation of this Number value using the radix specified by + // radixNumber. Letters a-z are used for digits with values 10 through 35. The precise + // algorithm is implementation-dependent, however the algorithm should be a + // generalization of that specified in 6.1.6.2.23. + // TODO: Implementation stringification + return new Value(x.bigintValue().toString(radixNumber)); +} + +// #sec-bigint.prototype.tostring +function BigIntProto_valueOf(args, { thisValue }) { + // Return ? thisBigIntValue(this value). + return Q(thisBigIntValue(thisValue)); +} + +export function BootstrapBigIntPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['toLocaleString', BigIntProto_toLocalString, 0], + ['toString', BigIntProto_toString, 0], + ['valueOf', BigIntProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'BigInt'); + + realmRec.Intrinsics['%BigInt.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Boolean.mjs b/engine262/src/intrinsics/Boolean.mjs new file mode 100644 index 0000000..ebd6f87 --- /dev/null +++ b/engine262/src/intrinsics/Boolean.mjs @@ -0,0 +1,32 @@ +import { + OrdinaryCreateFromConstructor, + ToBoolean, +} from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-boolean-constructor-boolean-value +function BooleanConstructor([value = Value.undefined], { NewTarget }) { + // 1. Let b be ! ToBoolean(value). + const b = X(ToBoolean(value)); + // 2. If NewTarget is undefined, return b. + if (NewTarget === Value.undefined) { + return b; + } + // 3. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Boolean.prototype%", « [[BooleanData]] »). + const O = Q(OrdinaryCreateFromConstructor(NewTarget, '%Boolean.prototype%', ['BooleanData'])); + // 4. Set O.[[BooleanData]] to b. + O.BooleanData = b; + // 5. Return O. + return O; +} + +export function BootstrapBoolean(realmRec) { + const cons = BootstrapConstructor( + realmRec, BooleanConstructor, 'Boolean', 1, + realmRec.Intrinsics['%Boolean.prototype%'], [], + ); + + realmRec.Intrinsics['%Boolean%'] = cons; +} diff --git a/engine262/src/intrinsics/BooleanPrototype.mjs b/engine262/src/intrinsics/BooleanPrototype.mjs new file mode 100644 index 0000000..b0f92ed --- /dev/null +++ b/engine262/src/intrinsics/BooleanPrototype.mjs @@ -0,0 +1,53 @@ +import { + Type, + Value, +} from '../value.mjs'; +import { + surroundingAgent, +} from '../engine.mjs'; +import { Assert } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + + +function thisBooleanValue(value) { + if (Type(value) === 'Boolean') { + return value; + } + + if (Type(value) === 'Object' && 'BooleanData' in value) { + const b = value.BooleanData; + Assert(Type(b) === 'Boolean'); + return b; + } + + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Boolean', value); +} + +// #sec-boolean.prototype.tostring +function BooleanProto_toString(argList, { thisValue }) { + // 1. Let b be ? thisBooleanValue(this value). + const b = Q(thisBooleanValue(thisValue)); + // 2. If b is true, return "true"; else return "false". + if (b === Value.true) { + return new Value('true'); + } + return new Value('false'); +} + +// #sec-boolean.prototype.valueof +function BooleanProto_valueOf(argList, { thisValue }) { + // 1. Return ? thisBooleanValue(this value). + return Q(thisBooleanValue(thisValue)); +} + +export function BootstrapBooleanPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['toString', BooleanProto_toString, 0], + ['valueOf', BooleanProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%']); + + proto.BooleanData = Value.false; + + realmRec.Intrinsics['%Boolean.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Bootstrap.mjs b/engine262/src/intrinsics/Bootstrap.mjs new file mode 100644 index 0000000..3a9aeb0 --- /dev/null +++ b/engine262/src/intrinsics/Bootstrap.mjs @@ -0,0 +1,116 @@ +import { + Assert, + CreateBuiltinFunction, + OrdinaryObjectCreate, + SetFunctionLength, + SetFunctionName, +} from '../abstract-ops/all.mjs'; +import { + Descriptor, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { X } from '../completion.mjs'; + +// 17 #sec-ecmascript-standard-built-in-objects +export function assignProps(realmRec, obj, props) { + for (const item of props) { + if (item === undefined) { + continue; + } + const [n, v, len, descriptor] = item; + const name = n instanceof Value ? n : new Value(n); + if (Array.isArray(v)) { + // Every accessor property described in clauses 18 through 26 and in + // Annex B.2 has the attributes { [[Enumerable]]: false, + // [[Configurable]]: true } unless otherwise specified. If only a get + // accessor function is described, the set accessor function is the + // default value, undefined. If only a set accessor is described the get + // accessor is the default value, undefined. + let [ + getter = Value.undefined, + setter = Value.undefined, + ] = v; + if (typeof getter === 'function') { + getter = CreateBuiltinFunction(getter, [], realmRec); + X(SetFunctionName(getter, name, new Value('get'))); + X(SetFunctionLength(getter, new Value(0))); + } + if (typeof setter === 'function') { + setter = CreateBuiltinFunction(setter, [], realmRec); + X(SetFunctionName(setter, name, new Value('set'))); + X(SetFunctionLength(setter, new Value(1))); + } + X(obj.DefineOwnProperty(name, Descriptor({ + Get: getter, + Set: setter, + Enumerable: Value.false, + Configurable: Value.true, + ...descriptor, + }))); + } else { + // Every other data property described in clauses 18 through 26 and in + // Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: + // false, [[Configurable]]: true } unless otherwise specified. + let value; + if (typeof v === 'function') { + Assert(typeof len === 'number'); + value = CreateBuiltinFunction(v, [], realmRec); + X(SetFunctionName(value, name)); + X(SetFunctionLength(value, new Value(len))); + } else { + value = v; + } + obj.properties.set(name, Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + ...descriptor, + })); + } + } +} + +export function BootstrapPrototype(realmRec, props, Prototype, stringTag) { + Assert(Prototype !== undefined); + const proto = OrdinaryObjectCreate(Prototype); + + assignProps(realmRec, proto, props); + + if (stringTag !== undefined) { + X(proto.DefineOwnProperty(wellKnownSymbols.toStringTag, Descriptor({ + Value: new Value(stringTag), + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + + return proto; +} + +export function BootstrapConstructor(realmRec, Constructor, name, length, Prototype, props = []) { + const cons = CreateBuiltinFunction(Constructor, [], realmRec, undefined, Value.true); + + SetFunctionName(cons, new Value(name)); + SetFunctionLength(cons, new Value(length)); + + X(cons.DefineOwnProperty(new Value('prototype'), Descriptor({ + Value: Prototype, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + X(Prototype.DefineOwnProperty(new Value('constructor'), Descriptor({ + Value: cons, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + assignProps(realmRec, cons, props); + + return cons; +} diff --git a/engine262/src/intrinsics/DataView.mjs b/engine262/src/intrinsics/DataView.mjs new file mode 100644 index 0000000..f741f26 --- /dev/null +++ b/engine262/src/intrinsics/DataView.mjs @@ -0,0 +1,65 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + IsDetachedBuffer, + OrdinaryCreateFromConstructor, + ToIndex, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-dataview-constructor +function DataViewConstructor([buffer = Value.undefined, byteOffset = Value.undefined, byteLength = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). + Q(RequireInternalSlot(buffer, 'ArrayBufferData')); + // 3. Let offset be ? ToIndex(byteOffset). + const offset = Q(ToIndex(byteOffset)).numberValue(); + // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. + const bufferByteLength = buffer.ArrayBufferByteLength.numberValue(); + // 6. If offset > bufferByteLength, throw a RangeError exception. + if (offset > bufferByteLength) { + return surroundingAgent.Throw('RangeError', 'DataViewOOB'); + } + let viewByteLength; + // 7. If byteLength is undefined, then + if (byteLength === Value.undefined) { + // a. Let viewByteLength be bufferByteLength - offset. + viewByteLength = bufferByteLength - offset; + } else { + // a. Let viewByteLength be ? ToIndex(byteLength). + viewByteLength = Q(ToIndex(byteLength)).numberValue(); + // b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. + if (offset + viewByteLength > bufferByteLength) { + return surroundingAgent.Throw('RangeError', 'DataViewOOB'); + } + } + // 9. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). + const O = Q(OrdinaryCreateFromConstructor(NewTarget, '%DataView.prototype%', ['DataView', 'ViewedArrayBuffer', 'ByteLength', 'ByteOffset'])); + // 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 11. Set O.[[ViewedArrayBuffer]] to buffer. + O.ViewedArrayBuffer = buffer; + // 12. Set O.[[ByteLength]] to viewByteLength. + O.ByteLength = new Value(viewByteLength); + // 13. Set O.[[ByteOffset]] to offset. + O.ByteOffset = new Value(offset); + // 14. Return O. + return O; +} + +export function BootstrapDataView(realmRec) { + const dvConstructor = BootstrapConstructor(realmRec, DataViewConstructor, 'DataView', 1, realmRec.Intrinsics['%DataView.prototype%'], []); + + realmRec.Intrinsics['%DataView%'] = dvConstructor; +} diff --git a/engine262/src/intrinsics/DataViewPrototype.mjs b/engine262/src/intrinsics/DataViewPrototype.mjs new file mode 100644 index 0000000..ae32a5a --- /dev/null +++ b/engine262/src/intrinsics/DataViewPrototype.mjs @@ -0,0 +1,267 @@ +import { + Assert, + GetViewValue, + SetViewValue, + IsDetachedBuffer, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-get-dataview.prototype.buffer +function DataViewProto_buffer(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[DataView]]). + Q(RequireInternalSlot(O, 'DataView')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. Return buffer. + return buffer; +} + +// #sec-get-dataview.prototype.bytelength +function DataViewProto_byteLength(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[DataView]]). + Q(RequireInternalSlot(O, 'DataView')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 6. Let size be O.[[ByteLength]]. + const size = O.ByteLength; + // 7. Return size. + return size; +} + +// #sec-get-dataview.prototype.byteoffset +function DataViewProto_byteOffset(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[DataView]]). + Q(RequireInternalSlot(O, 'DataView')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 6. Let offset be O.[[ByteOffset]]. + const offset = O.ByteOffset; + // 7. Return offset. + return offset; +} + +// #sec-dataview.prototype.getbigint64 +function DataViewProto_getBigInt64([byteOffset = Value.undefined, littleEndian = Value.undefined], { thisValue }) { + // 1. Let v be the this value. + const v = thisValue; + // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). + return Q(GetViewValue(v, byteOffset, littleEndian, 'BigInt64')); +} + +// #sec-dataview.prototype.getbiguint64 +function DataViewProto_getBigUint64([byteOffset = Value.undefined, littleEndian = Value.undefined], { thisValue }) { + // 1. Let v be the this value. + const v = thisValue; + // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigUint64). + return Q(GetViewValue(v, byteOffset, littleEndian, 'BigUint64')); +} + +// 24.3.4.5 #sec-dataview.prototype.getfloat32 +function DataViewProto_getFloat32([byteOffset = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(GetViewValue(v, byteOffset, littleEndian, 'Float32')); +} + +// 24.3.4.6 #sec-dataview.prototype.getfloat64 +function DataViewProto_getFloat64([byteOffset = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(GetViewValue(v, byteOffset, littleEndian, 'Float64')); +} + +// 24.3.4.7 #sec-dataview.prototype.getint8 +function DataViewProto_getInt8([byteOffset = Value.undefined], { thisValue }) { + const v = thisValue; + return Q(GetViewValue(v, byteOffset, Value.true, 'Int8')); +} + +// 24.3.4.8 #sec-dataview.prototype.getint16 +function DataViewProto_getInt16([byteOffset = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(GetViewValue(v, byteOffset, littleEndian, 'Int16')); +} + +// 24.3.4.9 #sec-dataview.prototype.getint32 +function DataViewProto_getInt32([byteOffset = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(GetViewValue(v, byteOffset, littleEndian, 'Int32')); +} + +// 24.3.4.10 #sec-dataview.prototype.getuint8 +function DataViewProto_getUint8([byteOffset = Value.undefined], { thisValue }) { + const v = thisValue; + return Q(GetViewValue(v, byteOffset, Value.true, 'Uint8')); +} + +// 24.3.4.11 #sec-dataview.prototype.getuint16 +function DataViewProto_getUint16([byteOffset = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(GetViewValue(v, byteOffset, littleEndian, 'Uint16')); +} + +// 24.3.4.12 #sec-dataview.prototype.getuint32 +function DataViewProto_getUint32([byteOffset = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(GetViewValue(v, byteOffset, littleEndian, 'Uint32')); +} + +// #sec-dataview.prototype.setbigint64 +function DataViewProto_setBigInt64([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + // 1. Let v be the this value. + const v = thisValue; + // 2. If littleEndian is not present, set littleEndian to undefined. + if (littleEndian === undefined) { + littleEndian = Value.undefined; + } + // 3. Return ? SetViewValue(v, byteOffset, littleEndian, BigInt64, value). + return Q(SetViewValue(v, byteOffset, littleEndian, 'BigInt64', value)); +} + +// #sec-dataview.prototype.setbiguint64 +function DataViewProto_setBigUint64([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + // 1. Let v be the this value. + const v = thisValue; + // 2. If littleEndian is not present, set littleEndian to undefined. + if (littleEndian === undefined) { + littleEndian = Value.undefined; + } + // 3. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). + return Q(SetViewValue(v, byteOffset, littleEndian, 'BigUint64', value)); +} + +// 24.3.4.13 #sec-dataview.prototype.setfloat32 +function DataViewProto_setFloat32([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(SetViewValue(v, byteOffset, littleEndian, 'Float32', value)); +} + +// 24.3.4.14 #sec-dataview.prototype.setfloat64 +function DataViewProto_setFloat64([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(SetViewValue(v, byteOffset, littleEndian, 'Float64', value)); +} + +// 24.3.4.15 #sec-dataview.prototype.setint8 +function DataViewProto_setInt8([byteOffset = Value.undefined, value = Value.undefined], { thisValue }) { + const v = thisValue; + return Q(SetViewValue(v, byteOffset, Value.true, 'Int8', value)); +} + +// 24.3.4.16 #sec-dataview.prototype.setint16 +function DataViewProto_setInt16([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(SetViewValue(v, byteOffset, littleEndian, 'Int16', value)); +} + +// 24.3.4.17 #sec-dataview.prototype.setint32 +function DataViewProto_setInt32([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(SetViewValue(v, byteOffset, littleEndian, 'Int32', value)); +} + +// 24.3.4.18 #sec-dataview.prototype.setuint8 +function DataViewProto_setUint8([byteOffset = Value.undefined, value = Value.undefined], { thisValue }) { + const v = thisValue; + return Q(SetViewValue(v, byteOffset, Value.true, 'Uint8', value)); +} + +// 24.3.4.19 #sec-dataview.prototype.setuint16 +function DataViewProto_setUint16([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(SetViewValue(v, byteOffset, littleEndian, 'Uint16', value)); +} + +// 24.3.4.20 #sec-dataview.prototype.setuint32 +function DataViewProto_setUint32([byteOffset = Value.undefined, value = Value.undefined, littleEndian], { thisValue }) { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(SetViewValue(v, byteOffset, littleEndian, 'Uint32', value)); +} + +export function BootstrapDataViewPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['buffer', [DataViewProto_buffer]], + ['byteLength', [DataViewProto_byteLength]], + ['byteOffset', [DataViewProto_byteOffset]], + ['getBigInt64', DataViewProto_getBigInt64, 1], + ['getBigUint64', DataViewProto_getBigUint64, 1], + ['getFloat32', DataViewProto_getFloat32, 1], + ['getFloat64', DataViewProto_getFloat64, 1], + ['getInt8', DataViewProto_getInt8, 1], + ['getInt16', DataViewProto_getInt16, 1], + ['getInt32', DataViewProto_getInt32, 1], + ['getUint8', DataViewProto_getUint8, 1], + ['getUint16', DataViewProto_getUint16, 1], + ['getUint32', DataViewProto_getUint32, 1], + ['setBigInt64', DataViewProto_setBigInt64, 2], + ['setBigUint64', DataViewProto_setBigUint64, 2], + ['setFloat32', DataViewProto_setFloat32, 2], + ['setFloat64', DataViewProto_setFloat64, 2], + ['setInt8', DataViewProto_setInt8, 2], + ['setInt16', DataViewProto_setInt16, 2], + ['setInt32', DataViewProto_setInt32, 2], + ['setUint8', DataViewProto_setUint8, 2], + ['setUint16', DataViewProto_setUint16, 2], + ['setUint32', DataViewProto_setUint32, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'DataView'); + + realmRec.Intrinsics['%DataView.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Date.mjs b/engine262/src/intrinsics/Date.mjs new file mode 100644 index 0000000..8366c6c --- /dev/null +++ b/engine262/src/intrinsics/Date.mjs @@ -0,0 +1,204 @@ +import { + Assert, + OrdinaryCreateFromConstructor, + ToPrimitive, + ToNumber, + ToInteger, + ToString, + MakeDate, + MakeDay, + MakeTime, + UTC, + TimeClip, +} from '../abstract-ops/all.mjs'; +import { Value, Type } from '../value.mjs'; +import { + AbruptCompletion, + Q, X, +} from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; +import { ToDateString, thisTimeValue } from './DatePrototype.mjs'; + +// #sec-date-constructor +function DateConstructor(args, { NewTarget }) { + const numberOfArgs = args.length; + if (numberOfArgs >= 2) { + // 20.3.2.1 #sec-date-year-month-date-hours-minutes-seconds-ms + const [year, month, date, hours, minutes, seconds, ms] = args; + Assert(numberOfArgs >= 2); + if (NewTarget === Value.undefined) { + const now = Date.now(); + return ToDateString(new Value(now)); + } else { + const y = Q(ToNumber(year)); + const m = Q(ToNumber(month)); + let dt; + if (date !== undefined) { + dt = Q(ToNumber(date)); + } else { + dt = new Value(1); + } + let h; + if (hours !== undefined) { + h = Q(ToNumber(hours)); + } else { + h = new Value(0); + } + let min; + if (minutes !== undefined) { + min = Q(ToNumber(minutes)); + } else { + min = new Value(0); + } + let s; + if (seconds !== undefined) { + s = Q(ToNumber(seconds)); + } else { + s = new Value(0); + } + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = new Value(0); + } + let yr; + if (y.isNaN()) { + yr = new Value(NaN); + } else { + const yi = X(ToInteger(y)).numberValue(); + if (yi >= 0 && yi <= 99) { + yr = new Value(1900 + yi); + } else { + yr = y; + } + } + const finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)); + const O = Q(OrdinaryCreateFromConstructor(NewTarget, '%Date.prototype%', ['DateValue'])); + O.DateValue = TimeClip(UTC(finalDate)); + return O; + } + } else if (numberOfArgs === 1) { + const [value] = args; + // 20.3.2.2 #sec-date-value + Assert(numberOfArgs === 1); + if (NewTarget === Value.undefined) { + const now = Date.now(); + return ToDateString(new Value(now)); + } else { + let tv; + if (Type(value) === 'Object' && 'DateValue' in value) { + tv = thisTimeValue(value); + } else { + const v = Q(ToPrimitive(value)); + if (Type(v) === 'String') { + // Assert: The next step never returns an abrupt completion because Type(v) is String. + tv = parseDate(v); + } else { + tv = Q(ToNumber(v)); + } + } + const O = Q(OrdinaryCreateFromConstructor(NewTarget, '%Date.prototype%', ['DateValue'])); + O.DateValue = TimeClip(tv); + return O; + } + } else { + // 20.3.2.3 #sec-date-constructor-date + Assert(numberOfArgs === 0); + if (NewTarget === Value.undefined) { + const now = Date.now(); + return ToDateString(new Value(now)); + } else { + const O = Q(OrdinaryCreateFromConstructor(NewTarget, '%Date.prototype%', ['DateValue'])); + O.DateValue = new Value(Date.now()); + return O; + } + } +} + +// 20.3.3.1 #sec-date.now +function Date_now() { + const now = Date.now(); + return new Value(now); +} + +// 20.3.3.2 #sec-date.parse +function Date_parse([string = Value.undefined]) { + const str = ToString(string); + if (str instanceof AbruptCompletion) { + return str; + } + return parseDate(str); +} + +// 20.3.3.4 #sec-date.utc +function Date_UTC([year = Value.undefined, month, date, hours, minutes, seconds, ms]) { + const y = Q(ToNumber(year)); + let m; + if (month !== undefined) { + m = Q(ToNumber(month)); + } else { + m = new Value(0); + } + let dt; + if (date !== undefined) { + dt = Q(ToNumber(date)); + } else { + dt = new Value(1); + } + let h; + if (hours !== undefined) { + h = Q(ToNumber(hours)); + } else { + h = new Value(0); + } + let min; + if (minutes !== undefined) { + min = Q(ToNumber(minutes)); + } else { + min = new Value(0); + } + let s; + if (seconds !== undefined) { + s = Q(ToNumber(seconds)); + } else { + s = new Value(0); + } + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = new Value(0); + } + + let yr; + if (y.isNaN()) { + yr = new Value(NaN); + } else { + const yi = X(ToInteger(y)).numberValue(); + if (yi >= 0 && yi <= 99) { + yr = new Value(1900 + yi); + } else { + yr = y; + } + } + + return TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli))); +} + +function parseDate(dateTimeString) { + // 20.3.1.15 #sec-date-time-string-format + // TODO: implement parsing without the host. + const parsed = Date.parse(dateTimeString.stringValue()); + return new Value(parsed); +} + +export function BootstrapDate(realmRec) { + const cons = BootstrapConstructor(realmRec, DateConstructor, 'Date', 7, realmRec.Intrinsics['%Date.prototype%'], [ + ['now', Date_now, 0], + ['parse', Date_parse, 1], + ['UTC', Date_UTC, 7], + ]); + + realmRec.Intrinsics['%Date%'] = cons; +} diff --git a/engine262/src/intrinsics/DatePrototype.mjs b/engine262/src/intrinsics/DatePrototype.mjs new file mode 100644 index 0000000..bda592b --- /dev/null +++ b/engine262/src/intrinsics/DatePrototype.mjs @@ -0,0 +1,705 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + DateFromTime, + Day, + HourFromTime, + Invoke, + LocalTime, + LocalTZA, + MakeDate, + MakeDay, + MakeTime, + MinFromTime, + MonthFromTime, + msFromTime, + msPerMinute, + OrdinaryToPrimitive, + SecFromTime, + TimeClip, + TimeWithinDay, + ToNumber, + ToPrimitive, + ToObject, + UTC, + WeekDay, + YearFromTime, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { StringPad } from '../runtime-semantics/all.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + + +export function thisTimeValue(value) { + if (Type(value) === 'Object' && 'DateValue' in value) { + return value.DateValue; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', value); +} + +// 20.3.4.2 #sec-date.prototype.getdate +function DateProto_getDate(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return DateFromTime(LocalTime(t)); +} + +// 20.3.4.3 #sec-date.prototype.getday +function DateProto_getDay(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return WeekDay(LocalTime(t)); +} + +// 20.3.4.4 #sec-date.prototype.getfullyear +function DateProto_getFullYear(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return YearFromTime(LocalTime(t)); +} + +// 20.3.4.5 #sec-date.prototype.gethours +function DateProto_getHours(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return HourFromTime(LocalTime(t)); +} + +// 20.3.4.6 #sec-date.prototype.getmilliseconds +function DateProto_getMilliseconds(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return msFromTime(LocalTime(t)); +} + +// 20.3.4.7 #sec-date.prototype.getminutes +function DateProto_getMinutes(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return MinFromTime(LocalTime(t)); +} + +// 20.3.4.8 #sec-date.prototype.getmonth +function DateProto_getMonth(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return MonthFromTime(LocalTime(t)); +} + +// 20.3.4.9 #sec-date.prototype.getseconds +function DateProto_getSeconds(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return SecFromTime(LocalTime(t)); +} + +// 20.3.4.10 #sec-date.prototype.gettime +function DateProto_getTime(args, { thisValue }) { + return Q(thisTimeValue(thisValue)); +} + +// 20.3.4.11 #sec-date.prototype.gettimezoneoffset +function DateProto_getTimezoneOffset(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return new Value((t.numberValue() - LocalTime(t).numberValue()) / msPerMinute); +} + +// 20.3.4.12 #sec-date.prototype.getutcdate +function DateProto_getUTCDate(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return DateFromTime(t); +} + +// 20.3.4.13 #sec-date.prototype.getutcday +function DateProto_getUTCDay(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return WeekDay(t); +} + +// 20.3.4.14 #sec-date.prototype.getutcfullyear +function DateProto_getUTCFullYear(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return YearFromTime(t); +} + +// 20.3.4.15 #sec-date.prototype.getutchours +function DateProto_getUTCHours(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return HourFromTime(t); +} + +// 20.3.4.16 #sec-date.prototype.getutcmilliseconds +function DateProto_getUTCMilliseconds(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return msFromTime(t); +} + +// 20.3.4.17 #sec-date.prototype.getutcminutes +function DateProto_getUTCMinutes(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return MinFromTime(t); +} + +// 20.3.4.18 #sec-date.prototype.getutcmonth +function DateProto_getUTCMonth(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return MonthFromTime(t); +} + +// 20.3.4.19 #sec-date.prototype.getutcseconds +function DateProto_getUTCSeconds(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return new Value(NaN); + } + return SecFromTime(t); +} + +// 20.3.4.20 #sec-date.prototype.setdate +function DateProto_setDate([date = Value.undefined], { thisValue }) { + const t = LocalTime(Q(thisTimeValue(thisValue))); + const dt = Q(ToNumber(date)); + const newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)); + const u = TimeClip(UTC(newDate)); + thisValue.DateValue = u; + return u; +} + +// 20.3.4.21 #sec-date.prototype.setfullyear +function DateProto_setFullYear([year = Value.undefined, month, date], { thisValue }) { + let t = Q(thisTimeValue(thisValue)); + t = t.isNaN() ? new Value(0) : LocalTime(t); + const y = Q(ToNumber(year)); + let m; + if (month !== undefined) { + m = Q(ToNumber(month)); + } else { + m = MonthFromTime(t); + } + let dt; + if (date !== undefined) { + dt = Q(ToNumber(date)); + } else { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)); + const u = TimeClip(UTC(newDate)); + thisValue.DateValue = u; + return u; +} + +// 20.3.4.22 #sec-date.prototype.sethours +function DateProto_setHours([hour = Value.undefined, min, sec, ms], { thisValue }) { + const t = LocalTime(Q(thisTimeValue(thisValue))); + const h = Q(ToNumber(hour)); + let m; + if (min !== undefined) { + m = Q(ToNumber(min)); + } else { + m = MinFromTime(t); + } + let s; + if (sec !== undefined) { + s = Q(ToNumber(sec)); + } else { + s = SecFromTime(t); + } + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(h, m, s, milli)); + const u = TimeClip(UTC(date)); + thisValue.DateValue = u; + return u; +} + +// 20.3.4.23 #sec-date.prototype.setmilliseconds +function DateProto_setMilliseconds([ms = Value.undefined], { thisValue }) { + const t = LocalTime(Q(thisTimeValue(thisValue))); + ms = Q(ToNumber(ms)); + const time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms); + const u = TimeClip(UTC(MakeDate(Day(t), time))); + thisValue.DateValue = u; + return u; +} + +// 20.3.4.24 #sec-date.prototype.setminutes +function DateProto_setMinutes([min = Value.undefined, sec, ms], { thisValue }) { + // 1. Let t be LocalTime(? thisTimeValue(this value)). + const t = LocalTime(Q(thisTimeValue(thisValue))); + // 2. Let m be ? ToNumber(min). + const m = Q(ToNumber(min)); + let s; + // 3. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec). + if (sec !== undefined) { + s = Q(ToNumber(sec)); + } else { + s = SecFromTime(t); + } + let milli; + // 4. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms). + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = msFromTime(t); + } + // 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)). + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + // 6. Let u be TimeClip(UTC(date)). + const u = TimeClip(UTC(date)); + // 7. Set the [[DateValue]] internal slot of this Date object to u. + thisValue.DateValue = u; + // 8. Return u. + return u; +} + +// 20.3.4.25 #sec-date.prototype.setmonth +function DateProto_setMonth([month = Value.undefined, date], { thisValue }) { + const t = LocalTime(Q(thisTimeValue(thisValue))); + const m = Q(ToNumber(month)); + let dt; + if (date !== undefined) { + dt = Q(ToNumber(date)); + } else { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)); + const u = TimeClip(UTC(newDate)); + thisValue.DateValue = u; + return u; +} + +// 20.3.4.26 #sec-date.prototype.setseconds +function DateProto_setSeconds([sec = Value.undefined, ms], { thisValue }) { + const t = LocalTime(Q(thisTimeValue(thisValue))); + const s = Q(ToNumber(sec)); + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + const u = TimeClip(UTC(date)); + thisValue.DateValue = u; + return u; +} + +// 20.3.4.27 #sec-date.prototype.settime +function DateProto_setTime([time = Value.undefined], { thisValue }) { + Q(thisTimeValue(thisValue)); + const t = Q(ToNumber(time)); + const v = TimeClip(t); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.28 #sec-date.prototype.setutcdate +function DateProto_setUTCDate([date = Value.undefined], { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + const dt = Q(ToNumber(date)); + const newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)); + const v = TimeClip(newDate); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.29 #sec-date.prototype.setutcfullyear +function DateProto_setUTCFullYear([year = Value.undefined, month, date], { thisValue }) { + let t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + t = new Value(0); + } + const y = Q(ToNumber(year)); + let m; + if (month !== undefined) { + m = Q(ToNumber(month)); + } else { + m = MonthFromTime(t); + } + let dt; + if (date !== undefined) { + dt = Q(ToNumber(date)); + } else { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)); + const v = TimeClip(newDate); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.30 #sec-date.prototype.setutchours +function DateProto_setUTCHours([hour = Value.undefined, min, sec, ms], { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + const h = Q(ToNumber(hour)); + let m; + if (min !== undefined) { + m = Q(ToNumber(min)); + } else { + m = MinFromTime(t); + } + let s; + if (sec !== undefined) { + s = Q(ToNumber(sec)); + } else { + s = SecFromTime(t); + } + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = msFromTime(t); + } + const newDate = MakeDate(Day(t), MakeTime(h, m, s, milli)); + const v = TimeClip(newDate); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.31 #sec-date.prototype.setutcmilliseconds +function DateProto_setUTCMilliseconds([ms = Value.undefined], { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + const milli = Q(ToNumber(ms)); + const time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); + const v = TimeClip(MakeDate(Day(t), time)); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.32 #sec-date.prototype.setutcminutes +function DateProto_setUTCMinutes([min = Value.undefined, sec, ms], { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + const m = Q(ToNumber(min)); + let s; + if (sec !== undefined) { + s = Q(ToNumber(sec)); + } else { + s = SecFromTime(t); + } + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + const v = TimeClip(date); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.33 #sec-date.prototype.setutcmonth +function DateProto_setUTCMonth([month = Value.undefined, date], { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + const m = Q(ToNumber(month)); + let dt; + if (date !== undefined) { + dt = Q(ToNumber(date)); + } else { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)); + const v = TimeClip(newDate); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.34 #sec-date.prototype.setutcseconds +function DateProto_setUTCSeconds([sec = Value.undefined, ms], { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + const s = Q(ToNumber(sec)); + let milli; + if (ms !== undefined) { + milli = Q(ToNumber(ms)); + } else { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + const v = TimeClip(date); + thisValue.DateValue = v; + return v; +} + +// 20.3.4.35 #sec-date.prototype.todatestring +function DateProto_toDateString(args, { thisValue }) { + const O = thisValue; + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + const tv = Q(thisTimeValue(O)); + if (tv.isNaN()) { + return new Value('Invalid Date'); + } + const t = LocalTime(tv); + return DateString(t); +} + +// 20.3.4.36 #sec-date.prototype.toisostring +function DateProto_toISOString(args, { thisValue }) { + const t = Q(thisTimeValue(thisValue)); + if (!Number.isFinite(t.numberValue())) { + return surroundingAgent.Throw('RangeError', 'DateInvalidTime'); + } + const year = YearFromTime(t).numberValue(); + const month = MonthFromTime(t).numberValue() + 1; + const date = DateFromTime(t).numberValue(); + const hour = HourFromTime(t).numberValue(); + const min = MinFromTime(t).numberValue(); + const sec = SecFromTime(t).numberValue(); + const ms = msFromTime(t).numberValue(); + + // TODO: figure out if there can be invalid years. + let YYYY = String(year); + if (year < 0 || year > 9999) { + YYYY = year < 0 ? `${String(year).padStart(6, '0')}` : `+${String(year).padStart(6, '0')}`; + } + const MM = String(month).padStart(2, '0'); + const DD = String(date).padStart(2, '0'); + const HH = String(hour).padStart(2, '0'); + const mm = String(min).padStart(2, '0'); + const ss = String(sec).padStart(2, '0'); + const sss = String(ms).padStart(3, '0'); + const format = `${YYYY}-${MM}-${DD}T${HH}:${mm}:${ss}.${sss}Z`; + return new Value(format); +} + +// 20.3.4.37 #sec-date.prototype.tojson +function DateProto_toJSON(args, { thisValue }) { + const O = Q(ToObject(thisValue)); + const tv = Q(ToPrimitive(O, 'Number')); + if (Type(tv) === 'Number' && !Number.isFinite(tv.numberValue())) { + return Value.null; + } + return Q(Invoke(O, new Value('toISOString'))); +} + +// 20.3.4.38 #sec-date.prototype.tolocaledatestring +function DateProto_toLocaleDateString() { + // TODO: implement this function. + return surroundingAgent.Throw('Error', 'Raw', 'Date.prototype.toLocaleDateString is not implemented'); +} + +// 20.3.4.39 #sec-date.prototype.tolocalestring +function DateProto_toLocaleString() { + // TODO: implement this function. + return surroundingAgent.Throw('Error', 'Raw', 'Date.prototype.toLocaleString is not implemented'); +} + +// 20.3.4.40 #sec-date.prototype.tolocaletimestring +function DateProto_toLocaleTimeString() { + // TODO: implement this function. + return surroundingAgent.Throw('Error', 'Raw', 'Date.prototype.toLocaleTimeString is not implemented'); +} + +// 20.3.4.41 #sec-date.prototype.tostring +function DateProto_toString(args, { thisValue }) { + const tv = Q(thisTimeValue(thisValue)); + return ToDateString(tv); +} + +// 20.3.4.41.1 #sec-timestring +function TimeString(tv) { + Assert(Type(tv) === 'Number'); + Assert(!tv.isNaN()); + const hour = String(HourFromTime(tv).numberValue()).padStart(2, '0'); + const minute = String(MinFromTime(tv).numberValue()).padStart(2, '0'); + const second = String(SecFromTime(tv).numberValue()).padStart(2, '0'); + return new Value(`${hour}:${minute}:${second} GMT`); +} + +// Table 46 #sec-todatestring-day-names +const daysOfTheWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; +// Table 47 #sec-todatestring-month-names +const monthsOfTheYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +// 20.3.4.41.2 #sec-datestring +function DateString(tv) { + Assert(Type(tv) === 'Number'); + Assert(!tv.isNaN()); + const weekday = daysOfTheWeek[WeekDay(tv).numberValue()]; + const month = monthsOfTheYear[MonthFromTime(tv).numberValue()]; + const day = String(DateFromTime(tv).numberValue()).padStart(2, '0'); + const yv = YearFromTime(tv).numberValue(); + const yearSign = yv >= 0 ? '' : '-'; + const year = new Value(String(Math.abs(yv))); + const paddedYear = X(StringPad(year, new Value(4), new Value('0'), 'start')).stringValue(); + return new Value(`${weekday} ${month} ${day} ${yearSign}${paddedYear}`); +} + +// 20.3.4.41.3 #sec-timezoneestring +export function TimeZoneString(tv) { + Assert(Type(tv) === 'Number'); + Assert(!tv.isNaN()); + const offset = LocalTZA(tv, true); + const offsetSign = offset >= 0 ? '+' : '-'; + const offsetMin = String(MinFromTime(new Value(Math.abs(offset))).numberValue()).padStart(2, '0'); + const offsetHour = String(HourFromTime(new Value(Math.abs(offset))).numberValue()).padStart(2, '0'); + const tzName = ''; + return new Value(`${offsetSign}${offsetHour}${offsetMin}${tzName}`); +} + +// 20.3.4.41.4 #sec-todatestring +export function ToDateString(tv) { + Assert(Type(tv) === 'Number'); + if (tv.isNaN()) { + return new Value('Invalid Date'); + } + const t = LocalTime(tv); + return new Value(`${DateString(t).stringValue()} ${TimeString(t).stringValue()}${TimeZoneString(t).stringValue()}`); +} + +// 20.3.4.42 #sec-date.prototype.totimestring +function DateProto_toTimeString(args, { thisValue }) { + const O = thisValue; + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + const tv = Q(thisTimeValue(O)); + if (tv.isNaN()) { + return new Value('Invalid Date'); + } + const t = LocalTime(tv); + return new Value(`${TimeString(t).stringValue()}${TimeZoneString(tv).stringValue()}`); +} + +// 20.3.4.43 #sec-date.prototype.toutcstring +function DateProto_toUTCString(args, { thisValue }) { + const O = thisValue; + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + const tv = Q(thisTimeValue(O)); + if (tv.isNaN()) { + return new Value('Invalid Date'); + } + const weekday = daysOfTheWeek[WeekDay(tv).numberValue()]; + const month = monthsOfTheYear[MonthFromTime(tv).numberValue()]; + const day = String(DateFromTime(tv).numberValue()).padStart(2, '0'); + const yv = YearFromTime(tv).numberValue(); + const yearSign = yv >= 0 ? '' : '-'; + const year = new Value(String(Math.abs(yv))); + const paddedYear = X(StringPad(year, new Value(4), new Value('0'), 'start')).stringValue(); + return new Value(`${weekday}, ${day} ${month} ${yearSign}${paddedYear} ${TimeString(tv).stringValue()}`); +} + +// 20.3.4.44 #sec-date.prototype.valueof +function DateProto_valueOf(args, { thisValue }) { + return Q(thisTimeValue(thisValue)); +} + +// 20.3.4.45 #sec-date.prototype-@@toprimitive +function DateProto_toPrimitive([hint = Value.undefined], { thisValue }) { + const O = thisValue; + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + let tryFirst; + if (Type(hint) === 'String' && (hint.stringValue() === 'string' || hint.stringValue() === 'default')) { + tryFirst = new Value('string'); + } else if (Type(hint) === 'String' && hint.stringValue() === 'number') { + tryFirst = new Value('number'); + } else { + return surroundingAgent.Throw('TypeError', 'InvalidHint', hint); + } + return Q(OrdinaryToPrimitive(O, tryFirst)); +} + +export function BootstrapDatePrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['getDate', DateProto_getDate, 0], + ['getDay', DateProto_getDay, 0], + ['getFullYear', DateProto_getFullYear, 0], + ['getHours', DateProto_getHours, 0], + ['getMilliseconds', DateProto_getMilliseconds, 0], + ['getMinutes', DateProto_getMinutes, 0], + ['getMonth', DateProto_getMonth, 0], + ['getSeconds', DateProto_getSeconds, 0], + ['getTime', DateProto_getTime, 0], + ['getTimezoneOffset', DateProto_getTimezoneOffset, 0], + ['getUTCDate', DateProto_getUTCDate, 0], + ['getUTCDay', DateProto_getUTCDay, 0], + ['getUTCFullYear', DateProto_getUTCFullYear, 0], + ['getUTCHours', DateProto_getUTCHours, 0], + ['getUTCMilliseconds', DateProto_getUTCMilliseconds, 0], + ['getUTCMinutes', DateProto_getUTCMinutes, 0], + ['getUTCMonth', DateProto_getUTCMonth, 0], + ['getUTCSeconds', DateProto_getUTCSeconds, 0], + ['setDate', DateProto_setDate, 1], + ['setFullYear', DateProto_setFullYear, 3], + ['setHours', DateProto_setHours, 4], + ['setMilliseconds', DateProto_setMilliseconds, 1], + ['setMinutes', DateProto_setMinutes, 3], + ['setMonth', DateProto_setMonth, 2], + ['setSeconds', DateProto_setSeconds, 2], + ['setTime', DateProto_setTime, 1], + ['setUTCDate', DateProto_setUTCDate, 1], + ['setUTCFullYear', DateProto_setUTCFullYear, 3], + ['setUTCHours', DateProto_setUTCHours, 4], + ['setUTCMilliseconds', DateProto_setUTCMilliseconds, 1], + ['setUTCMinutes', DateProto_setUTCMinutes, 3], + ['setUTCMonth', DateProto_setUTCMonth, 2], + ['setUTCSeconds', DateProto_setUTCSeconds, 2], + ['toDateString', DateProto_toDateString, 0], + ['toISOString', DateProto_toISOString, 0], + ['toJSON', DateProto_toJSON, 1], + ['toLocaleDateString', DateProto_toLocaleDateString, 0], + ['toLocaleString', DateProto_toLocaleString, 0], + ['toLocaleTimeString', DateProto_toLocaleTimeString, 0], + ['toString', DateProto_toString, 0], + ['toTimeString', DateProto_toTimeString, 0], + ['toUTCString', DateProto_toUTCString, 0], + ['valueOf', DateProto_valueOf, 0], + [wellKnownSymbols.toPrimitive, DateProto_toPrimitive, 1, { Writable: Value.false, Enumerable: Value.false, Configurable: Value.true }], + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%Date.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Error.mjs b/engine262/src/intrinsics/Error.mjs new file mode 100644 index 0000000..8257561 --- /dev/null +++ b/engine262/src/intrinsics/Error.mjs @@ -0,0 +1,51 @@ +import { + DefinePropertyOrThrow, + OrdinaryCreateFromConstructor, + ToString, +} from '../abstract-ops/all.mjs'; +import { + Descriptor, + Value, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { captureStack } from '../helpers.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-error-constructor +function ErrorConstructor([message = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let newTarget; + if (NewTarget === Value.undefined) { + newTarget = surroundingAgent.activeFunctionObject; + } else { + newTarget = NewTarget; + } + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »). + const O = Q(OrdinaryCreateFromConstructor(newTarget, '%Error.prototype%', ['ErrorData'])); + // 3. If message is not undefined, then + if (message !== Value.undefined) { + // a. Let msg be ? ToString(message). + const msg = Q(ToString(message)); + // b. Let msgDesc be the PropertyDescriptor { [[Value]]: msg, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }. + const msgDesc = Descriptor({ + Value: msg, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }); + // c. Perform ! DefinePropertyOrThrow(O, "message", msgDesc). + X(DefinePropertyOrThrow(O, new Value('message'), msgDesc)); + } + + X(captureStack(O)); // NON-SPEC + + // 4. Return O. + return O; +} + +export function BootstrapError(realmRec) { + const error = BootstrapConstructor(realmRec, ErrorConstructor, 'Error', 1, realmRec.Intrinsics['%Error.prototype%'], []); + + realmRec.Intrinsics['%Error%'] = error; +} diff --git a/engine262/src/intrinsics/ErrorPrototype.mjs b/engine262/src/intrinsics/ErrorPrototype.mjs new file mode 100644 index 0000000..72a200c --- /dev/null +++ b/engine262/src/intrinsics/ErrorPrototype.mjs @@ -0,0 +1,59 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Type, + Value, +} from '../value.mjs'; +import { + Get, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-error.prototype.tostring +function ErrorProto_toString(args, { thisValue }) { + // 1. Let O be this value. + const O = thisValue; + // 2. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let name be ? Get(O, "name"). + let name = Q(Get(O, new Value('name'))); + // 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name). + if (name === Value.undefined) { + name = new Value('Error'); + } else { + name = Q(ToString(name)); + } + // 5. Let msg be ? Get(O, "message"). + let msg = Q(Get(O, new Value('message'))); + // 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg). + if (msg === Value.undefined) { + msg = new Value(''); + } else { + msg = Q(ToString(msg)); + } + // 7. If name is the empty String, return msg. + if (name.stringValue() === '') { + return msg; + } + // 8. If msg is the empty String, return name. + if (msg.stringValue() === '') { + return name; + } + // 9. Return the string-concatenation of name, the code unit 0x003A (COLON), the code unit 0x0020 (SPACE), and msg. + return new Value(`${name.stringValue()}: ${msg.stringValue()}`); +} + +export function BootstrapErrorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['toString', ErrorProto_toString, 0], + ['message', new Value('')], + ['name', new Value('Error')], + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%Error.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/FinalizationRegistry.mjs b/engine262/src/intrinsics/FinalizationRegistry.mjs new file mode 100644 index 0000000..24d2cff --- /dev/null +++ b/engine262/src/intrinsics/FinalizationRegistry.mjs @@ -0,0 +1,42 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { IsCallable, OrdinaryCreateFromConstructor } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-finalization-registry-cleanup-callback +function FinalizationRegistryConstructor([cleanupCallback = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', 'FinalizationRegistry'); + } + // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception. + if (IsCallable(cleanupCallback) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', cleanupCallback); + } + // 3. Let finalizationGroup be ? OrdinaryCreateFromConstructor(NewTarget, "%FinalizationRegistryPrototype%", « [[Realm]], [[CleanupCallback]], [[Cells]] »). + const finalizationGroup = Q(OrdinaryCreateFromConstructor(NewTarget, '%FinalizationRegistry.prototype%', [ + 'Realm', + 'CleanupCallback', + 'Cells', + ])); + // 4. Let fn be the active function object. + const fn = surroundingAgent.activeFunctionObject; + // 5. Set finalizationGroup.[[Realm]] to fn.[[Realm]]. + finalizationGroup.Realm = fn.Realm; + // 6. Set finalizationGroup.[[CleanupCallback]] to cleanupCallback. + finalizationGroup.CleanupCallback = cleanupCallback; + // 7. Set finalizationGroup.[[Cells]] to be an empty List. + finalizationGroup.Cells = []; + // 8. Return finalizationGroup. + return finalizationGroup; +} + +export function BootstrapFinalizationRegistry(realmRec) { + const cons = BootstrapConstructor( + realmRec, FinalizationRegistryConstructor, 'FinalizationRegistry', 1, + realmRec.Intrinsics['%FinalizationRegistry.prototype%'], [], + ); + + realmRec.Intrinsics['%FinalizationRegistry%'] = cons; +} diff --git a/engine262/src/intrinsics/FinalizationRegistryPrototype.mjs b/engine262/src/intrinsics/FinalizationRegistryPrototype.mjs new file mode 100644 index 0000000..722e9ec --- /dev/null +++ b/engine262/src/intrinsics/FinalizationRegistryPrototype.mjs @@ -0,0 +1,101 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Type } from '../value.mjs'; +import { + CleanupFinalizationRegistry, + IsCallable, + RequireInternalSlot, + SameValue, +} from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-finalization-registry.prototype.cleanupSome +function FinalizationRegistryProto_cleanupSome([callback = Value.undefined], { thisValue }) { + // 1. Let finalizationRegistry be the this value. + const finalizationRegistry = thisValue; + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + Q(RequireInternalSlot(finalizationRegistry, 'Cells')); + // 3. If callback is present and IsCallable(callback) is false, throw a TypeError exception. + if (callback !== Value.undefined && IsCallable(callback) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callback); + } + // 4. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback). + Q(CleanupFinalizationRegistry(finalizationRegistry, callback)); + // 5. Return *undefined*. + return Value.undefined; +} + +// #sec-finalization-registry.prototype.register +function FinalizationRegistryProto_register([target = Value.undefined, heldValue = Value.undefined, unregisterToken = Value.undefined], { thisValue }) { + // 1. Let finalizationRegistry be the this value. + const finalizationRegistry = thisValue; + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + Q(RequireInternalSlot(finalizationRegistry, 'Cells')); + // 3. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 4. If SameValue(target, heldValue), throw a TypeError exception. + if (SameValue(target, heldValue) === Value.true) { + return surroundingAgent.Throw('TypeError', 'TargetMatchesHeldValue', heldValue); + } + // 5. If Type(unregisterToken) is not Object, + if (Type(unregisterToken) !== 'Object') { + // a. If unregisterToken is not undefined, throw a TypeError exception. + if (unregisterToken !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', unregisterToken); + } + // b. Set unregisterToken to empty. + unregisterToken = undefined; + } + // 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }. + const cell = { + WeakRefTarget: target, + HeldValue: heldValue, + UnregisterToken: unregisterToken, + }; + // 7. Append cell to finalizationRegistry.[[Cells]]. + finalizationRegistry.Cells.push(cell); + // 8. Return undefined. + return Value.undefined; +} + +// #sec-finalization-registry.prototype.unregister +function FinalizationRegistryProto_unregister([unregisterToken = Value.undefined], { thisValue }) { + // 1. Let finalizationRegistry be the this value. + const finalizationRegistry = thisValue; + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + Q(RequireInternalSlot(finalizationRegistry, 'Cells')); + // 3. If Type(unregisterToken) is not Object, throw a TypeError exception. + if (Type(unregisterToken) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', unregisterToken); + } + // 4. Let removed be false. + let removed = Value.false; + // 5. For each Record { [[WeakRefTarget]], [[HeldValue]], [[UnregisterToken]] } cell that is an element of finalizationRegistry.[[Cells]], do + finalizationRegistry.Cells = finalizationRegistry.Cells.filter((cell) => { + let r = true; + // a. If cell.[[UnregisterToken]] is not empty and SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then + if (cell.UnregisterToken !== undefined && SameValue(cell.UnregisterToken, unregisterToken) === Value.true) { + // i. Remove cell from finalizationRegistry.Cells. + r = false; + // ii. Set removed to true. + removed = Value.true; + } + return r; + }); + // 6. Return removed. + return removed; +} + +export function BootstrapFinalizationRegistryPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + surroundingAgent.feature('cleanup-some') + ? ['cleanupSome', FinalizationRegistryProto_cleanupSome, 0] + : undefined, + ['register', FinalizationRegistryProto_register, 2], + ['unregister', FinalizationRegistryProto_unregister, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'FinalizationRegistry'); + + realmRec.Intrinsics['%FinalizationRegistry.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/ForInIteratorPrototype.mjs b/engine262/src/intrinsics/ForInIteratorPrototype.mjs new file mode 100644 index 0000000..8ace2ec --- /dev/null +++ b/engine262/src/intrinsics/ForInIteratorPrototype.mjs @@ -0,0 +1,104 @@ +import { Value, Type } from '../value.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + SameValue, + OrdinaryObjectCreate, + CreateIterResultObject, +} from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-createforiniterator +export function CreateForInIterator(object) { + // 1. Assert: Type(object) is Object. + Assert(Type(object) === 'Object'); + // 2. Let iterator be ObjectCreate(%ForInIteratorPrototype%, « [[Object]], [[ObjectWasVisited]], [[VisitedKeys]], [[RemainingKeys]] »). + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%ForInIteratorPrototype%'), [ + 'Object', + 'ObjectWasVisited', + 'VisitedKeys', + 'RemainingKeys', + ]); + // 3. Set iterator.[[Object]] to object. + iterator.Object = object; + // 4. Set iterator.[[ObjectWasVisited]] to false. + iterator.ObjectWasVisited = Value.false; + // 5. Set iterator.[[VisitedKeys]] to a new empty List. + iterator.VisitedKeys = []; + // 6. Set iterator.[[RemainingKeys]] to a new empty List. + iterator.RemainingKeys = []; + // 7. Return iterator. + return iterator; +} + +// #sec-%foriniteratorprototype%.next +function ForInIteratorPrototype_next(args, { thisValue }) { + // 1. Let O be this value. + const O = thisValue; + // 2. Assert: Type(O) is Object. + Assert(Type(O) === 'Object'); + // 3. Assert: O has all the internal slot sof a For-In Iterator Instance. + Assert('Object' in O && 'ObjectWasVisited' in O && 'VisitedKeys' in O && 'RemainingKeys in O'); + // 4. Let object be O.[[Object]]. + let object = O.Object; + // 5. Let visited be O.[[VisitedKeys]]. + const visited = O.VisitedKeys; + // 6. Let remaining be O.[[RemainingKeys]]. + const remaining = O.RemainingKeys; + // 7. Repeat, + while (true) { + // a. If O.[[ObjectWasVisited]] is false, then + if (O.ObjectWasVisited === Value.false) { + // i. Let keys be ? object.[[OwnPropertyKeys]](). + const keys = Q(object.OwnPropertyKeys()); + // ii. for each key of keys in List order, do + for (const key of keys) { + // 1. If Type(key) is String, then + if (Type(key) === 'String') { + // a. Append key to remaining. + remaining.push(key); + } + } + // iii. Set O.ObjectWasVisited to true. + O.ObjectWasVisited = Value.true; + } + // b. Repeat, while remaining is not empty, + while (remaining.length > 0) { + // i. Remove the first element from remaining and let r be the value of the element. + const r = remaining.shift(); + // ii. If there does not exist an element v of visisted such that SameValue(r, v) is true, then + if (!visited.find((v) => SameValue(r, v) === Value.true)) { + // 1. Let desc be ? object.[[GetOwnProperty]](r). + const desc = Q(object.GetOwnProperty(r)); + // 2. If desc is not undefined, then, + if (desc !== Value.undefined) { + // a. Append r to visited. + visited.push(r); + // b. If desc.[[Enumerable]] is true, return CreateIterResultObject(r, false). + if (desc.Enumerable === Value.true) { + return CreateIterResultObject(r, Value.false); + } + } + } + } + // c. Set object to ? object.[[GetPrototypeOf]](). + object = Q(object.GetPrototypeOf()); + // d. Set O.Object to object. + O.Object = object; + // e. Set O.ObjectWasVisited to false. + O.ObjectWasVisited = Value.false; + // f. If object is null, return CreateIterResultObject(undefined, true). + if (object === Value.null) { + return CreateIterResultObject(Value.undefined, Value.true); + } + } +} + +export function BootstrapForInIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', ForInIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%IteratorPrototype%']); + + realmRec.Intrinsics['%ForInIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Function.mjs b/engine262/src/intrinsics/Function.mjs new file mode 100644 index 0000000..652673c --- /dev/null +++ b/engine262/src/intrinsics/Function.mjs @@ -0,0 +1,18 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Q } from '../completion.mjs'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-function-p1-p2-pn-body +function FunctionConstructor(args, { NewTarget }) { + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject; + // 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]]. + // 3. Return ? CreateDynamicFunction(C, NewTarget, normal, args). + return Q(CreateDynamicFunction(C, NewTarget, 'normal', args)); +} + +export function BootstrapFunction(realmRec) { + const cons = BootstrapConstructor(realmRec, FunctionConstructor, 'Function', 1, realmRec.Intrinsics['%Function.prototype%'], []); + realmRec.Intrinsics['%Function%'] = cons; +} diff --git a/engine262/src/intrinsics/FunctionPrototype.mjs b/engine262/src/intrinsics/FunctionPrototype.mjs new file mode 100644 index 0000000..23bcd3e --- /dev/null +++ b/engine262/src/intrinsics/FunctionPrototype.mjs @@ -0,0 +1,240 @@ +import { + surroundingAgent, + HostHasSourceTextAvailable, +} from '../engine.mjs'; +import { + Assert, + Call, + Construct, + CreateListFromArrayLike, + Get, + HasOwnProperty, + IsCallable, + IsConstructor, + OrdinaryHasInstance, + PrepareForTailCall, + SameValue, + SetFunctionLength, + SetFunctionName, + ToInteger, + CreateBuiltinFunction, + MakeBasicObject, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { assignProps } from './Bootstrap.mjs'; + +// #sec-properties-of-the-function-prototype-object +function FunctionProto(_args, _meta) { + // * accepts any arguments and returns undefined when invoked. + return Value.undefined; +} + +// #sec-function.prototype.apply +function FunctionProto_apply([thisArg = Value.undefined, argArray = Value.undefined], { thisValue }) { + // 1. Let func be the this value. + const func = thisValue; + // 2. If IsCallable(func) is false, throw a TypeError exception. + if (IsCallable(func) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); + } + // 3. If argArray is undefined or null, then + if (argArray === Value.undefined || argArray === Value.null) { + // a. Perform PrepareForTailCall(). + PrepareForTailCall(); + // b. Return ? Call(func, thisArg). + return Q(Call(func, thisArg)); + } + // 4. Let argList be ? CreateListFromArrayLike(argArray). + const argList = Q(CreateListFromArrayLike(argArray)); + // 5. Perform PrepareForTailCall(). + PrepareForTailCall(); + // 6. Return ? Call(func, thisArg, argList). + return Q(Call(func, thisArg, argList)); +} + +function BoundFunctionExoticObjectCall(thisArgument, argumentsList) { + const F = this; + + const target = F.BoundTargetFunction; + const boundThis = F.BoundThis; + const boundArgs = F.BoundArguments; + const args = [...boundArgs, ...argumentsList]; + return Q(Call(target, boundThis, args)); +} + +function BoundFunctionExoticObjectConstruct(argumentsList, newTarget) { + const F = this; + + const target = F.BoundTargetFunction; + Assert(IsConstructor(target) === Value.true); + const boundArgs = F.BoundArguments; + const args = [...boundArgs, ...argumentsList]; + if (SameValue(F, newTarget) === Value.true) { + newTarget = target; + } + return Q(Construct(target, args, newTarget)); +} + +// #sec-boundfunctioncreate +function BoundFunctionCreate(targetFunction, boundThis, boundArgs) { + // 1. Assert: Type(targetFunction) is Object. + Assert(Type(targetFunction) === 'Object'); + // 2. Let proto be ? targetFunction.[[GetPrototypeOf]](). + const proto = Q(targetFunction.GetPrototypeOf()); + // 3. Let internalSlotsList be the internal slots listed in Table 30, plus [[Prototype]] and [[Extensible]]. + const internalSlotsList = [ + 'BoundTargetFunction', + 'BoundThis', + 'BoundArguments', + 'Prototype', + 'Extensible', + ]; + // 4. Let obj be ! MakeBasicObject(internalSlotsList). + const obj = X(MakeBasicObject(internalSlotsList)); + // 5. Set obj.[[Prototype]] to proto. + obj.Prototype = proto; + // 6. Set obj.[[Call]] as described in 9.4.1.1. + obj.Call = BoundFunctionExoticObjectCall; + // 7. If IsConstructor(targetFunction) is true, then + if (IsConstructor(targetFunction) === Value.true) { + // a. Set obj.[[Construct]] as described in 9.4.1.2. + obj.Construct = BoundFunctionExoticObjectConstruct; + } + // 8. Set obj.[[BoundTargetFunction]] to targetFunction. + obj.BoundTargetFunction = targetFunction; + // 9. Set obj.[[BoundThis]] to boundThis. + obj.BoundThis = boundThis; + // 10. Set obj.[[BoundArguments]] to boundArguments. + obj.BoundArguments = boundArgs; + // 11. Return obj. + return obj; +} + +// #sec-function.prototype.bind +function FunctionProto_bind([thisArg = Value.undefined, ...args], { thisValue }) { + // 1. Let Target be the this value. + const Target = thisValue; + // 2. If IsCallable(Target) is false, throw a TypeError exception. + if (IsCallable(Target) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', Target); + } + // 3. Let args be a new (possibly empty) List consisting of all of the argument values provided after thisArg in order. + // 4. Let F be ? BoundFunctionCreate(Target, thisArg, args). + const F = Q(BoundFunctionCreate(Target, thisArg, args)); + // 5. Let targetHasLength be ? HasOwnProperty(Target, "length"). + const targetHasLength = Q(HasOwnProperty(Target, new Value('length'))); + // 6. If targetHasLength is true, then + let L; + if (targetHasLength === Value.true) { + // a. Let targetLen be ? Get(Target, "length"). + let targetLen = Q(Get(Target, new Value('length'))); + // b. If Type(targetLen) is not Number, let L be 0. + if (Type(targetLen) !== 'Number') { + L = 0; + } else { // c. Else, + // i. Set targetLen to ! ToInteger(targetLen). + targetLen = Q(ToInteger(targetLen)).numberValue(); + // ii. Let L be the larger of 0 and the result of targetLen minus the number of elements of args. + L = Math.max(0, targetLen - args.length); + } + } else { + // 7. ELse, let L be 0. + L = 0; + } + // 8. Perform ! SetFunctionLength(F, L). + X(SetFunctionLength(F, new Value(L))); + // 9. Let targetName be ? Get(Target, "name"). + let targetName = Q(Get(Target, new Value('name'))); + // 10. If Type(targetName) is not String, set targetName to the empty String. + if (Type(targetName) !== 'String') { + targetName = new Value(''); + } + // 11. Perform SetFunctionName(F, targetName, "bound"). + SetFunctionName(F, targetName, new Value('bound')); + // 12. Return F. + return F; +} + +// #sec-function.prototype.call +function FunctionProto_call([thisArg = Value.undefined, ...args], { thisValue }) { + // 1. Let func be the this value. + const func = thisValue; + // 2. If IsCallable(func) is false, throw a TypeError exception. + if (IsCallable(func) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); + } + // 3. Let argList be a new empty List. + const argList = []; + // 4. If this method was called with more than one argument, then in left to right order, starting with the second argument, append each argument as the last element of argList. + for (const arg of args) { + argList.push(arg); + } + // 5. Perform PrepareForTailCall(). + PrepareForTailCall(); + // 6. Return ? Call(func, thisArg, argList). + return Q(Call(func, thisArg, argList)); +} + +// #sec-function.prototype.tostring +function FunctionProto_toString(args, { thisValue }) { + // 1. Let func be the this value. + const func = thisValue; + // 2. If func is a built-in function object, then return an implementation-defined + // String source code representation of func. The representation must have the + // syntax of a NativeFunction. Additionally, if func has an [[InitialName]] internal + // slot and func.[[InitialName]] is a String, the portion of the returned String + // that would be matched by `NativeFunctionAccessor? PropertyName` must be the + // value of func.[[InitialName]]. + if ('nativeFunction' in func) { + if (func.InitialName !== Value.null) { + return new Value(`function ${func.InitialName.stringValue()}() { [native code] }`); + } + return new Value('function() { [native code] }'); + } + // 3. If Type(func) is Object and func has a [[SourceText]] internal slot and func.[[SourceText]] + // is a sequence of Unicode code points and ! HostHasSourceTextAvailable(func) is true, then + if (Type(func) === 'Object' + && 'SourceText' in func + && X(HostHasSourceTextAvailable(func)) === Value.true) { + // Return ! UTF16Encode(func.[[SourceText]]). + return new Value(func.SourceText); + } + // 4. If Type(func) is Object and IsCallable(func) is true, then return an implementation + // dependent String source code representation of func. The representation must have + // the syntax of a NativeFunction. + if (Type(func) === 'Object' && IsCallable(func) === Value.true) { + return new Value('function() { [native code] }'); + } + // 5. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); +} + +// #sec-function.prototype-@@hasinstance +function FunctionProto_hasInstance([V = Value.undefined], { thisValue }) { + // 1. Let F be this value. + const F = thisValue; + // 2. Return ? OrdinaryHasInstance(F, V). + return Q(OrdinaryHasInstance(F, V)); +} + +export function BootstrapFunctionPrototype(realmRec) { + const proto = CreateBuiltinFunction(FunctionProto, [], realmRec, realmRec.Intrinsics['%Object.prototype%']); + realmRec.Intrinsics['%Function.prototype%'] = proto; + + SetFunctionLength(proto, new Value(0)); + SetFunctionName(proto, new Value('')); + + const readonly = { Writable: Value.false, Configurable: Value.false }; + assignProps(realmRec, proto, [ + ['apply', FunctionProto_apply, 2], + ['bind', FunctionProto_bind, 1], + ['call', FunctionProto_call, 1], + ['toString', FunctionProto_toString, 0], + [wellKnownSymbols.hasInstance, FunctionProto_hasInstance, 1, readonly], + ]); +} diff --git a/engine262/src/intrinsics/Generator.mjs b/engine262/src/intrinsics/Generator.mjs new file mode 100644 index 0000000..b0f6879 --- /dev/null +++ b/engine262/src/intrinsics/Generator.mjs @@ -0,0 +1,21 @@ +import { Descriptor, Value } from '../value.mjs'; +import { DefinePropertyOrThrow } from '../abstract-ops/all.mjs'; +import { X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +export function BootstrapGenerator(realmRec) { + const generatorPrototype = realmRec.Intrinsics['%Generator.prototype%']; + + const generator = BootstrapPrototype(realmRec, [ + ['prototype', generatorPrototype, undefined, { Writable: Value.false }], + ], realmRec.Intrinsics['%Function.prototype%'], 'GeneratorFunction'); + + X(DefinePropertyOrThrow(generatorPrototype, new Value('constructor'), Descriptor({ + Value: generator, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%Generator%'] = generator; +} diff --git a/engine262/src/intrinsics/GeneratorFunction.mjs b/engine262/src/intrinsics/GeneratorFunction.mjs new file mode 100644 index 0000000..7c2e1d2 --- /dev/null +++ b/engine262/src/intrinsics/GeneratorFunction.mjs @@ -0,0 +1,30 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { DefinePropertyOrThrow } from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-generatorfunction +function GeneratorFunctionConstructor(args, { NewTarget }) { + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject; + // 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]]. + // 3. Return ? CreateDynamicFunction(C, NewTarget, generator, args). + return Q(CreateDynamicFunction(C, NewTarget, 'generator', args)); +} + +export function BootstrapGeneratorFunction(realmRec) { + const generator = realmRec.Intrinsics['%Generator%']; + + const cons = BootstrapConstructor(realmRec, GeneratorFunctionConstructor, 'GeneratorFunction', 1, generator, []); + X(DefinePropertyOrThrow(cons, new Value('prototype'), Descriptor({ + Writable: Value.false, + Configurable: Value.false, + }))); + X(DefinePropertyOrThrow(generator, new Value('constructor'), Descriptor({ + Writable: Value.false, + }))); + + realmRec.Intrinsics['%GeneratorFunction%'] = cons; +} diff --git a/engine262/src/intrinsics/GeneratorPrototype.mjs b/engine262/src/intrinsics/GeneratorPrototype.mjs new file mode 100644 index 0000000..266a38a --- /dev/null +++ b/engine262/src/intrinsics/GeneratorPrototype.mjs @@ -0,0 +1,49 @@ +import { + GeneratorResume, + GeneratorResumeAbrupt, +} from '../abstract-ops/all.mjs'; +import { + Completion, + ThrowCompletion, + Q, +} from '../completion.mjs'; +import { Value } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-generator.prototype.next +function GeneratorProto_next([value = Value.undefined], { thisValue }) { + // 1. Let g be the this value. + const g = thisValue; + // 2. Return ? GeneratorResume(g, value). + return Q(GeneratorResume(g, value)); +} + +// #sec-generator.prototype.return +function GeneratorProto_return([value = Value.undefined], { thisValue }) { + // 1. Let g be the this value. + const g = thisValue; + // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + const C = new Completion({ Type: 'return', Value: value, Target: undefined }); + // 3. Return ? GeneratorResumeAbrupt(g, C). + return Q(GeneratorResumeAbrupt(g, C)); +} + +// #sec-generator.prototype.throw +function GeneratorProto_throw([exception = Value.undefined], { thisValue }) { + // 1. Let g be the this value. + const g = thisValue; + // 2. Let C be ThrowCompletion(exception). + const C = ThrowCompletion(exception); + // 3. Return ? GeneratorResumeAbrupt(g, C). + return Q(GeneratorResumeAbrupt(g, C)); +} + +export function BootstrapGeneratorPrototype(realmRec) { + const generatorPrototype = BootstrapPrototype(realmRec, [ + ['next', GeneratorProto_next, 1], + ['return', GeneratorProto_return, 1], + ['throw', GeneratorProto_throw, 1], + ], realmRec.Intrinsics['%IteratorPrototype%'], 'Generator'); + + realmRec.Intrinsics['%Generator.prototype%'] = generatorPrototype; +} diff --git a/engine262/src/intrinsics/IteratorPrototype.mjs b/engine262/src/intrinsics/IteratorPrototype.mjs new file mode 100644 index 0000000..fdd2586 --- /dev/null +++ b/engine262/src/intrinsics/IteratorPrototype.mjs @@ -0,0 +1,16 @@ +import { wellKnownSymbols } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-%iteratorprototype%-@@iterator +function IteratorPrototype_iterator(args, { thisValue }) { + // 1. Return this value. + return thisValue; +} + +export function BootstrapIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + [wellKnownSymbols.iterator, IteratorPrototype_iterator, 0], + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%IteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/JSON.mjs b/engine262/src/intrinsics/JSON.mjs new file mode 100644 index 0000000..92ba20e --- /dev/null +++ b/engine262/src/intrinsics/JSON.mjs @@ -0,0 +1,548 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + BooleanValue, + NullValue, + NumberValue, + ObjectValue, + JSStringValue, + Type, + Value, +} from '../value.mjs'; +import { + Assert, + Call, + CreateDataProperty, + CreateDataPropertyOrThrow, + EnumerableOwnPropertyNames, + Get, + GetV, + IsArray, + IsCallable, + OrdinaryObjectCreate, + LengthOfArrayLike, + ToInteger, + ToNumber, + ToString, +} from '../abstract-ops/all.mjs'; +import { + isLeadingSurrogate, + isTrailingSurrogate, +} from '../parser/Lexer.mjs'; +import { + CodePointToUTF16CodeUnits, +} from '../static-semantics/all.mjs'; +import { + NormalCompletion, + Q, X, +} from '../completion.mjs'; +import { ValueSet } from '../helpers.mjs'; +import { evaluateScript } from '../api.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +const WHITESPACE = [' ', '\t', '\r', '\n']; +const NUMERIC = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; +const VALID_HEX = [...NUMERIC, 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f']; +const ESCAPABLE = ['"', '\\', '/', 'b', 'f', 'n', 'r', 't']; + +class JSONValidator { + constructor(input) { + this.input = input; + this.pos = 0; + this.char = input.charAt(0); + } + + validate() { + X(this.eatWhitespace()); + Q(this.parseValue()); + if (this.pos < this.input.length) { + return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken'); + } + return NormalCompletion(undefined); + } + + advance() { + this.pos += 1; + if (this.pos === this.input.length) { + this.char = null; + } else if (this.pos > this.input.length) { + return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken'); + } else { + this.char = this.input.charAt(this.pos); + } + return this.char; + } + + eatWhitespace() { + while (this.eat(WHITESPACE)) { + // nothing + } + } + + eat(c) { + if (Array.isArray(c) && c.includes(this.char)) { + X(this.advance()); + return true; + } else if (this.char === c) { + X(this.advance()); + return true; + } + return false; + } + + expect(c) { + const { char } = this; + if (!this.eat(c)) { + return surroundingAgent.Throw('SyntaxError', 'JSONExpected', c, this.char); + } + return char; + } + + parseValue() { + switch (this.char) { + case '"': + return Q(this.parseString()); + case '{': + return Q(this.parseObject()); + case '[': + return Q(this.parseArray()); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Q(this.parseNumber()); + case 'f': + X(this.expect('f')); + Q(this.expect('a')); + Q(this.expect('l')); + Q(this.expect('s')); + Q(this.expect('e')); + return X(this.eatWhitespace()); + case 't': + X(this.expect('t')); + Q(this.expect('r')); + Q(this.expect('u')); + Q(this.expect('e')); + return X(this.eatWhitespace()); + case 'n': + X(this.expect('n')); + Q(this.expect('u')); + Q(this.expect('l')); + Q(this.expect('l')); + return X(this.eatWhitespace()); + default: + return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedChar', this.char); + } + } + + parseString() { + Q(this.expect('"')); + while (!this.eat('"')) { + if (this.eat('\\')) { + if (!this.eat(ESCAPABLE)) { + Q(this.expect('u')); + Q(this.expect(VALID_HEX)); + Q(this.expect(VALID_HEX)); + Q(this.expect(VALID_HEX)); + Q(this.expect(VALID_HEX)); + } + } else { + if (this.char < ' ') { + return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedChar', this.char); + } + Q(this.advance()); + } + } + return X(this.eatWhitespace()); + } + + parseNumber() { + this.eat('-'); + if (!this.eat('0')) { + Q(this.expect(NUMERIC)); + while (this.eat(NUMERIC)) { + // nothing + } + } + if (this.eat('.')) { + Q(this.expect(NUMERIC)); + while (this.eat(NUMERIC)) { + // nothing + } + } + if (this.eat(['e', 'E'])) { + this.eat(['-', '+']); + Q(this.expect(NUMERIC)); + while (this.eat(NUMERIC)) { + // nothing + } + } + X(this.eatWhitespace()); + } + + parseObject() { + Q(this.expect('{')); + X(this.eatWhitespace()); + let first = true; + while (!this.eat('}')) { + if (first) { + first = false; + } else { + Q(this.expect(',')); + X(this.eatWhitespace()); + } + Q(this.parseString()); + X(this.eatWhitespace()); + Q(this.expect(':')); + X(this.eatWhitespace()); + Q(this.parseValue()); + X(this.eatWhitespace()); + } + X(this.eatWhitespace()); + } + + parseArray() { + Q(this.expect('[')); + X(this.eatWhitespace()); + let first = true; + while (!this.eat(']')) { + if (first) { + first = false; + } else { + Q(this.expect(',')); + X(this.eatWhitespace()); + } + Q(this.parseValue()); + X(this.eatWhitespace()); + } + X(this.eatWhitespace()); + } + + static validate(input) { + const v = new JSONValidator(input); + return v.validate(); + } +} + +function InternalizeJSONProperty(holder, name, reviver) { + const val = Q(Get(holder, name)); + if (Type(val) === 'Object') { + const isArray = Q(IsArray(val)); + if (isArray === Value.true) { + let I = 0; + const len = Q(LengthOfArrayLike(val)).numberValue(); + while (I < len) { + const Istr = X(ToString(new Value(I))); + const newElement = Q(InternalizeJSONProperty(val, Istr, reviver)); + if (Type(newElement) === 'Undefined') { + Q(val.Delete(Istr)); + } else { + Q(CreateDataProperty(val, Istr, newElement)); + } + I += 1; + } + } else { + const keys = Q(EnumerableOwnPropertyNames(val, 'key')); + for (const P of keys) { + const newElement = Q(InternalizeJSONProperty(val, P, reviver)); + if (Type(newElement) === 'Undefined') { + Q(val.Delete(P)); + } else { + Q(CreateDataProperty(val, P, newElement)); + } + } + } + } + return Q(Call(reviver, holder, [name, val])); +} + +// #sec-json.parse +function JSON_parse([text = Value.undefined, reviver = Value.undefined]) { + // 1. Let jsonString be ? ToString(text). + const jsonString = Q(ToString(text)); + // 2. Parse ! UTF16DecodeString(jsonString) as a JSON text as specified in ECMA-404. + // Throw a SyntaxError exception if it is not a valid JSON text as defined in that specification. + Q(JSONValidator.validate(jsonString.stringValue())); + // 3. Let scriptString be the string-concatenation of "(", jsonString, and ");". + const scriptString = `(${jsonString.stringValue()});`; + // 4. Let completion be the result of parsing and evaluating + // ! UTF16DecodeString(scriptString) as if it was the source text of an ECMAScript Script. The + // extended PropertyDefinitionEvaluation semantics defined in B.3.1 must not be used during the evaluation. + const completion = evaluateScript(scriptString, surroundingAgent.currentRealmRecord); + // 5. Let unfiltered be completion.[[Value]]. + const unfiltered = completion.Value; + // 6. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. + Assert(unfiltered instanceof JSStringValue + || unfiltered instanceof NumberValue + || unfiltered instanceof BooleanValue + || unfiltered instanceof NullValue + || unfiltered instanceof ObjectValue); + // 7. If IsCallable(reviver) is true, then + if (IsCallable(reviver) === Value.true) { + // a. Let root be OrdinaryObjectCreate(%Object.prototype%). + const root = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // b. Let rootName be the empty String. + const rootName = new Value(''); + // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). + X(CreateDataPropertyOrThrow(root, rootName, unfiltered)); + // d. Return ? InternalizeJSONProperty(root, rootName, reviver). + return Q(InternalizeJSONProperty(root, rootName, reviver)); + } else { + // a. Return unfiltered. + return unfiltered; + } +} + +const codeUnitTable = new Map([ + [0x0008, '\\b'], + [0x0009, '\\t'], + [0x000A, '\\n'], + [0x000C, '\\f'], + [0x000D, '\\r'], + [0x0022, '\\"'], + [0x005C, '\\\\'], +]); + +// #sec-serializejsonproperty +function SerializeJSONProperty(state, key, holder) { + let value = Q(Get(holder, key)); // eslint-disable-line no-shadow + if (Type(value) === 'Object' || Type(value) === 'BigInt') { + const toJSON = Q(GetV(value, new Value('toJSON'))); + if (IsCallable(toJSON) === Value.true) { + value = Q(Call(toJSON, value, [key])); + } + } + if (state.ReplacerFunction !== Value.undefined) { + value = Q(Call(state.ReplacerFunction, holder, [key, value])); + } + if (Type(value) === 'Object') { + if ('NumberData' in value) { + value = Q(ToNumber(value)); + } else if ('StringData' in value) { + value = Q(ToString(value)); + } else if ('BooleanData' in value) { + value = value.BooleanData; + } else if ('BigIntData' in value) { + value = value.BigIntData; + } + } + if (value === Value.null) { + return new Value('null'); + } + if (value === Value.true) { + return new Value('true'); + } + if (value === Value.false) { + return new Value('false'); + } + if (Type(value) === 'String') { + return QuoteJSONString(value); + } + if (Type(value) === 'Number') { + if (value.isFinite()) { + return X(ToString(value)); + } + return new Value('null'); + } + if (Type(value) === 'BigInt') { + return surroundingAgent.Throw('TypeError', 'CannotJSONSerializeBigInt'); + } + if (Type(value) === 'Object' && IsCallable(value) === Value.false) { + const isArray = Q(IsArray(value)); + if (isArray === Value.true) { + return Q(SerializeJSONArray(state, value)); + } + return Q(SerializeJSONObject(state, value)); + } + return Value.undefined; +} + +function UnicodeEscape(C) { + const n = C.charCodeAt(0); + Assert(n < 0xFFFF); + return `\u005Cu${n.toString(16).padStart(4, '0')}`; +} + +function QuoteJSONString(value) { // eslint-disable-line no-shadow + let product = '\u0022'; + const cpList = [...value.stringValue()].map((c) => c.codePointAt(0)); + for (const C of cpList) { + if (codeUnitTable.has(C)) { + product = `${product}${codeUnitTable.get(C)}`; + } else if (C < 0x0020 || isLeadingSurrogate(C) || isTrailingSurrogate(C)) { + const unit = String.fromCodePoint(C); + product = `${product}${UnicodeEscape(unit)}`; + } else { + product = `${product}${String.fromCodePoint(...CodePointToUTF16CodeUnits(C))}`; + } + } + product = `${product}\u0022`; + return new Value(product); +} + +// #sec-serializejsonobject +function SerializeJSONObject(state, value) { + if (state.Stack.includes(value)) { + return surroundingAgent.Throw('TypeError', 'JSONCircular'); + } + state.Stack.push(value); + const stepback = state.Indent; + state.Indent = `${state.Indent}${state.Gap}`; + let K; + if (state.PropertyList !== Value.undefined) { + K = state.PropertyList; + } else { + K = Q(EnumerableOwnPropertyNames(value, 'key')); + } + const partial = []; + for (const P of K) { + const strP = Q(SerializeJSONProperty(state, P, value)); + if (strP !== Value.undefined) { + let member = QuoteJSONString(P).stringValue(); + member = `${member}:`; + if (state.Gap !== '') { + member = `${member} `; + } + member = `${member}${strP.stringValue()}`; + partial.push(member); + } + } + let final; + if (partial.length === 0) { + final = new Value('{}'); + } else { + if (state.Gap === '') { + const properties = partial.join(','); + final = new Value(`{${properties}}`); + } else { + const separator = `,\u000A${state.Indent}`; + const properties = partial.join(separator); + final = new Value(`{\u000A${state.Indent}${properties}\u000A${stepback}}`); + } + } + state.Stack.pop(); + state.Indent = stepback; + return final; +} + +// #sec-serializejsonarray +function SerializeJSONArray(state, value) { + if (state.Stack.includes(value)) { + return surroundingAgent.Throw('TypeError', 'JSONCircular'); + } + state.Stack.push(value); + const stepback = state.Indent; + state.Indent = `${state.Indent}${state.Gap}`; + const partial = []; + const len = Q(LengthOfArrayLike(value)).numberValue(); + let index = 0; + while (index < len) { + const indexStr = X(ToString(new Value(index))); + const strP = Q(SerializeJSONProperty(state, indexStr, value)); + if (strP === Value.undefined) { + partial.push('null'); + } else { + partial.push(strP.stringValue()); + } + index += 1; + } + let final; + if (partial.length === 0) { + final = new Value('[]'); + } else { + if (state.Gap === '') { + const properties = partial.join(','); + final = new Value(`[${properties}]`); + } else { + const separator = `,\u000A${state.Indent}`; + const properties = partial.join(separator); + final = new Value(`[\u000A${state.Indent}${properties}\u000A${stepback}]`); + } + } + state.Stack.pop(); + state.Indent = stepback; + return final; +} + +// #sec-json.stringify +function JSON_stringify([value = Value.undefined, replacer = Value.undefined, space = Value.undefined]) { + const stack = []; + const indent = ''; + let PropertyList = Value.undefined; + let ReplacerFunction = Value.undefined; + if (Type(replacer) === 'Object') { + if (IsCallable(replacer) === Value.true) { + ReplacerFunction = replacer; + } else { + const isArray = Q(IsArray(replacer)); + if (isArray === Value.true) { + PropertyList = new ValueSet(); + const len = Q(LengthOfArrayLike(replacer)).numberValue(); + let k = 0; + while (k < len) { + const vStr = X(ToString(new Value(k))); + const v = Q(Get(replacer, vStr)); + let item = Value.undefined; + if (Type(v) === 'String') { + item = v; + } else if (Type(v) === 'Number') { + item = X(ToString(v)); + } else if (Type(v) === 'Object') { + if ('StringData' in v || 'NumberData' in v) { + item = Q(ToString(v)); + } + } + if (item !== Value.undefined && !PropertyList.has(item)) { + PropertyList.add(item); + } + k += 1; + } + } + } + } + if (Type(space) === 'Object') { + if ('NumberData' in space) { + space = Q(ToNumber(space)); + } else if ('StringData' in space) { + space = Q(ToString(space)); + } + } + let gap; + if (Type(space) === 'Number') { + space = Math.min(10, X(ToInteger(space)).numberValue()); + if (space < 1) { + gap = ''; + } else { + gap = ' '.repeat(space); + } + } else if (Type(space) === 'String') { + if (space.stringValue().length <= 10) { + gap = space.stringValue(); + } else { + gap = space.stringValue().slice(0, 10); + } + } else { + gap = ''; + } + const wrapper = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataPropertyOrThrow(wrapper, new Value(''), value)); + const state = { + ReplacerFunction, Stack: stack, Indent: indent, Gap: gap, PropertyList, + }; + return Q(SerializeJSONProperty(state, new Value(''), wrapper)); +} + +export function BootstrapJSON(realmRec) { + const json = BootstrapPrototype(realmRec, [ + ['parse', JSON_parse, 2], + ['stringify', JSON_stringify, 3], + ], realmRec.Intrinsics['%Object.prototype%'], 'JSON'); + + realmRec.Intrinsics['%JSON%'] = json; +} diff --git a/engine262/src/intrinsics/Map.mjs b/engine262/src/intrinsics/Map.mjs new file mode 100644 index 0000000..388089c --- /dev/null +++ b/engine262/src/intrinsics/Map.mjs @@ -0,0 +1,87 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + Call, + Get, + GetIterator, + IsCallable, + IteratorClose, + IteratorStep, + IteratorValue, + OrdinaryCreateFromConstructor, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + AbruptCompletion, + Q, +} from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +export function AddEntriesFromIterable(target, iterable, adder) { + if (IsCallable(adder) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + Assert(iterable !== undefined && iterable !== Value.undefined && iterable !== Value.null); + const iteratorRecord = Q(GetIterator(iterable)); + while (true) { + const next = Q(IteratorStep(iteratorRecord)); + if (next === Value.false) { + return target; + } + const nextItem = Q(IteratorValue(next)); + if (Type(nextItem) !== 'Object') { + const error = surroundingAgent.Throw('TypeError', 'NotAnObject', nextItem); + return Q(IteratorClose(iteratorRecord, error)); + } + const k = Get(nextItem, new Value('0')); + if (k instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, k)); + } + const v = Get(nextItem, new Value('1')); + if (v instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, v)); + } + const status = Call(adder, target, [k.Value, v.Value]); + if (status instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, status)); + } + } +} + +// #sec-map-iterable +function MapConstructor([iterable = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »). + const map = Q(OrdinaryCreateFromConstructor(NewTarget, '%Map.prototype%', ['MapData'])); + // 3. Set map.[[MapData]] to a new empty List. + map.MapData = []; + // 4. If iterable is either undefined or null, return map. + if (iterable === Value.undefined || iterable === Value.null) { + return map; + } + // 5. Let adder be ? Get(map, "set"). + const adder = Q(Get(map, new Value('set'))); + // 6. Return ? AddEntriesFromIterable(map, iterable, adder). + return Q(AddEntriesFromIterable(map, iterable, adder)); +} + +// #sec-get-map-@@species +function Map_speciesGetter(args, { thisValue }) { + // 1. Return the this value. + return thisValue; +} + +export function BootstrapMap(realmRec) { + const mapConstructor = BootstrapConstructor(realmRec, MapConstructor, 'Map', 0, realmRec.Intrinsics['%Map.prototype%'], [ + [wellKnownSymbols.species, [Map_speciesGetter]], + ]); + + realmRec.Intrinsics['%Map%'] = mapConstructor; +} diff --git a/engine262/src/intrinsics/MapIteratorPrototype.mjs b/engine262/src/intrinsics/MapIteratorPrototype.mjs new file mode 100644 index 0000000..ef32c0d --- /dev/null +++ b/engine262/src/intrinsics/MapIteratorPrototype.mjs @@ -0,0 +1,79 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + CreateArrayFromList, + CreateIterResultObject, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + + +// #sec-%mapiteratorprototype%.next +function MapIteratorPrototype_next(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Map Iterator', O); + } + // 3. If O does not have all of the internal slots of a Map Iterator Instance (23.1.5.3), throw a TypeError exception. + if (!('IteratedMap' in O && 'MapNextIndex' in O && 'MapIterationKind' in O)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Map Iterator', O); + } + // 4. Let m be O.[[IteratedMap]]. + const m = O.IteratedMap; + // 5. Let index be O.[[MapNextIndex]]. + let index = O.MapNextIndex; + // 6. Let index be O.[[MapNextIndex]]. + const itemKind = O.MapIterationKind; + // 7. If m is undefined, return CreateIterResultObject(undefined, true). + if (m === Value.undefined) { + return CreateIterResultObject(Value.undefined, Value.true); + } + // 8. Assert: m has a [[MapData]] internal slot. + Assert('MapData' in m); + // 9. Let entries be the List that is m.[[MapData]]. + const entries = m.MapData; + // 10. Let numEntries be the number of elements of entries. + const numEntries = entries.length; + // 11. NOTE: numEntries must be redetermined each time this method is evaluated. + // 12. Repeat, while index is less than numEntries, + while (index < numEntries) { + // a. Let e be the Record { [[Key]], [[Value]] } that is the value of entries[index]. + const e = entries[index]; + // b. Set index to index + 1. + index += 1; + // c. Set O.[[MapNextIndex]] to index. + O.MapNextIndex = index; + // d. If e.[[Key]] is not empty, then + if (e.Key !== undefined) { + let result; + // i. If itemKind is key, let result be e.[[Key]]. + if (itemKind === 'key') { + result = e.Key; + } else if (itemKind === 'value') { // ii. Else if itemKind is value, let result be e.[[Value]]. + result = e.Value; + } else { // iii. Else, + // 1. Assert: itemKind is key+value. + Assert(itemKind === 'key+value'); + // 2. Let result be ! CreateArrayFromList(« e.[[Key]], e.[[Value]] »). + result = X(CreateArrayFromList([e.Key, e.Value])); + } + // iv. Return CreateIterResultObject(result, false). + return CreateIterResultObject(result, Value.false); + } + } + // 13. Set O.[[IteratedMap]] to undefined. + O.IteratedMap = Value.undefined; + // 14. Return CreateIterResultObject(undefined, true). + return CreateIterResultObject(Value.undefined, Value.true); +} + +export function BootstrapMapIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', MapIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%IteratorPrototype%'], 'Map Iterator'); + + realmRec.Intrinsics['%MapIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/MapPrototype.mjs b/engine262/src/intrinsics/MapPrototype.mjs new file mode 100644 index 0000000..05ddc23 --- /dev/null +++ b/engine262/src/intrinsics/MapPrototype.mjs @@ -0,0 +1,229 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Call, + IsCallable, + OrdinaryObjectCreate, + SameValueZero, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + + +function CreateMapIterator(map, kind) { + Q(RequireInternalSlot(map, 'MapData')); + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%MapIteratorPrototype%'), [ + 'IteratedMap', + 'MapNextIndex', + 'MapIterationKind', + ]); + iterator.IteratedMap = map; + iterator.MapNextIndex = 0; + iterator.MapIterationKind = kind; + return iterator; +} + +// #sec-map.prototype.clear +function MapProto_clear(args, { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Let entries be the List that is M.[[MapData]]. + const entries = M.MapData; + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. Set p.[[Key]] to empty. + p.Key = undefined; + // b. Set p.[[Value]] to empty. + p.Value = undefined; + } + // 5. Return undefined. + return Value.undefined; +} + +// #sec-map.prototype.delete +function MapProto_delete([key = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Let entires be M.[[MapData]]. + const entries = M.MapData; + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then + if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) { + // i. Set p.[[Key]] to empty. + p.Key = undefined; + // ii. Set p.[[Value]] to empty. + p.Value = undefined; + // iii. Return true. + return Value.true; + } + } + return Value.false; +} + +// #sec-map.prototype.entries +function MapProto_entries(args, { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Return ? CreateMapIterator(M, key+value); + return Q(CreateMapIterator(M, 'key+value')); +} + +// #sec-map.prototype.foreach +function MapProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 4. Let entries be the List that is M.[[MapData]]. + const entries = M.MapData; + // 5. For each Record { [[Key]], [[Value]] } e that is an element of entries, in original key insertion order, do + for (const e of entries) { + // a. If e.[[Key]] is not empty, then + if (e.Key !== undefined) { + // i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »). + Q(Call(callbackfn, thisArg, [e.Value, e.Key, M])); + } + } + // 6. Return undefined. + return Value.undefined; +} + +// #sec-map.prototype.get +function MapProto_get([key = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Let entries be the List that is M.[[MapData]]. + const entries = M.MapData; + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]]. + if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) { + // i. Return p.[[Value]]. + return p.Value; + } + } + // 5. Return undefined. + return Value.undefined; +} + +// #sec-map.prototype.has +function MapProto_has([key = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Let entries be the List that is M.[[MapData]]. + const entries = M.MapData; + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true. + if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) { + return Value.true; + } + } + // 5. Return false. + return Value.false; +} + +// #sec-map.prototype.keys +function MapProto_keys(args, { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Return ? CreateMapIterator(M, key). + return Q(CreateMapIterator(M, 'key')); +} + +// #sec-map.prototype.set +function MapProto_set([key = Value.undefined, value = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Let entries be the List that is M.[[MapData]]. + const entries = M.MapData; + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then + if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) { + // i. Set p.[[Value]] to value. + p.Value = value; + // ii. Return M. + return M; + } + } + // 5. If key is -0, set key to +0. + if (Type(key) === 'Number' && Object.is(key.numberValue(), -0)) { + key = new Value(0); + } + // 6. Let p be the Record { [[Key]]: key, [[Value]]: value }. + const p = { Key: key, Value: value }; + // 7. Append p as the last element of entries. + entries.push(p); + // 8. Return M. + return M; +} + +// #sec-get-map.prototype.size +function MapProto_sizeGetter(args, { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Let entries be the List that is M.[[MapData]]. + const entries = M.MapData; + // 4. Let count be 0. + let count = 0; + // 5. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty, set count to count + 1. + if (p.Key !== undefined) { + count += 1; + } + } + // 6. Return count. + return new Value(count); +} + +// #sec-map.prototype.values +function MapProto_values(args, { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Return ? CreateMapIterator(M, value). + return Q(CreateMapIterator(M, 'value')); +} + +export function BootstrapMapPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['clear', MapProto_clear, 0], + ['delete', MapProto_delete, 1], + ['entries', MapProto_entries, 0], + ['forEach', MapProto_forEach, 1], + ['get', MapProto_get, 1], + ['has', MapProto_has, 1], + ['keys', MapProto_keys, 0], + ['set', MapProto_set, 2], + ['size', [MapProto_sizeGetter]], + ['values', MapProto_values, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Map'); + + const entriesFunc = X(proto.GetOwnProperty(new Value('entries'))); + X(proto.DefineOwnProperty(wellKnownSymbols.iterator, entriesFunc)); + + realmRec.Intrinsics['%Map.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Math.mjs b/engine262/src/intrinsics/Math.mjs new file mode 100644 index 0000000..239404b --- /dev/null +++ b/engine262/src/intrinsics/Math.mjs @@ -0,0 +1,179 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Descriptor, + Value, + NumberValue, +} from '../value.mjs'; +import { + CreateBuiltinFunction, + SetFunctionLength, + SetFunctionName, + ToNumber, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// 20.2.2.1 #sec-math.abs +function Math_abs([x = Value.undefined]) { + x = Q(ToNumber(x)); + if (x.isNaN()) { + return x; + } else if (Object.is(x.numberValue(), -0)) { + return new Value(0); + } else if (x.isInfinity()) { + return new Value(Infinity); + } + + if (x.numberValue() < 0) { + return new Value(-x.numberValue()); + } + return x; +} + +// 20.2.2.2 #sec-math.acos +function Math_acos([x = Value.undefined]) { + x = Q(ToNumber(x)); + if (x.isNaN()) { + return x; + } else if (x.numberValue() > 1) { + return new Value(NaN); + } else if (x.numberValue() < -1) { + return new Value(NaN); + } else if (x.numberValue() === 1) { + return new Value(+0); + } + + return new Value(Math.acos(x.numberValue())); +} + +// #sec-math.pow +function Math_pow([base = Value.undefined, exponent = Value.undefined]) { + // 1. Set base to ? ToNumber(base). + base = Q(ToNumber(base)); + // 2. Set exponent to ? ToNumber(exponent). + exponent = Q(ToNumber(exponent)); + // 3. Return ! Number::exponentiate(base, exponent). + return X(NumberValue.exponentiate(base, exponent)); +} + +function fmix64(h) { + h ^= h >> 33n; + h *= 0xFF51AFD7ED558CCDn; + h ^= h >> 33n; + h *= 0xC4CEB9FE1A85EC53n; + h ^= h >> 33n; + return h; +} + +const floatView = new Float64Array(1); +const big64View = new BigUint64Array(floatView.buffer); +// #sec-math.random +function Math_random() { + const realm = surroundingAgent.currentRealmRecord; + if (realm.randomState === undefined) { + const seed = realm.HostDefined.randomSeed + ? BigInt(X(realm.HostDefined.randomSeed())) + : BigInt(Math.round(Math.random() * (2 ** 32))); + realm.randomState = new BigUint64Array([ + fmix64(BigInt.asUintN(64, seed)), + fmix64(BigInt.asUintN(64, ~seed)), + ]); + } + const s = realm.randomState; + + // XorShift128+ + let s1 = s[0]; + const s0 = s[1]; + s[0] = s0; + s1 ^= s1 << 23n; + s1 ^= s1 >> 17n; + s1 ^= s0; + s1 ^= s0 >> 26n; + s[1] = s1; + + // Convert to double in [0, 1) range + big64View[0] = (s0 >> 12n) | 0x3FF0000000000000n; + const result = floatView[0] - 1; + return new Value(result); +} + +// 20.2 #sec-math-object +export function BootstrapMath(realmRec) { + // 20.2.1 #sec-value-properties-of-the-math-object + const readonly = { Writable: Value.false, Configurable: Value.false }; + const valueProps = [ + ['E', 2.7182818284590452354], + ['LN10', 2.302585092994046], + ['LN2', 0.6931471805599453], + ['LOG10E', 0.4342944819032518], + ['LOG2E', 1.4426950408889634], + ['PI', 3.1415926535897932], + ['SQRT1_2', 0.7071067811865476], + ['SQRT2', 1.4142135623730951], + ].map(([name, value]) => [name, new Value(value), undefined, readonly]); + // @@toStringTag is handled in the BootstrapPrototype() call. + + const mathObj = BootstrapPrototype(realmRec, [ + ...valueProps, + ['abs', Math_abs, 1], + ['acos', Math_acos, 1], + ['pow', Math_pow, 2], + ['random', Math_random, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Math'); + + // 20.2.2 #sec-function-properties-of-the-math-object + + [ + ['acosh', 1], + ['asin', 1], + ['asinh', 1], + ['atan', 1], + ['atanh', 1], + ['atan2', 2], + ['cbrt', 1], + ['ceil', 1], + ['clz32', 1], + ['cos', 1], + ['cosh', 1], + ['exp', 1], + ['expm1', 1], + ['floor', 1], + ['fround', 1], + ['hypot', 2], + ['imul', 2], + ['log', 1], + ['log1p', 1], + ['log10', 1], + ['log2', 1], + ['max', 2], + ['min', 2], + ['round', 1], + ['sign', 1], + ['sin', 1], + ['sinh', 1], + ['sqrt', 1], + ['tan', 1], + ['tanh', 1], + ['trunc', 1], + ].forEach(([name, length]) => { + // TODO(18): Math + // #sec-function-properties-of-the-math-object + const method = (args) => { + for (let i = 0; i < args.length; i += 1) { + args[i] = Q(ToNumber(args[i])).numberValue(); + } + return new Value(Math[name](...args)); + }; + const func = CreateBuiltinFunction(method, [], realmRec); + X(SetFunctionName(func, new Value(name))); + X(SetFunctionLength(func, new Value(length))); + mathObj.DefineOwnProperty(new Value(name), Descriptor({ + Value: func, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + })); + }); + + realmRec.Intrinsics['%Math%'] = mathObj; +} diff --git a/engine262/src/intrinsics/NativeError.mjs b/engine262/src/intrinsics/NativeError.mjs new file mode 100644 index 0000000..044754f --- /dev/null +++ b/engine262/src/intrinsics/NativeError.mjs @@ -0,0 +1,73 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + DefinePropertyOrThrow, + OrdinaryCreateFromConstructor, + ToString, +} from '../abstract-ops/all.mjs'; +import { + Descriptor, + Type, + Value, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { captureStack } from '../helpers.mjs'; +import { BootstrapConstructor, BootstrapPrototype } from './Bootstrap.mjs'; + +export function BootstrapNativeError(realmRec) { + for (const name of [ + 'EvalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError', + ]) { + const proto = BootstrapPrototype(realmRec, [ + ['name', new Value(name)], + ['message', new Value('')], + ], realmRec.Intrinsics['%Error.prototype%']); + + // #sec-nativeerror + const Constructor = ([message = Value.undefined], { NewTarget }) => { + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let newTarget; + if (Type(NewTarget) === 'Undefined') { + newTarget = surroundingAgent.activeFunctionObject; + } else { + newTarget = NewTarget; + } + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + const O = Q(OrdinaryCreateFromConstructor(newTarget, `%${name}.prototype%`, ['ErrorData'])); + // 3. If message is not undefined, then + if (message !== Value.undefined) { + // a. Let msg be ? ToString(message). + const msg = Q(ToString(message)); + // b. Let msgDesc be the PropertyDescriptor { [[Value]]: msg, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }. + const msgDesc = Descriptor({ + Value: msg, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }); + // c. Perform ! DefinePropertyOrThrow(O, "message", msgDesc). + X(DefinePropertyOrThrow(O, new Value('message'), msgDesc)); + } + // NON-SPEC + X(captureStack(O)); + // 4. Return O. + return O; + }; + Object.defineProperty(Constructor, 'name', { + value: `${name}Constructor`, + configurable: true, + }); + + const cons = BootstrapConstructor(realmRec, Constructor, name, 1, proto, []); + cons.Prototype = realmRec.Intrinsics['%Error%']; + + realmRec.Intrinsics[`%${name}.prototype%`] = proto; + realmRec.Intrinsics[`%${name}%`] = cons; + } +} diff --git a/engine262/src/intrinsics/Number.mjs b/engine262/src/intrinsics/Number.mjs new file mode 100644 index 0000000..309f14e --- /dev/null +++ b/engine262/src/intrinsics/Number.mjs @@ -0,0 +1,120 @@ +import { + IsInteger, + OrdinaryCreateFromConstructor, + ToNumeric, +} from '../abstract-ops/all.mjs'; +import { + Descriptor, + Type, + Value, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// 20.1.1.1 #sec-number-constructor-number-value +function NumberConstructor([value], { NewTarget }) { + let n; + if (value !== undefined) { + const prim = Q(ToNumeric(value)); + if (Type(prim) === 'BigInt') { + n = new Value(Number(prim.bigintValue())); + } else { + n = prim; + } + } else { + n = new Value(0); + } + if (NewTarget === Value.undefined) { + return n; + } + const O = OrdinaryCreateFromConstructor(NewTarget, '%Number.prototype%', ['NumberData']); + O.NumberData = n; + return O; +} + +// 20.1.2.2 #sec-number.isfinite +function Number_isFinite([number = Value.undefined]) { + if (Type(number) !== 'Number') { + return Value.false; + } + + if (number.isNaN() || number.isInfinity()) { + return Value.false; + } + return Value.true; +} + +// 20.1.2.3 #sec-number.isinteger +function Number_isInteger([number = Value.undefined]) { + return X(IsInteger(number)); +} + +// 20.1.2.4 #sec-number.isnan +function Number_isNaN([number = Value.undefined]) { + if (Type(number) !== 'Number') { + return Value.false; + } + + if (number.isNaN()) { + return Value.true; + } + return Value.false; +} + +// 20.1.2.5 #sec-number.issafeinteger +function Number_isSafeInteger([number = Value.undefined]) { + if (Type(number) !== 'Number') { + return Value.false; + } + + if (X(IsInteger(number)) === Value.true) { + if (Math.abs(number.numberValue()) <= (2 ** 53) - 1) { + return Value.true; + } + } + + return Value.false; +} + +export function BootstrapNumber(realmRec) { + const override = { + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }; + const numberConstructor = BootstrapConstructor(realmRec, NumberConstructor, 'Number', 1, realmRec.Intrinsics['%Number.prototype%'], [ + ['EPSILON', new Value(Number.EPSILON), undefined, override], + ['MAX_SAFE_INTEGER', new Value(Number.MAX_SAFE_INTEGER), undefined, override], + ['MAX_VALUE', new Value(Number.MAX_VALUE), undefined, override], + ['MIN_SAFE_INTEGER', new Value(Number.MIN_SAFE_INTEGER), undefined, override], + ['MIN_VALUE', new Value(Number.MIN_VALUE), undefined, override], + ['NaN', new Value(NaN), undefined, override], + ['NEGATIVE_INFINITY', new Value(-Infinity), undefined, override], + ['POSITIVE_INFINITY', new Value(Infinity), undefined, override], + + ['isFinite', Number_isFinite, 1], + ['isInteger', Number_isInteger, 1], + ['isNaN', Number_isNaN, 1], + ['isSafeInteger', Number_isSafeInteger, 1], + ]); + + // 20.1.2.12 #sec-number.parsefloat + // The value of the Number.parseFloat data property is the same built-in function object that is the value of the parseFloat property of the global object defined in 18.2.4. + X(numberConstructor.DefineOwnProperty(new Value('parseFloat'), Descriptor({ + Value: realmRec.Intrinsics['%parseFloat%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + // 20.1.2.13 #sec-number.parseint + // The value of the Number.parseInt data property is the same built-in function object that is the value of the parseInt property of the global object defined in 18.2.5. + X(numberConstructor.DefineOwnProperty(new Value('parseInt'), Descriptor({ + Value: realmRec.Intrinsics['%parseInt%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%Number%'] = numberConstructor; +} diff --git a/engine262/src/intrinsics/NumberPrototype.mjs b/engine262/src/intrinsics/NumberPrototype.mjs new file mode 100644 index 0000000..4f5c6ed --- /dev/null +++ b/engine262/src/intrinsics/NumberPrototype.mjs @@ -0,0 +1,117 @@ +import { + Type, + Value, + NumberValue, +} from '../value.mjs'; +import { + Assert, + ToInteger, + ToString, +} from '../abstract-ops/all.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +function thisNumberValue(value) { + if (Type(value) === 'Number') { + return value; + } + if (Type(value) === 'Object' && 'NumberData' in value) { + const n = value.NumberData; + Assert(Type(n) === 'Number'); + return n; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Number', value); +} + +// #sec-number.prototype.toexponential +function NumberProto_toExponential([fractionDigits = Value.undefined], { thisValue }) { + const x = Q(thisNumberValue(thisValue)); + const f = Q(ToInteger(fractionDigits)).numberValue(); + Assert(fractionDigits !== Value.undefined || f === 0); + if (!x.isFinite()) { + return NumberValue.toString(x); + } + if (f < 0 || f > 100) { + return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toExponential'); + } + return new Value(x.numberValue().toExponential(fractionDigits === Value.undefined ? undefined : f)); +} + +// 20.1.3.3 #sec-number.prototype.tofixed +function NumberProto_toFixed([fractionDigits = Value.undefined], { thisValue }) { + const x = Q(thisNumberValue(thisValue)); + const f = Q(ToInteger(fractionDigits)).numberValue(); + Assert(fractionDigits !== Value.undefined || f === 0); + if (f < 0 || f > 100) { + return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toFixed'); + } + if (!x.isFinite()) { + return X(NumberValue.toString(x)); + } + return new Value(x.numberValue().toFixed(f)); +} + +// 20.1.3.4 #sec-number.prototype.tolocalestring +function NumberProto_toLocaleString(args, { thisValue }) { + return NumberProto_toString([], { thisValue }); +} + +// 20.1.3.5 #sec-number.prototype.toprecision +function NumberProto_toPrecision([precision = Value.undefined], { thisValue }) { + const x = Q(thisNumberValue(thisValue)); + if (precision === Value.undefined) { + return X(ToString(x)); + } + const p = Q(ToInteger(precision)).numberValue(); + if (!x.isFinite()) { + return X(NumberValue.toString(x)); + } + if (p < 1 || p > 100) { + return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toPrecision'); + } + return new Value(x.numberValue().toPrecision(p)); +} + +// 20.1.3.6 #sec-number.prototype.tostring +function NumberProto_toString([radix = Value.undefined], { thisValue }) { + const x = Q(thisNumberValue(thisValue)); + let radixNumber; + if (radix === Value.undefined) { + radixNumber = 10; + } else { + radixNumber = Q(ToInteger(radix)).numberValue(); + } + if (radixNumber < 2 || radixNumber > 36) { + return surroundingAgent.Throw('TypeError', 'NumberFormatRange', 'toString'); + } + if (radixNumber === 10) { + return X(ToString(x)); + } + // FIXME(devsnek): Return the String representation of this Number + // value using the radix specified by radixNumber. Letters a-z are + // used for digits with values 10 through 35. The precise algorithm + // is implementation-dependent, however the algorithm should be a + // generalization of that specified in 7.1.12.1. + return new Value(x.numberValue().toString(radixNumber)); +} + +// 20.1.3.7 #sec-number.prototype.valueof +function NumberProto_valueOf(args, { thisValue }) { + return Q(thisNumberValue(thisValue)); +} + +export function BootstrapNumberPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['toExponential', NumberProto_toExponential, 1], + ['toFixed', NumberProto_toFixed, 1], + ['toLocaleString', NumberProto_toLocaleString, 0], + ['toPrecision', NumberProto_toPrecision, 1], + ['toString', NumberProto_toString, 1], + ['valueOf', NumberProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%']); + + proto.NumberData = new Value(0); + + realmRec.Intrinsics['%Number.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Object.mjs b/engine262/src/intrinsics/Object.mjs new file mode 100644 index 0000000..b1498d8 --- /dev/null +++ b/engine262/src/intrinsics/Object.mjs @@ -0,0 +1,430 @@ +import { + Type, + Value, +} from '../value.mjs'; +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Assert, + CreateArrayFromList, + CreateDataProperty, + DefinePropertyOrThrow, + CreateDataPropertyOrThrow, + EnumerableOwnPropertyNames, + FromPropertyDescriptor, + Get, + IsExtensible, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + RequireObjectCoercible, + SameValue, + Set, + SetIntegrityLevel, + TestIntegrityLevel, + ToObject, + ToPropertyDescriptor, + ToPropertyKey, + CreateBuiltinFunction, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { AddEntriesFromIterable } from './Map.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-object-value +function ObjectConstructor([value = Value.undefined], { NewTarget }) { + // 1. If NewTarget is neither undefined nor the active function, then + if (NewTarget !== Value.undefined && NewTarget !== surroundingAgent.activeFunctionObject) { + // a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%"). + return OrdinaryCreateFromConstructor(NewTarget, '%Object.prototype%'); + } + // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%). + if (value === Value.null || value === Value.undefined) { + return OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + } + // 3. Return ! ToObject(value). + return X(ToObject(value)); +} + +// #sec-object.assign +function Object_assign([target = Value.undefined, ...sources]) { + // 1. Let to be ? ToObject(target). + const to = Q(ToObject(target)); + // 2. If only one argument was passed, return to. + if (sources.length === 0) { + return to; + } + // 3. Let sources be the List of argument values starting with the second argument. + // 4. For each element nextSource of sources, in ascending index order, do + for (const nextSource of sources) { + // a. If nextSource is neither undefined nor null, then + if (nextSource !== Value.undefined && nextSource !== Value.null) { + // i. Let from be ! ToObject(nextSource). + const from = X(ToObject(nextSource)); + // ii. Let keys be ? from.[[OwnPropertyKeys]](). + const keys = Q(from.OwnPropertyKeys()); + // iii. For each element nextKey of keys in List order, do + for (const nextKey of keys) { + // 1. Let desc be ? from.[[GetOwnProperty]](nextKey). + const desc = Q(from.GetOwnProperty(nextKey)); + // 2. If desc is not undefined and desc.[[Enumerable]] is true, then + if (desc !== Value.undefined && desc.Enumerable === Value.true) { + // a. Let propValue be ? Get(from, nextKey). + const propValue = Q(Get(from, nextKey)); + // b. Perform ? Set(to, nextKey, propValue, true). + Q(Set(to, nextKey, propValue, Value.true)); + } + } + } + } + // 5. Return to. + return to; +} + +// #sec-object.create +function Object_create([O = Value.undefined, Properties = Value.undefined]) { + // 1. If Type(O) is neither Object nor Null, throw a TypeError exception. + if (Type(O) !== 'Object' && Type(O) !== 'Null') { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // 2. Let obj be OrdinaryObjectCreate(O). + const obj = OrdinaryObjectCreate(O); + // 3. If Properties is not undefined, then + if (Properties !== Value.undefined) { + // a. Return ? ObjectDefineProperties(obj, Properties). + return Q(ObjectDefineProperties(obj, Properties)); + } + // 4. Return obj. + return obj; +} + +// #sec-object.defineproperties +function Object_defineProperties([O = Value.undefined, Properties = Value.undefined]) { + // 1. Return ? ObjectDefineProperties(O, Properties). + return Q(ObjectDefineProperties(O, Properties)); +} + +// #sec-objectdefineproperties ObjectDefineProperties +function ObjectDefineProperties(O, Properties) { + // 1. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 2. Let props be ? ToObject(Properties). + const props = Q(ToObject(Properties)); + // 3. Let keys be ? props.[[OwnPropertyKeys]](). + const keys = Q(props.OwnPropertyKeys()); + // 4. Let descriptors be a new empty List. + const descriptors = []; + // 5. For each element nextKey of keys in List order, do + for (const nextKey of keys) { + // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey). + const propDesc = Q(props.GetOwnProperty(nextKey)); + // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then + if (propDesc !== Value.undefined && propDesc.Enumerable === Value.true) { + // i. Let descObj be ? Get(props, nextKey). + const descObj = Q(Get(props, nextKey)); + // ii. Let desc be ? ToPropertyDescriptor(descObj). + const desc = Q(ToPropertyDescriptor(descObj)); + // iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors. + descriptors.push([nextKey, desc]); + } + } + // 6. For each pair from descriptors in list order, do + for (const pair of descriptors) { + // a. Let P be the first element of pair. + const P = pair[0]; + // b. Let desc be the second element of pair. + const desc = pair[1]; + // c. Perform ? DefinePropertyOrThrow(O, P, desc). + Q(DefinePropertyOrThrow(O, P, desc)); + } + // 7. Return O. + return O; +} + +// #sec-object.defineproperty +function Object_defineProperty([O = Value.undefined, P = Value.undefined, Attributes = Value.undefined]) { + // 1. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 2. Let key be ? ToPropertyKey(P). + const key = Q(ToPropertyKey(P)); + // 3. Let desc be ? ToPropertyDescriptor(Attributes). + const desc = Q(ToPropertyDescriptor(Attributes)); + // 4. Perform ? DefinePropertyOrThrow(O, key, desc). + Q(DefinePropertyOrThrow(O, key, desc)); + // 5. Return O. + return O; +} + +// #sec-object.entries +function Object_entries([O = Value.undefined]) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value). + const nameList = Q(EnumerableOwnPropertyNames(obj, 'key+value')); + // 3. Return CreateArrayFromList(nameList). + return CreateArrayFromList(nameList); +} + +// #sec-object.freeze +function Object_freeze([O = Value.undefined]) { + // 1. If Type(O) is not Object, return O. + if (Type(O) !== 'Object') { + return O; + } + // 2. Let status be ? SetIntegrityLevel(O, frozen). + const status = Q(SetIntegrityLevel(O, 'frozen')); + // 3. If status is false, throw a TypeError exception. + if (status === Value.false) { + return surroundingAgent.Throw('TypeError', 'UnableToFreeze', O); + } + // 4. Return O. + return O; +} + +// #sec-create-data-property-on-object-functions +function CreateDataPropertyOnObjectFunctions([key, value], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Assert: Type(O) is Object. + Assert(Type(O) === 'Object'); + // 3. Assert: O is an extensible ordinary object. + Assert(O.Extensible === Value.true); + // 4. Let propertyKey be ? ToPropertyKey(key). + const propertyKey = Q(ToPropertyKey(key)); + // 5. Perform ! CreateDataPropertyOrThrow(O, propertyKey, value). + X(CreateDataPropertyOrThrow(O, propertyKey, value)); + // 6. Return undefined. + return Value.undefined; +} + +// #sec-object.fromentries +function Object_fromEntries([iterable = Value.undefined]) { + // 1. Perform ? RequireObjectCoercible(iterable). + Q(RequireObjectCoercible(iterable)); + // 2. Let obj be OrdinaryObjectCreate(%Object.prototype%). + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // 3. Assert: obj is an extensible ordinary object with no own properties. + Assert(obj.Extensible === Value.true && obj.properties.size === 0); + // 4. Let stepsDefine be the algorithm steps defined in CreateDataPropertyOnObject Functions. + const stepsDefine = CreateDataPropertyOnObjectFunctions; + // 5. Let adder be ! CreateBuiltinFunction(stepsDefine, « »). + const adder = X(CreateBuiltinFunction(stepsDefine, [])); + // 6. Return ? AddEntriesFromIterable(obj, iterable, adder). + return Q(AddEntriesFromIterable(obj, iterable, adder)); +} + +// #sec-object.getownpropertydescriptor +function Object_getOwnPropertyDescriptor([O = Value.undefined, P = Value.undefined]) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let key be ? ToPropertyKey(P). + const key = Q(ToPropertyKey(P)); + // 3. Let desc be ? obj.[[GetOwnProperty]](key). + const desc = Q(obj.GetOwnProperty(key)); + // 4. Return FromPropertyDescriptor(desc). + return FromPropertyDescriptor(desc); +} + +// #sec-object.getownpropertydescriptors +function Object_getOwnPropertyDescriptors([O = Value.undefined]) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let ownKeys be ? obj.[[OwnPropertyKeys]](). + const ownKeys = Q(obj.OwnPropertyKeys()); + // 3. Let descriptors be ! OrdinaryObjectCreate(%Object.prototype%). + const descriptors = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'))); + // 4. For each element key of ownKeys in List order, do + for (const key of ownKeys) { + // a. Let desc be ? obj.[[GetOwnProperty]](key). + const desc = Q(obj.GetOwnProperty(key)); + // b. Let descriptor be ! FromPropertyDescriptor(desc). + const descriptor = X(FromPropertyDescriptor(desc)); + // c. If descriptor is not undefined, perform ! CreateDataPropertyOrThrow(descriptors, key, descriptor). + if (descriptor !== Value.undefined) { + X(CreateDataProperty(descriptors, key, descriptor)); + } + } + // 5. Return descriptors. + return descriptors; +} + +// #sec-getownpropertykeys +function GetOwnPropertyKeys(O, type) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let keys be ? obj.[[OwnPropertyKeys]](). + const keys = Q(obj.OwnPropertyKeys()); + // 3. Let nameList be a new empty List. + const nameList = []; + // 4. For each element nextKey of keys in List order, do + keys.forEach((nextKey) => { + // a. If Type(nextKey) is Symbol and type is symbol or Type(nextKey) is String and type is string, then + if (Type(nextKey) === type) { + // i. Append nextKey as the last element of nameList. + nameList.push(nextKey); + } + }); + return CreateArrayFromList(nameList); +} + +// #sec-object.getownpropertynames +function Object_getOwnPropertyNames([O = Value.undefined]) { + // 1. Return ? GetOwnPropertyKeys(O, string). + return Q(GetOwnPropertyKeys(O, 'String')); +} + +// #sec-object.getownpropertysymbols +function Object_getOwnPropertySymbols([O = Value.undefined]) { + // 1. Return ? GetOwnPropertyKeys(O, symbol). + return Q(GetOwnPropertyKeys(O, 'Symbol')); +} + +// #sec-object.getprototypeof +function Object_getPrototypeOf([O = Value.undefined]) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Return ? obj.[[GetPrototypeOf]](). + return Q(obj.GetPrototypeOf()); +} + +// #sec-object.is +function Object_is([value1 = Value.undefined, value2 = Value.undefined]) { + // 1. Return SameValue(value1, value2). + return SameValue(value1, value2); +} + +// #sec-object.isextensible +function Object_isExtensible([O = Value.undefined]) { + // 1. If Type(O) is not Object, return false. + if (Type(O) !== 'Object') { + return Value.false; + } + // 2. Return ? IsExtensible(O). + return Q(IsExtensible(O)); +} + +// #sec-object.isfrozen +function Object_isFrozen([O = Value.undefined]) { + // 1. If Type(O) is not Object, return true. + if (Type(O) !== 'Object') { + return Value.true; + } + // 2. Return ? TestIntegrityLevel(O, frozen). + return Q(TestIntegrityLevel(O, 'frozen')); +} + +// #sec-object.issealed +function Object_isSealed([O = Value.undefined]) { + // 1. If Type(O) is not Object, return true. + if (Type(O) !== 'Object') { + return Value.true; + } + // 2. Return ? TestIntegrityLevel(O, sealed). + return Q(TestIntegrityLevel(O, 'sealed')); +} + +// #sec-object.keys +function Object_keys([O = Value.undefined]) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key). + const nameList = Q(EnumerableOwnPropertyNames(obj, 'key')); + // 3. Return CreateArrayFromList(nameList). + return CreateArrayFromList(nameList); +} + +// #sec-object.preventextensions +function Object_preventExtensions([O = Value.undefined]) { + // 1. If Type(O) is not Object, return O. + if (Type(O) !== 'Object') { + return O; + } + // 2. Let status be ? O.[[PreventExtensions]](). + const status = Q(O.PreventExtensions()); + // 3. If status is false, throw a TypeError exception. + if (status === Value.false) { + return surroundingAgent.Throw('TypeError', 'UnableToPreventExtensions', O); + } + // 4. Return O. + return O; +} + +// #sec-object.seal +function Object_seal([O = Value.undefined]) { + // 1. If Type(O) is not Object, return O. + if (Type(O) !== 'Object') { + return O; + } + // 2. Let status be ? SetIntegrityLevel(O, sealed). + const status = Q(SetIntegrityLevel(O, 'sealed')); + // 3. If status is false, throw a TypeError exception. + if (status === Value.false) { + return surroundingAgent.Throw('TypeError', 'UnableToSeal', O); + } + // 4. Return O. + return O; +} + +// #sec-object.setprototypeof +function Object_setPrototypeOf([O = Value.undefined, proto = Value.undefined]) { + // 1. Set O to ? RequireObjectCoercible(O). + O = Q(RequireObjectCoercible(O)); + // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. + if (Type(proto) !== 'Object' && Type(proto) !== 'Null') { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // 3. If Type(O) is not Object, return O. + if (Type(O) !== 'Object') { + return O; + } + // 4. Let status be ? O.[[SetPrototypeOf]](proto). + const status = Q(O.SetPrototypeOf(proto)); + // 5. If status is false, throw a TypeError exception. + if (status === Value.false) { + return surroundingAgent.Throw('TypeError', 'ObjectSetPrototype'); + } + // 6. Return O. + return O; +} + +// #sec-object.values +function Object_values([O = Value.undefined]) { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, value). + const nameList = Q(EnumerableOwnPropertyNames(obj, 'value')); + // 3. Return CreateArrayFromList(nameList). + return CreateArrayFromList(nameList); +} + +export function BootstrapObject(realmRec) { + const objectConstructor = BootstrapConstructor(realmRec, ObjectConstructor, 'Object', 1, realmRec.Intrinsics['%Object.prototype%'], [ + ['assign', Object_assign, 2], + ['create', Object_create, 2], + ['defineProperties', Object_defineProperties, 2], + ['defineProperty', Object_defineProperty, 3], + ['entries', Object_entries, 1], + ['freeze', Object_freeze, 1], + ['fromEntries', Object_fromEntries, 1], + ['getOwnPropertyDescriptor', Object_getOwnPropertyDescriptor, 2], + ['getOwnPropertyDescriptors', Object_getOwnPropertyDescriptors, 1], + ['getOwnPropertyNames', Object_getOwnPropertyNames, 1], + ['getOwnPropertySymbols', Object_getOwnPropertySymbols, 1], + ['getPrototypeOf', Object_getPrototypeOf, 1], + ['is', Object_is, 2], + ['isExtensible', Object_isExtensible, 1], + ['isFrozen', Object_isFrozen, 1], + ['isSealed', Object_isSealed, 1], + ['keys', Object_keys, 1], + ['preventExtensions', Object_preventExtensions, 1], + ['seal', Object_seal, 1], + ['setPrototypeOf', Object_setPrototypeOf, 2], + ['values', Object_values, 1], + ]); + + realmRec.Intrinsics['%Object%'] = objectConstructor; +} diff --git a/engine262/src/intrinsics/ObjectPrototype.mjs b/engine262/src/intrinsics/ObjectPrototype.mjs new file mode 100644 index 0000000..66e6cb1 --- /dev/null +++ b/engine262/src/intrinsics/ObjectPrototype.mjs @@ -0,0 +1,142 @@ +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + Get, + HasOwnProperty, + Invoke, + IsArray, + SameValue, + ToObject, + ToPropertyKey, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { assignProps } from './Bootstrap.mjs'; + +// #sec-object.prototype.hasownproperty +function ObjectProto_hasOwnProperty([V = Value.undefined], { thisValue }) { + // 1. Let P be ? ToPropertyKey(V). + const P = Q(ToPropertyKey(V)); + // 2. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 3. Return ? HasOwnProperty(O, P). + return HasOwnProperty(O, P); +} + +// #sec-object.prototype.isprototypeof +function ObjectProto_isPrototypeOf([V = Value.undefined], { thisValue }) { + // 1. If Type(V) is not Object, return false. + if (Type(V) !== 'Object') { + return Value.false; + } + // 2. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 3. Repeat, + while (true) { + // a. Set V to ? V.[[GetPrototypeOf]](). + V = Q(V.GetPrototypeOf()); + // b. If V is null, return false. + if (V === Value.null) { + return Value.false; + } + // c. If SameValue(O, V) is true, return true. + if (SameValue(O, V) === Value.true) { + return Value.true; + } + } +} + +// #sec-object.prototype.propertyisenumerable +function ObjectProto_propertyIsEnumerable([V = Value.undefined], { thisValue }) { + // 1. Let P be ? ToPropertyKey(V). + const P = Q(ToPropertyKey(V)); + // 2. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 3. Let desc be ? O.[[GetOwnProperty]](P). + const desc = Q(O.GetOwnProperty(P)); + // 4. If desc is undefined, return false. + if (Type(desc) === 'Undefined') { + return Value.false; + } + // 5. Return desc.[[Enumerable]]. + return desc.Enumerable; +} + +// #sec-object.prototype.tolocalestring +function ObjectProto_toLocaleString(argList, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Return ? Invoke(O, "toString"). + return Q(Invoke(O, new Value('toString'))); +} + +// #sec-object.prototype.tostring +function ObjectProto_toString(argList, { thisValue }) { + // 1. If the this value is undefined, return "[object Undefined]". + if (thisValue === Value.undefined) { + return new Value('[object Undefined]'); + } + // 2. If the this value is null, return "[object Null]". + if (thisValue === Value.null) { + return new Value('[object Null]'); + } + // 3. Let O be ! ToObject(this value). + const O = X(ToObject(thisValue)); + // 4. Let isArray be ? IsArray(O). + const isArray = Q(IsArray(O)); + let builtinTag; + // 5. If isArray is true, let builtinTag be "Array". + if (isArray === Value.true) { + builtinTag = 'Array'; + } else if ('ParameterMap' in O) { // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments". + builtinTag = 'Arguments'; + } else if ('Call' in O) { // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". + builtinTag = 'Function'; + } else if ('ErrorData' in O) { // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error". + builtinTag = 'Error'; + } else if ('BooleanData' in O) { // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean". + builtinTag = 'Boolean'; + } else if ('NumberData' in O) { // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number". + builtinTag = 'Number'; + } else if ('StringData' in O) { // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String". + builtinTag = 'String'; + } else if ('DateValue' in O) { // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date". + builtinTag = 'Date'; + } else if ('RegExpMatcher' in O) { // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp". + builtinTag = 'RegExp'; + } else { // 14. Else, let builtinTag be "Object". + builtinTag = 'Object'; + } + // 15. Let tag be ? Get(O, @@toStringTag). + let tag = Q(Get(O, wellKnownSymbols.toStringTag)); + // 16. If Type(tag) is not String, set tag to builtinTag. + if (Type(tag) !== 'String') { + tag = builtinTag; + } + // 17. Return the string-concatenation of "[object ", tag, and "]". + return new Value(`[object ${tag.stringValue ? tag.stringValue() : tag}]`); +} + +// #sec-object.prototype.valueof +function ObjectProto_valueOf(argList, { thisValue }) { + // 1. Return ? ToObject(this value). + return Q(ToObject(thisValue)); +} + +export function BootstrapObjectPrototype(realmRec) { + const proto = realmRec.Intrinsics['%Object.prototype%']; + + assignProps(realmRec, proto, [ + ['hasOwnProperty', ObjectProto_hasOwnProperty, 1], + ['isPrototypeOf', ObjectProto_isPrototypeOf, 1], + ['propertyIsEnumerable', ObjectProto_propertyIsEnumerable, 1], + ['toLocaleString', ObjectProto_toLocaleString, 0], + ['toString', ObjectProto_toString, 0], + ['valueOf', ObjectProto_valueOf, 0], + ]); + + realmRec.Intrinsics['%Object.prototype.toString%'] = X(Get(proto, new Value('toString'))); + realmRec.Intrinsics['%Object.prototype.valueOf%'] = X(Get(proto, new Value('valueOf'))); +} diff --git a/engine262/src/intrinsics/Promise.mjs b/engine262/src/intrinsics/Promise.mjs new file mode 100644 index 0000000..cfa53af --- /dev/null +++ b/engine262/src/intrinsics/Promise.mjs @@ -0,0 +1,681 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Descriptor, + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + Assert, + Call, + CreateArrayFromList, + CreateBuiltinFunction, + CreateDataProperty, + CreateResolvingFunctions, + DefinePropertyOrThrow, + Get, + GetIterator, + Invoke, + IsCallable, + IsConstructor, + IteratorClose, + IteratorStep, + IteratorValue, + NewPromiseCapability, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + PromiseCapabilityRecord, + PromiseResolve, + SetFunctionLength, + SetFunctionName, +} from '../abstract-ops/all.mjs'; +import { + AbruptCompletion, Completion, + ThrowCompletion, + IfAbruptRejectPromise, + ReturnIfAbrupt, + EnsureCompletion, + Q, X, +} from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-promise-executor +function PromiseConstructor([executor = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. If IsCallable(executor) is false, throw a TypeError exception. + if (IsCallable(executor) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', executor); + } + // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »). + const promise = Q(OrdinaryCreateFromConstructor(NewTarget, '%Promise.prototype%', [ + 'PromiseState', + 'PromiseResult', + 'PromiseFulfillReactions', + 'PromiseRejectReactions', + 'PromiseIsHandled', + ])); + // 4. Set promise.[[PromiseState]] to pending. + promise.PromiseState = 'pending'; + // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. + promise.PromiseFulfillReactions = []; + // 6. Set promise.[[PromiseFulfillReactions]] to a new empty List. + promise.PromiseRejectReactions = []; + // 7. Set promise.[[PromiseIsHandled]] to false. + promise.PromiseIsHandled = Value.false; + // 8. Let resolvingFunctions be CreateResolvingFunctions(promise). + const resolvingFunctions = CreateResolvingFunctions(promise); + // 9. Let completion be Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). + const completion = Call(executor, Value.undefined, [ + resolvingFunctions.Resolve, resolvingFunctions.Reject, + ]); + // 10. If completion is an abrupt completion, then + if (completion instanceof AbruptCompletion) { + // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). + Q(Call(resolvingFunctions.Reject, Value.undefined, [completion.Value])); + } + // 11. Return promise. + return promise; +} + +// #sec-promise.all-resolve-element-functions +function PromiseAllResolveElementFunctions([x = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value === true) { + return Value.undefined; + } + alreadyCalled.Value = true; + const index = F.Index; + const values = F.Values; + const promiseCapability = F.Capability; + const remainingElementsCount = F.RemainingElements; + values[index] = x; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const valuesArray = CreateArrayFromList(values); + return Q(Call(promiseCapability.Resolve, Value.undefined, [valuesArray])); + } + return Value.undefined; +} + +// #sec-getpromiseresolve +function GetPromiseResolve(promiseConstructor) { + // 1. Assert: IsConstructor(promiseConstructor) is true. + Assert(IsConstructor(promiseConstructor) === Value.true); + // 2. Let promiseResolve be ? Get(promiseConstructor, "resolve"). + const promiseResolve = Q(Get(promiseConstructor, new Value('resolve'))); + // 3. If IsCallable(promiseResolve) is false, throw a TypeError exception. + if (IsCallable(promiseResolve) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', promiseResolve); + } + // 4. Return promiseResolve. + return promiseResolve; +} + +// #sec-performpromiseall +function PerformPromiseAll(iteratorRecord, constructor, resultCapability, promiseResolve) { + // 1. Assert: IsConstructor(constructor) is true. + Assert(IsConstructor(constructor) === Value.true); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve) === Value.true); + // 4. Let values be a new empty List. + const values = []; + // 5. Let remainingElementsCount be the Record { [[Value]]: 1 }. + const remainingElementsCount = { Value: 1 }; + // 6. Let index be 0. + let index = 0; + // 7. Repeat, + while (true) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, then + if (next === Value.false) { + // i. Set iteratorRecord.[[Done]] to true. + iteratorRecord.Done = Value.true; + // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remainingElementsCount.Value -= 1; + // iii. If remainingElementsCount.[[Value]] is 0, then + if (remainingElementsCount.Value === 0) { + // 1. Let valuesArray be ! CreateArrayFromList(values). + const valuesArray = CreateArrayFromList(values); + // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »). + Q(Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + // iv. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // e. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // g. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // h. Append undefined to values. + values.push(Value.undefined); + // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + const nextPromise = Q(Call(promiseResolve, constructor, [nextValue])); + // j. Let steps be the algorithm steps defined in Promise.all Resolve Element Functions. + const steps = PromiseAllResolveElementFunctions; + // k. Let resolveElement be ! CreateBuiltinFunction(steps, « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). + const resolveElement = X(CreateBuiltinFunction(steps, [ + 'AlreadyCalled', 'Index', 'Values', 'Capability', 'RemainingElements', + ])); + X(SetFunctionLength(resolveElement, new Value(1))); + X(SetFunctionName(resolveElement, new Value(''))); + // l. Set resolveElement.[[AlreadyCalled]] to the Record { [[Value]]: false }. + resolveElement.AlreadyCalled = { Value: false }; + // m. Set resolveElement.[[Index]] to index. + resolveElement.Index = index; + // n. Set resolveElement.[[Values]] to values. + resolveElement.Values = values; + // o. Set resolveElement.[[Capability]] to resultCapability. + resolveElement.Capability = resultCapability; + // p. Set resolveElement.[[RemainingElements]] to remainingElementsCount. + resolveElement.RemainingElements = remainingElementsCount; + // q. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. + remainingElementsCount.Value += 1; + // r. Perform ? Invoke(nextPromise, "then", « resolveElement, resultCapability.[[Reject]] »). + Q(Invoke(nextPromise, new Value('then'), [resolveElement, resultCapability.Reject])); + // s. Set index to index + 1. + index += 1; + } +} + +// #sec-promise.all +function Promise_all([iterable = Value.undefined], { thisValue }) { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(NewPromiseCapability(C)); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = GetPromiseResolve(C); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = GetIterator(iterable); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + // 7. Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve). + let result = EnsureCompletion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)); + // 8. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result). + if (iteratorRecord.Done === Value.false) { + result = IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return Completion(result). + return Completion(result); +} + +function PromiseAllSettledResolveElementFunctions([x = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value === true) { + return Value.undefined; + } + alreadyCalled.Value = true; + const index = F.Index; + const values = F.Values; + const promiseCapability = F.Capability; + const remainingElementsCount = F.RemainingElements; + const obj = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'))); + X(CreateDataProperty(obj, new Value('status'), new Value('fulfilled'))); + X(CreateDataProperty(obj, new Value('value'), x)); + values[index] = obj; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const valuesArray = X(CreateArrayFromList(values)); + return Q(Call(promiseCapability.Resolve, Value.undefined, [valuesArray])); + } + return Value.undefined; +} + +function PromiseAllSettledRejectElementFunctions([x = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value === true) { + return Value.undefined; + } + alreadyCalled.Value = true; + const index = F.Index; + const values = F.Values; + const promiseCapability = F.Capability; + const remainingElementsCount = F.RemainingElements; + const obj = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'))); + X(CreateDataProperty(obj, new Value('status'), new Value('rejected'))); + X(CreateDataProperty(obj, new Value('reason'), x)); + values[index] = obj; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const valuesArray = X(CreateArrayFromList(values)); + return Q(Call(promiseCapability.Resolve, Value.undefined, [valuesArray])); + } + return Value.undefined; +} + +// #sec-performpromiseallsettled +function PerformPromiseAllSettled(iteratorRecord, constructor, resultCapability, promiseResolve) { + // 1. Assert: ! IsConstructor(constructor) is true. + Assert(X(IsConstructor(constructor) === Value.true)); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve) === Value.true); + // 4. Let values be a new empty List. + const values = []; + // 5. Let remainingElementsCount be the Record { [[Value]]: 1 }. + const remainingElementsCount = { Value: 1 }; + // 6. Let index be 0. + let index = 0; + // 7. Repeat, + while (true) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. Let next be IteratorStep(iteratorRecord). + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, + if (next === Value.false) { + // i. Set iteratorRecord.[[Done]] to true. + iteratorRecord.Done = Value.true; + // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remainingElementsCount.Value -= 1; + // iii. If remainingElementsCount.[[Value]] is 0, then + if (remainingElementsCount.Value === 0) { + // 1. Let valuesArray be ! CreateArrayFromList(values). + const valuesArray = X(CreateArrayFromList(values)); + // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »). + Q(Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + // iv. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // e. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // g. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // h. Append undefined to values. + values.push(Value.undefined); + // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + const nextPromise = Q(Call(promiseResolve, constructor, [nextValue])); + // j. Let steps be the algorithm steps defined in Promise.allSettled Resolve Element Functions. + const steps = PromiseAllSettledResolveElementFunctions; + // k. Let resolveElement be ! CreateBuiltinFunction(steps, « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). + const resolveElement = X(CreateBuiltinFunction(steps, [ + 'AlreadyCalled', + 'Index', + 'Values', + 'Capability', + 'RemainingElements', + ])); + X(SetFunctionLength(resolveElement, new Value(1))); + X(SetFunctionName(resolveElement, new Value(''))); + // l. Let alreadyCalled be the Record { [[Value]]: false }. + const alreadyCalled = { Value: false }; + // m. Set resolveElement.[[AlreadyCalled]] to alreadyCalled. + resolveElement.AlreadyCalled = alreadyCalled; + // n. Set resolveElement.[[Index]] to index. + resolveElement.Index = index; + // o. Set resolveElement.[[Values]] to values. + resolveElement.Values = values; + // p. Set resolveElement.[[Capability]] to resultCapability. + resolveElement.Capability = resultCapability; + // q. Set resolveElement.[[RemainingElements]] to remainingElementsCount. + resolveElement.RemainingElements = remainingElementsCount; + // r. Let rejectSteps be the algorithm steps defined in Promise.allSettled Reject Element Functions. + const rejectSteps = PromiseAllSettledRejectElementFunctions; + // s. Let rejectElement be ! CreateBuiltinFunction(rejectSteps, « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). + const rejectElement = X(CreateBuiltinFunction(rejectSteps, [ + 'AlreadyCalled', + 'Index', + 'Values', + 'Capability', + 'RemainingElements', + ])); + X(SetFunctionLength(rejectElement, new Value(1))); + X(SetFunctionName(rejectElement, new Value(''))); + // t. Set rejectElement.[[AlreadyCalled]] to alreadyCalled. + rejectElement.AlreadyCalled = alreadyCalled; + // u. Set rejectElement.[[Index]] to index. + rejectElement.Index = index; + // v. Set rejectElement.[[Values]] to values. + rejectElement.Values = values; + // w. Set rejectElement.[[Capability]] to resultCapability. + rejectElement.Capability = resultCapability; + // x. Set rejectElement.[[RemainingElements]] to remainingElementsCount. + rejectElement.RemainingElements = remainingElementsCount; + // y. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. + remainingElementsCount.Value += 1; + // z. Perform ? Invoke(nextPromise, "then", « resolveElement, rejectElement »). + Q(Invoke(nextPromise, new Value('then'), [resolveElement, rejectElement])); + // aa. Set index to index + 1. + index += 1; + } +} + +// #sec-promise.allsettled +function Promise_allSettled([iterable = Value.undefined], { thisValue }) { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(NewPromiseCapability(C)); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = GetPromiseResolve(C); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = GetIterator(iterable); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + // 7. Let result be PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve). + let result = EnsureCompletion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)); + // 8. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result). + if (iteratorRecord.Done === Value.false) { + result = IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return Completion(result). + return Completion(result); +} + +// #sec-promise.any-reject-element-functions +function PromiseAnyRejectElementFunctions([x = Value.undefined]) { + // 1. Let F be the active function object. + const F = surroundingAgent.activeFunctionObject; + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + const alreadyCalled = F.AlreadyCalled; + // 3. If alreadyCalled.[[Value]] is true, return undefined. + if (alreadyCalled.Value) { + return Value.undefined; + } + // 4. Set alreadyCalled.[[Value]] to true. + alreadyCalled.Value = true; + // 5. Let index be F.[[Index]]. + const index = F.Index; + // 6. Let errors be F.[[Errors]]. + const errors = F.Errors; + // 7. Let promiseCapability be F.[[Capability]]. + const promiseCapability = F.Capability; + // 8. Let remainingElementsCount be F.[[RemainingElements]]. + const remainingElementsCount = F.RemainingElements; + // 9. Set errors[index] to x. + errors[index] = x; + // 10. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remainingElementsCount.Value -= 1; + // 11. If remainingElementsCount.[[Value]] is 0, then + if (remainingElementsCount.Value === 0) { + // a. Let error be a newly created AggregateError object. + const error = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value; + // b. Perform ! DefinePropertyOrThrow(error, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: errors }). + X(DefinePropertyOrThrow(error, new Value('errors'), Descriptor({ + Configurable: Value.true, + Enmerable: Value.false, + Writable: Value.true, + Value: X(CreateArrayFromList(errors)), + }))); + // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). + return Q(Call(promiseCapability.Reject, Value.undefined, [error])); + } + // 12. Return undefined. + return Value.undefined; +} + +// #sec-performpromiseany +function PerformPromiseAny(iteratorRecord, constructor, resultCapability, promiseResolve) { + // 1. Assert: ! IsConstructor(constructor) is true. + Assert(X(IsConstructor(constructor)) === Value.true); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: ! IsCallable(promiseResolve) is true. + Assert(X(IsCallable(promiseResolve)) === Value.true); + // 4. Let errors be a new empty List. + const errors = []; + // 5. Let remainingElementsCount be a new Record { [[Value]]: 1 }. + const remainingElementsCount = { Value: 1 }; + // 6. Let index be 0. + let index = 0; + // 7. Repeat, + while (true) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, then + if (next === Value.false) { + // i. Set iteratorRecord.[[Done]] to true. + iteratorRecord.Done = Value.true; + // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remainingElementsCount.Value -= 1; + // iii. If remainingElementsCount.[[Value]] is 0, then + if (remainingElementsCount.Value === 0) { + // 1. Let error be a newly created AggregateError object. + const error = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value; + // 2. Perform ! DefinePropertyOrThrow(error, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: errors }). + X(DefinePropertyOrThrow(error, new Value('errors'), Descriptor({ + Configurable: Value.true, + Enmerable: Value.false, + Writable: Value.true, + Value: X(CreateArrayFromList(errors)), + }))); + // 3. Return ThrowCompletion(error). + return ThrowCompletion(error); + } + // iv. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // e. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // g. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // h. Append undefined to errors. + errors.push(Value.undefined); + // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + const nextPromise = Q(Call(promiseResolve, constructor, [nextValue])); + // j. Let steps be the algorithm steps defined in Promise.any Reject Element Functions. + const steps = PromiseAnyRejectElementFunctions; + // k. Let rejectElement be ! CreateBuiltinFunction(steps, « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »). + const rejectElement = X(CreateBuiltinFunction(steps, ['AlreadyCalled', 'Index', 'Errors', 'Capability', 'RemainingElements'])); + X(SetFunctionLength(rejectElement, new Value(1))); + X(SetFunctionName(rejectElement, new Value(''))); + // l. Set rejectElement.[[AlreadyCalled]] to a new Record { [[Value]]: false }. + rejectElement.AlreadyCalled = { Value: false }; + // m. Set rejectElement.[[Index]] to index. + rejectElement.Index = index; + // n. Set rejectElement.[[Errors]] to errors. + rejectElement.Errors = errors; + // o. Set rejectElement.[[Capability]] to resultCapability. + rejectElement.Capability = resultCapability; + // p. Set rejectElement.[[RemainingElements]] to remainingElementsCount. + rejectElement.RemainingElements = remainingElementsCount; + // q. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. + remainingElementsCount.Value += 1; + // r. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], rejectElement »). + Q(Invoke(nextPromise, new Value('then'), [resultCapability.Resolve, rejectElement])); + // s. Increase index by 1. + index += 1; + } +} + +// #sec-promise.any +function Promise_any([iterable = Value.undefined], { thisValue }) { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(NewPromiseCapability(C)); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = GetPromiseResolve(C); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = GetIterator(iterable); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + // 7. Let result be PerformPromiseAny(iteratorRecord, C, promiseCapability). + let result = EnsureCompletion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)); + // 8. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result). + if (iteratorRecord.Done === Value.false) { + result = IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return Completion(result). + return Completion(result); +} + +function PerformPromiseRace(iteratorRecord, constructor, resultCapability, promiseResolve) { + // 1. Assert: IsConstructor(constructor) is true. + Assert(IsConstructor(constructor) === Value.true); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve) === Value.true); + // 4. Repeat, + while (true) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, then + if (next === Value.false) { + // i. Set iteratorRecord.[[Done]] to true. + iteratorRecord.Done = Value.true; + // ii. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // e. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // g. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + const nextPromise = Q(Call(promiseResolve, constructor, [nextValue])); + // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). + Q(Invoke(nextPromise, new Value('then'), [resultCapability.Resolve, resultCapability.Reject])); + } +} + +// #sec-promise.race +function Promise_race([iterable = Value.undefined], { thisValue }) { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(NewPromiseCapability(C)); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = GetPromiseResolve(C); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = GetIterator(iterable); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + // 7. Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve). + let result = EnsureCompletion(PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve)); + // 8. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result). + if (iteratorRecord.Done === Value.false) { + result = IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return Completion(result). + return Completion(result); +} + +// #sec-promise.reject +function Promise_reject([r = Value.undefined], { thisValue }) { + // 1. Let C be this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(NewPromiseCapability(C)); + // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). + Q(Call(promiseCapability.Reject, Value.undefined, [r])); + // 4. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} + +// #sec-promise.resolve +function Promise_resolve([x = Value.undefined], { thisValue }) { + // 1. Let C be the this value. + const C = thisValue; + // 2. If Type(C) is not Object, throw a TypeError exception. + if (Type(C) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Promise.resolve', C); + } + // 3. Return ? PromiseResolve(C, x). + return Q(PromiseResolve(C, x)); +} + +// #sec-get-promise-@@species +function Promise_symbolSpecies(args, { thisValue }) { + // 1. Return the this value. + return thisValue; +} + +export function BootstrapPromise(realmRec) { + const promiseConstructor = BootstrapConstructor(realmRec, PromiseConstructor, 'Promise', 1, realmRec.Intrinsics['%Promise.prototype%'], [ + ['all', Promise_all, 1], + ['allSettled', Promise_allSettled, 1], + ['any', Promise_any, 1], + ['race', Promise_race, 1], + ['reject', Promise_reject, 1], + ['resolve', Promise_resolve, 1], + [wellKnownSymbols.species, [Promise_symbolSpecies]], + ]); + + promiseConstructor.DefineOwnProperty(new Value('prototype'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + })); + + realmRec.Intrinsics['%Promise%'] = promiseConstructor; +} diff --git a/engine262/src/intrinsics/PromisePrototype.mjs b/engine262/src/intrinsics/PromisePrototype.mjs new file mode 100644 index 0000000..c3e4c41 --- /dev/null +++ b/engine262/src/intrinsics/PromisePrototype.mjs @@ -0,0 +1,130 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + Call, + CreateBuiltinFunction, + Get, + Invoke, + IsCallable, + IsConstructor, + IsPromise, + NewPromiseCapability, + PerformPromiseThen, + PromiseResolve, + SetFunctionLength, + SetFunctionName, + SpeciesConstructor, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q, ThrowCompletion, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-promise.prototype.catch +function PromiseProto_catch([onRejected = Value.undefined], { thisValue }) { + // 1. Let promise be the this value. + const promise = thisValue; + // 2. Return ? Invoke(promise, "then", « undefined, onRejected »). + return Q(Invoke(promise, new Value('then'), [Value.undefined, onRejected])); +} + +function ThenFinallyFunctions([value = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + const onFinally = F.OnFinally; + Assert(IsCallable(onFinally) === Value.true); + const result = Q(Call(onFinally, Value.undefined)); + const C = F.Constructor; + Assert(IsConstructor(C) === Value.true); + const promise = Q(PromiseResolve(C, result)); + const valueThunk = CreateBuiltinFunction(() => value, []); + SetFunctionLength(valueThunk, new Value(0)); + SetFunctionName(valueThunk, new Value('')); + return Q(Invoke(promise, new Value('then'), [valueThunk])); +} + +function CatchFinallyFunctions([reason = Value.undefined]) { + const F = surroundingAgent.activeFunctionObject; + const onFinally = F.OnFinally; + Assert(IsCallable(onFinally) === Value.true); + const result = Q(Call(onFinally, Value.undefined)); + const C = F.Constructor; + Assert(IsConstructor(C) === Value.true); + const promise = Q(PromiseResolve(C, result)); + const thrower = CreateBuiltinFunction(() => ThrowCompletion(reason), []); + SetFunctionLength(thrower, new Value(0)); + SetFunctionName(thrower, new Value('')); + return Q(Invoke(promise, new Value('then'), [thrower])); +} + +// #sec-promise.prototype.finally +function PromiseProto_finally([onFinally = Value.undefined], { thisValue }) { + // 1. Let promise be the this value. + const promise = thisValue; + // 2. If Type(promise) is not Object, throw a TypeError exception. + if (Type(promise) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Promise', promise); + } + // 3. Let C be ? SpeciesConstructor(promise, %Promise%). + const C = SpeciesConstructor(promise, surroundingAgent.intrinsic('%Promise%')); + // 4. Assert: IsConstructor(C) is true. + Assert(IsConstructor(C) === Value.true); + let thenFinally; + let catchFinally; + // 5. If IsCallable(onFinally) is false, then + if (IsCallable(onFinally) === Value.false) { + // a. Let thenFinally be onFinally. + thenFinally = onFinally; + // b. Let catchFinally be onFinally. + catchFinally = onFinally; + } else { // 6. Else, + // a. Let stepsThenFinally be the algorithm steps defined in Then Finally Functions. + const stepsThenFinally = ThenFinallyFunctions; + // b. .Let thenFinally be ! CreateBuiltinFunction(stepsThenFinally, « [[Constructor]], [[OnFinally]] »). + thenFinally = X(CreateBuiltinFunction(stepsThenFinally, ['Constructor', 'OnFinally'])); + SetFunctionLength(thenFinally, new Value(1)); + SetFunctionName(thenFinally, new Value('')); + // c. Set thenFinally.[[Constructor]] to C. + thenFinally.Constructor = C; + // d. Set thenFinally.[[OnFinally]] to onFinally. + thenFinally.OnFinally = onFinally; + // e. Let stepsCatchFinally be the algorithm steps defined in Catch Finally Functions. + const stepsCatchFinally = CatchFinallyFunctions; + // f. Let catchFinally be ! CreateBuiltinFunction(stepsCatchFinally, « [[Constructor]], [[OnFinally]] »). + catchFinally = X(CreateBuiltinFunction(stepsCatchFinally, ['Constructor', 'OnFinally'])); + SetFunctionLength(catchFinally, new Value(1)); + SetFunctionName(catchFinally, new Value('')); + // g. Set catchFinally.[[Constructor]] to C. + catchFinally.Constructor = C; + // h. Set catchFinally.[[OnFinally]] to onFinally. + catchFinally.OnFinally = onFinally; + } + // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). + return Q(Invoke(promise, new Value('then'), [thenFinally, catchFinally])); +} + +// #sec-promise.prototype.then +function PromiseProto_then([onFulfilled = Value.undefined, onRejected = Value.undefined], { thisValue }) { + // 1. Let promise be the this value. + const promise = thisValue; + // 2. If IsPromise(promise) is false, throw a TypeError exception. + if (IsPromise(promise) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Promise', promise); + } + // 3. Let C be ? SpeciesConstructor(promise, %Promise%). + const C = Q(SpeciesConstructor(promise, surroundingAgent.intrinsic('%Promise%'))); + // 4. Let resultCapability be ? NewPromiseCapability(C). + const resultCapability = Q(NewPromiseCapability(C)); + // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability). + return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability); +} + +export function BootstrapPromisePrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['catch', PromiseProto_catch, 1], + ['finally', PromiseProto_finally, 1], + ['then', PromiseProto_then, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'Promise'); + + realmRec.Intrinsics['%Promise.prototype.then%'] = X(Get(proto, new Value('then'))); + + realmRec.Intrinsics['%Promise.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Proxy.mjs b/engine262/src/intrinsics/Proxy.mjs new file mode 100644 index 0000000..e53e96c --- /dev/null +++ b/engine262/src/intrinsics/Proxy.mjs @@ -0,0 +1,82 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Assert, + CreateBuiltinFunction, + CreateDataProperty, + OrdinaryObjectCreate, + ProxyCreate, + SetFunctionLength, + SetFunctionName, + isProxyExoticObject, +} from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { assignProps } from './Bootstrap.mjs'; + +// #sec-proxy-target-handler +function ProxyConstructor([target = Value.undefined, handler = Value.undefined], { NewTarget }) { + // 1. f NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Return ? ProxyCreate(target, handler). + return Q(ProxyCreate(target, handler)); +} + +// #sec-proxy-revocation-functions +function ProxyRevocationFunctions() { + // 1. Let F be the active function object. + const F = surroundingAgent.activeFunctionObject; + // 2. Let p be F.[[RevocableProxy]]. + const p = F.RevocableProxy; + // 3. If p is null, return undefined. + if (p === Value.null) { + return Value.undefined; + } + // 4. Set F.[[RevocableProxy]] to null. + F.RevocableProxy = Value.null; + // 5. Assert: p is a Proxy object. + Assert(isProxyExoticObject(p)); + // 6. Set p.[[ProxyTarget]] to null. + p.ProxyTarget = Value.null; + // 7. Set p.[[ProxyHandler]] to null. + p.ProxyHandler = Value.null; + // 8. Return undefined. + return Value.undefined; +} + +// #sec-proxy.revocable +function Proxy_revocable([target = Value.undefined, handler = Value.undefined]) { + // 1. Let p be ? ProxyCreate(target, handler). + const p = Q(ProxyCreate(target, handler)); + // 2. Let steps be the algorithm steps defined in #sec-proxy-revocation-functions. + const steps = ProxyRevocationFunctions; + // 3. Let revoker be ! CreateBuiltinFunction(steps, « [[RevocableProxy]] »). + const revoker = X(CreateBuiltinFunction(steps, ['RevocableProxy'])); + SetFunctionLength(revoker, new Value(0)); + SetFunctionName(revoker, new Value('')); + // 4. Set revoker.[[RevocableProxy]] to p. + revoker.RevocableProxy = p; + // 5. Let result be OrdinaryObjectCreate(%Object.prototype%). + const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). + X(CreateDataProperty(result, new Value('proxy'), p)); + // 7. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker). + X(CreateDataProperty(result, new Value('revoke'), revoker)); + // 8. Return result. + return result; +} + +export function BootstrapProxy(realmRec) { + const proxyConstructor = CreateBuiltinFunction(ProxyConstructor, [], realmRec, undefined, Value.true); + SetFunctionName(proxyConstructor, new Value('Proxy')); + SetFunctionLength(proxyConstructor, new Value(2)); + + assignProps(realmRec, proxyConstructor, [ + ['revocable', Proxy_revocable, 2], + ]); + + realmRec.Intrinsics['%Proxy%'] = proxyConstructor; +} diff --git a/engine262/src/intrinsics/Reflect.mjs b/engine262/src/intrinsics/Reflect.mjs new file mode 100644 index 0000000..84003c6 --- /dev/null +++ b/engine262/src/intrinsics/Reflect.mjs @@ -0,0 +1,209 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Call, + Construct, + CreateArrayFromList, + CreateListFromArrayLike, + FromPropertyDescriptor, + IsCallable, + IsConstructor, + PrepareForTailCall, + ToPropertyDescriptor, + ToPropertyKey, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-reflect.apply +function Reflect_apply([target = Value.undefined, thisArgument = Value.undefined, argumentsList = Value.undefined]) { + // 1. If IsCallable(target) is false, throw a TypeError exception. + if (IsCallable(target) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', target); + } + // 2. Let args be ? CreateListFromArrayLike(argumentsList). + const args = Q(CreateListFromArrayLike(argumentsList)); + // 3. Perform PrepareForTailCall(). + PrepareForTailCall(); + // 4. Return ? Call(target, thisArgument, args). + return Q(Call(target, thisArgument, args)); +} + +// #sec-reflect.construct +function Reflect_construct([target = Value.undefined, argumentsList = Value.undefined, newTarget]) { + // 1. If IsConstructor(target) is false, throw a TypeError exception. + if (IsConstructor(target) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', target); + } + // 2. If newTarget is not present, set newTarget to target. + if (newTarget === undefined) { + newTarget = target; + } else if (IsConstructor(newTarget) === Value.false) { // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAConstructor', newTarget); + } + // 4. Let args be ? CreateListFromArrayLike(argumentsList). + const args = Q(CreateListFromArrayLike(argumentsList)); + // 5. Return ? Construct(target, args, newTarget). + return Q(Construct(target, args, newTarget)); +} + +// #sec-reflect.defineproperty +function Reflect_defineProperty([target = Value.undefined, propertyKey = Value.undefined, attributes = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(ToPropertyKey(propertyKey)); + // 3. Let desc be ? ToPropertyDescriptor(attributes). + const desc = Q(ToPropertyDescriptor(attributes)); + // 4. Return ? target.[[DefineOwnProperty]](key, desc). + return Q(target.DefineOwnProperty(key, desc)); +} + +// #sec-reflect.deleteproperty +function Reflect_deleteProperty([target = Value.undefined, propertyKey = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(ToPropertyKey(propertyKey)); + // 3. Return ? target.[[Delete]](key). + return Q(target.Delete(key)); +} + +// #sec-reflect.get +function Reflect_get([target = Value.undefined, propertyKey = Value.undefined, receiver]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(ToPropertyKey(propertyKey)); + // 3. If receiver is not present, then + if (receiver === undefined) { + // a. Set receiver to target. + receiver = target; + } + // 4. Return ? target.[[Get]](key, receiver). + return Q(target.Get(key, receiver)); +} + +// #sec-reflect.getownpropertydescriptor +function Reflect_getOwnPropertyDescriptor([target = Value.undefined, propertyKey = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(ToPropertyKey(propertyKey)); + // 3. Let desc be ? target.[[GetOwnProperty]](key). + const desc = Q(target.GetOwnProperty(key)); + // 4. Return FromPropertyDescriptor(desc). + return FromPropertyDescriptor(desc); +} + +// #sec-reflect.getprototypeof +function Reflect_getPrototypeOf([target = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Return ? target.[[GetPrototypeOf]](). + return Q(target.GetPrototypeOf()); +} + +// #sec-reflect.has +function Reflect_has([target = Value.undefined, propertyKey = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(ToPropertyKey(propertyKey)); + // 3. Return ? target.[[HasProperty]](key). + return Q(target.HasProperty(key)); +} + +// #sec-reflect.isextensible +function Reflect_isExtensible([target = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Return ? target.[[IsExtensible]](). + return Q(target.IsExtensible()); +} + +// #sec-reflect.ownkeys +function Reflect_ownKeys([target = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let keys be ? target.[[OwnPropertyKeys]](). + const keys = Q(target.OwnPropertyKeys()); + // 3. Return CreateArrayFromList(keys). + return CreateArrayFromList(keys); +} + +// #sec-reflect.preventextensions +function Reflect_preventExtensions([target = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Return ? target.[[PreventExtensions]](). + return Q(target.PreventExtensions()); +} + +// #sec-reflect.set +function Reflect_set([target = Value.undefined, propertyKey = Value.undefined, V = Value.undefined, receiver]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(ToPropertyKey(propertyKey)); + // 3. If receiver is not present, then + if (receiver === undefined) { + receiver = target; + } + // 4. Return ? target.[[Set]](key, V, receiver). + return Q(target.Set(key, V, receiver)); +} + +// #sec-reflect.setprototypeof +function Reflect_setPrototypeOf([target = Value.undefined, proto = Value.undefined]) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. If Type(proto) is not Object and proto is not null, throw a TypeError exception. + if (Type(proto) !== 'Object' && proto !== Value.null) { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // 3. Return ? target.[[SetPrototypeOf]](proto). + return Q(target.SetPrototypeOf(proto)); +} + +export function BootstrapReflect(realmRec) { + const reflect = BootstrapPrototype(realmRec, [ + ['apply', Reflect_apply, 3], + ['construct', Reflect_construct, 2], + ['defineProperty', Reflect_defineProperty, 3], + ['deleteProperty', Reflect_deleteProperty, 2], + ['get', Reflect_get, 2], + ['getOwnPropertyDescriptor', Reflect_getOwnPropertyDescriptor, 2], + ['getPrototypeOf', Reflect_getPrototypeOf, 1], + ['has', Reflect_has, 2], + ['isExtensible', Reflect_isExtensible, 1], + ['ownKeys', Reflect_ownKeys, 1], + ['preventExtensions', Reflect_preventExtensions, 1], + ['set', Reflect_set, 3], + ['setPrototypeOf', Reflect_setPrototypeOf, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'Reflect'); + + realmRec.Intrinsics['%Reflect%'] = reflect; +} diff --git a/engine262/src/intrinsics/RegExp.mjs b/engine262/src/intrinsics/RegExp.mjs new file mode 100644 index 0000000..c56a037 --- /dev/null +++ b/engine262/src/intrinsics/RegExp.mjs @@ -0,0 +1,87 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Get, + IsRegExp, + RegExpAlloc, + RegExpInitialize, + SameValue, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-regexp-constructor +function RegExpConstructor([pattern = Value.undefined, flags = Value.undefined], { NewTarget }) { + // 1. Let patternIsRegExp be ? IsRegExp(pattern). + const patternIsRegExp = Q(IsRegExp(pattern)); + let newTarget; + // 2. If NewTarget is undefined, then + if (NewTarget === Value.undefined) { + // a. Let newTarget be the active function object. + newTarget = surroundingAgent.activeFunctionObject; + // b. If patternIsRegExp is true and flags is undefined, then + if (patternIsRegExp === Value.true && flags === Value.undefined) { + // i. Let patternConstructor be ? Get(pattern, "constructor"). + const patternConstructor = Q(Get(pattern, new Value('constructor'))); + // ii. If SameValue(newTarget, patternConstructor) is true, return pattern. + if (SameValue(newTarget, patternConstructor) === Value.true) { + return pattern; + } + } + } else { // 3. Else, let newTarget be NewTarget. + newTarget = NewTarget; + } + let P; + let F; + // 4. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then + if (Type(pattern) === 'Object' && 'RegExpMatcher' in pattern) { + // a. Let P be pattern.[[OriginalSource]]. + P = pattern.OriginalSource; + // b. If flags is undefined, let F be pattern.[[OriginalFlags]]. + if (flags === Value.undefined) { + F = pattern.OriginalFlags; + } else { // c. Else, let F be flags. + F = flags; + } + } else if (patternIsRegExp === Value.true) { // 5. Else if patternIsRegExp is true, then + // a. Else if patternIsRegExp is true, then + P = Q(Get(pattern, new Value('source'))); + // b. If flags is undefined, then + if (flags === Value.undefined) { + // i. Let F be ? Get(pattern, "flags"). + F = Q(Get(pattern, new Value('flags'))); + } else { // c. Else, let F be flags. + F = flags; + } + } else { // 6. Else, + // a. Let P be pattern. + P = pattern; + // b. Let F be flags. + F = flags; + } + // 7. Let O be ? RegExpAlloc(newTarget). + const O = Q(RegExpAlloc(newTarget)); + // 8. Return ? RegExpInitialize(O, P, F). + return Q(RegExpInitialize(O, P, F)); +} + +// 21.2.4.2 #sec-get-regexp-@@species +function RegExp_speciesGetter(args, { thisValue }) { + return thisValue; +} + +export function BootstrapRegExp(realmRec) { + const proto = realmRec.Intrinsics['%RegExp.prototype%']; + + const cons = BootstrapConstructor(realmRec, RegExpConstructor, 'RegExp', 2, proto, [ + [wellKnownSymbols.species, [RegExp_speciesGetter]], + ]); + + realmRec.Intrinsics['%RegExp%'] = cons; +} diff --git a/engine262/src/intrinsics/RegExpPrototype.mjs b/engine262/src/intrinsics/RegExpPrototype.mjs new file mode 100644 index 0000000..36a2dce --- /dev/null +++ b/engine262/src/intrinsics/RegExpPrototype.mjs @@ -0,0 +1,815 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + ArrayCreate, + Assert, + Call, + Construct, + CreateDataProperty, + CreateDataPropertyOrThrow, + EscapeRegExpPattern, + Get, + GetMatchString, + GetStringIndex, + IsCallable, + MakeIndicesArray, + OrdinaryObjectCreate, + SameValue, + Set, + SpeciesConstructor, + LengthOfArrayLike, + ToBoolean, + ToInteger, + ToLength, + ToString, + ToObject, + ToUint32, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { RegExpState as State, GetSubstitution } from '../runtime-semantics/all.mjs'; +import { CodePointAt, CodePointsToString } from '../static-semantics/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; +import { CreateRegExpStringIterator } from './RegExpStringIteratorPrototype.mjs'; + + +// 21.2.5.2 #sec-regexp.prototype.exec +function RegExpProto_exec([string = Value.undefined], { thisValue }) { + const R = thisValue; + Q(RequireInternalSlot(R, 'RegExpMatcher')); + const S = Q(ToString(string)); + return Q(RegExpBuiltinExec(R, S)); +} + +// 21.2.5.2.1 #sec-regexpexec +export function RegExpExec(R, S) { + Assert(Type(R) === 'Object'); + Assert(Type(S) === 'String'); + + const exec = Q(Get(R, new Value('exec'))); + if (IsCallable(exec) === Value.true) { + const result = Q(Call(exec, R, [S])); + if (Type(result) !== 'Object' && Type(result) !== 'Null') { + return surroundingAgent.Throw('TypeError', 'RegExpExecNotObject', result); + } + return result; + } + Q(RequireInternalSlot(R, 'RegExpMatcher')); + return Q(RegExpBuiltinExec(R, S)); +} + +// #sec-regexpbuiltinexec +export function RegExpBuiltinExec(R, S) { + // 1. Assert: R is an initialized RegExp instance. + Assert('RegExpMatcher' in R); + // 2. Assert: Type(S) is String. + Assert(Type(S) === 'String'); + // 3. Let length be the number of code units in S. + const length = S.stringValue().length; + // 4. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). + let lastIndex = Q(ToLength(Q(Get(R, new Value('lastIndex'))))); + // 5. Let flags be R.[[OriginalFlags]]. + const flags = R.OriginalFlags.stringValue(); + // 6. If flags contains "g", let global be true; else let global be false. + const global = flags.includes('g'); + // 7. If flags contains "y", let sticky be true; else let sticky be false. + const sticky = flags.includes('y'); + // 8. If global is false and sticky is false, set lastIndex to 0. + if (!global && !sticky) { + lastIndex = new Value(0); + } + // 9. Let matcher be R.[[RegExpMatcher]]. + const matcher = R.RegExpMatcher; + // 10. If flags contains "u", let fullUnicode be true; else let fullUnicode be false. + const fullUnicode = flags.includes('u'); + // 11. Let matchSucceeded be false. + let matchSucceeded = false; + let r; + // 12. Repeat, while matchSucceeded is false + while (matchSucceeded === false) { + // a. If lastIndex > length, then + if (lastIndex.numberValue() > length) { + // i. If global is true or sticky is true, then + if (global || sticky) { + // 1. Perform ? Set(R, "lastIndex", 0, true). + Q(Set(R, new Value('lastIndex'), new Value(0), Value.true)); + } + // ii. Return null. + return Value.null; + } + // b. Let r be matcher(S, lastIndex). + r = matcher(S, lastIndex); + // c. If r is failure, then + if (r === 'failure') { + // i. If sticky is true, then + if (sticky) { + // 1. Perform ? Set(R, "lastIndex", 0, true). + Q(Set(R, new Value('lastIndex'), new Value(0), Value.true)); + // 2. Return null. + return Value.null; + } + // ii. Set lastIndex to AdvanceStringIndex(S, lastIndex, fullUnicode). + lastIndex = AdvanceStringIndex(S, lastIndex, fullUnicode ? Value.true : Value.false); + } else { // d. Else, + // i. Assert: r is a State. + Assert(r instanceof State); + // ii. Set matchSucceeded to true. + matchSucceeded = true; + } + } + // 13. Let e be r's endIndex value. + let e = r.endIndex; + const Input = fullUnicode ? Array.from(S.stringValue()) : S.stringValue().split(''); + // 14. If fullUnicode is true, then + if (fullUnicode) { + if (surroundingAgent.feature('regexp-match-indices')) { + // If fullUnicode is true, set e to ! GetStringIndex(S, Input, e). + e = X(GetStringIndex(S, Input, e)); + } else { + // a. e is an index into the Input character list, derived from S, matched by matcher. + // Let eUTF be the smallest index into S that corresponds to the character at element e of Input. + // If e is greater than or equal to the number of elements in Input, then eUTF is the number of code units in S. + let eUTF = 0; + if (e >= Input.length) { + eUTF = S.stringValue().length; + } else { + for (let i = 0; i < e; i += 1) { + eUTF += Input[i].length; + } + } + // b. Set e to eUTF. + e = eUTF; + } + } + // 15. If global is true or sticky is true, then + if (global || sticky) { + // a. Perform ? Set(R, "lastIndex", e, true). + Q(Set(R, new Value('lastIndex'), new Value(e), Value.true)); + } + // 16. Let n be the number of elements in r's captures List. + const n = r.captures.length - 1; + // 17. Assert: n < 2^32 - 1. + Assert(n < (2 ** 32) - 1); + // 18. Let A be ! ArrayCreate(n + 1). + const A = X(ArrayCreate(new Value(n + 1))); + // 19. Assert: The value of A's "length" property is n + 1. + Assert(X(Get(A, new Value('length'))).numberValue() === n + 1); + // 20. Perform ! CreateDataPropertyOrThrow(A, "index", lastIndex). + X(CreateDataPropertyOrThrow(A, new Value('index'), lastIndex)); + // 21. Perform ! CreateDataPropertyOrThrow(A, "input", S). + X(CreateDataPropertyOrThrow(A, new Value('input'), S)); + const capturingParens = R.parsedPattern.capturingGroups; + let indices; + if (surroundingAgent.feature('regexp-match-indices')) { + // 25. Let indices be a new empty List. + indices = []; + // 26. Let match be the Match { [[StartIndex]]: lastIndex, [[EndIndex]]: e }. + const match = { StartIndex: lastIndex.numberValue(), EndIndex: e }; + // 27. Add match as the last element of indices. + indices.push(match); + // 28. Let matchedValue be ! GetMatchString(S, match). + const matchedValue = X(GetMatchString(S, match)); + // 29. Perform ! CreateDataProperty(A, "0", matchedValue). + X(CreateDataPropertyOrThrow(A, new Value('0'), matchedValue)); + } else { + // 22. Let matchedSubstr be the matched substring (i.e. the portion of S between offset lastIndex inclusive and offset e exclusive). + const matchedSubstr = S.stringValue().substring(lastIndex.numberValue(), e); + // 23. Perform ! CreateDataPropertyOrThrow(A, "0", matchedSubstr). + X(CreateDataPropertyOrThrow(A, new Value('0'), new Value(matchedSubstr))); + } + let groups; + let groupNames; + // 24. If R contains any GroupName, then + if (R.parsedPattern.groupSpecifiers.size > 0) { + // a. Let groups be OrdinaryObjectCreate(null). + groups = OrdinaryObjectCreate(Value.null); + if (surroundingAgent.feature('regexp-match-indices')) { + // b. Let groupNames be a new empty List. + groupNames = [Value.undefined]; + } + } else { // 25. Else, + // a. Let groups be undefined. + groups = Value.undefined; + if (surroundingAgent.feature('regexp-match-indices')) { + // b. Let groupNames be undefined. + groupNames = Value.undefined; + } + } + // 26. Perform ! CreateDataPropertyOrThrow(A, "groups", groups). + X(CreateDataPropertyOrThrow(A, new Value('groups'), groups)); + // 27. For each integer i such that i > 0 and i ≤ n, do + for (let i = 1; i <= n; i += 1) { + // a. Let captureI be ith element of r's captures List. + const captureI = r.captures[i]; + let capturedValue; + if (surroundingAgent.feature('regexp-match-indices')) { + // e. If captureI is undefined, then + if (captureI === Value.undefined) { + // i. Let capturedValue be undefined. + capturedValue = Value.undefined; + // ii. Add undefined as the last element of indices. + indices.push(Value.undefined); + } else { // f. Else, + // i. Let captureStart be captureI's startIndex. + let captureStart = captureI.startIndex; + // ii. Let captureEnd be captureI's endIndex. + let captureEnd = captureI.endIndex; + // iii. If fullUnicode is true, then + if (fullUnicode) { + // 1. Set captureStart to ! GetStringIndex(S, Input, captureStart). + captureStart = X(GetStringIndex(S, Input, captureStart)); + // 2. Set captureEnd to ! GetStringIndex(S, Input, captureEnd). + captureEnd = X(GetStringIndex(S, Input, captureEnd)); + } + // iv. Let capture be the Match { [[StartIndex]]: captureStart, [[EndIndex]:: captureEnd }. + const capture = { StartIndex: captureStart, EndIndex: captureEnd }; + // v. Append capture to indices. + indices.push(capture); + // vi. Let capturedValue be ! GetMatchString(S, capture). + capturedValue = X(GetMatchString(S, capture)); + } + } else { + // b. If captureI is undefined, let capturedValue be undefined. + if (captureI === Value.undefined) { + capturedValue = Value.undefined; + } else if (fullUnicode) { // c. Else if fullUnicode is true, then + // i. Assert: captureI is a List of code points. + // ii. Let capturedValue be ! CodePointsToString(captureI). + capturedValue = new Value(X(CodePointsToString(captureI))); + } else { // d. Else, + // i. Assert: fullUnicode is false. + Assert(fullUnicode === false); + // ii. Assert: captureI is a List of code units. + // iii. Let capturedValue be the String value consisting of the code units of captureI. + capturedValue = new Value(String.fromCharCode(...captureI)); + } + } + // e. Perform ! CreateDataPropertyOrThrow(A, ! ToString(i), capturedValue). + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(i))), capturedValue)); + // f. If the ith capture of R was defined with a GroupName, then + if (capturingParens[i - 1].GroupSpecifier) { + // i. Let s be the StringValue of the corresponding RegExpIdentifierName. + const s = new Value(capturingParens[i - 1].GroupSpecifier); + // ii. Perform ! CreateDataPropertyOrThrow(groups, s, capturedValue). + X(CreateDataPropertyOrThrow(groups, s, capturedValue)); + if (surroundingAgent.feature('regexp-match-indices')) { + // iii. Assert: groupNames is a List. + Assert(Array.isArray(groupNames)); + // iv. Append s to groupNames. + groupNames.push(s); + } + } else if (surroundingAgent.feature('regexp-match-indices')) { + // i. If groupNames is a List, append undefined to groupNames. + if (Array.isArray(groupNames)) { + groupNames.push(Value.undefined); + } + } + } + if (surroundingAgent.feature('regexp-match-indices')) { + // 34. Let indicesArray be MakeIndicesArray(S, indices, groupNames). + const indicesArray = MakeIndicesArray(S, indices, groupNames); + // 35. Perform ! CreateDataProperty(A, "indices", indicesArray). + X(CreateDataPropertyOrThrow(A, new Value('indices'), indicesArray)); + } + // 28. Return A. + return A; +} + +// #sec-advancestringindex +export function AdvanceStringIndex(S, index, unicode) { + index = index.numberValue(); + // 1. Assert: Type(S) is String. + Assert(Type(S) === 'String'); + // 2. Assert: 0 ≤ index ≤ 253 - 1 and ! IsInteger(index) is true. + Assert(Number.isInteger(index) && index >= 0 && index <= (2 ** 53) - 1); + // 3. Assert: Type(unicode) is Boolean. + Assert(Type(unicode) === 'Boolean'); + // 4. If unicode is false, return index + 1. + if (unicode === Value.false) { + return new Value(index + 1); + } + // 5. Let length be the number of code units in S. + const length = S.stringValue().length; + // 6. If index + 1 ≥ length, return index + 1. + if (index + 1 >= length) { + return new Value(index + 1); + } + // 7. Let cp be ! CodePointAt(S, index). + const cp = X(CodePointAt(S.stringValue(), index)); + // 8. Return index + cp.[[CodeUnitCount]]. + return new Value(index + cp.CodeUnitCount); +} + +// 21.2.5.3 #sec-get-regexp.prototype.dotAll +function RegExpProto_dotAllGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalFlags' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const flags = R.OriginalFlags; + if (flags.stringValue().includes('s')) { + return Value.true; + } + return Value.false; +} + +// 21.2.5.4 #sec-get-regexp.prototype.flags +function RegExpProto_flagsGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + let result = ''; + const global = ToBoolean(Q(Get(R, new Value('global')))); + if (global === Value.true) { + result += 'g'; + } + const ignoreCase = ToBoolean(Q(Get(R, new Value('ignoreCase')))); + if (ignoreCase === Value.true) { + result += 'i'; + } + const multiline = ToBoolean(Q(Get(R, new Value('multiline')))); + if (multiline === Value.true) { + result += 'm'; + } + const dotAll = ToBoolean(Q(Get(R, new Value('dotAll')))); + if (dotAll === Value.true) { + result += 's'; + } + const unicode = ToBoolean(Q(Get(R, new Value('unicode')))); + if (unicode === Value.true) { + result += 'u'; + } + const sticky = ToBoolean(Q(Get(R, new Value('sticky')))); + if (sticky === Value.true) { + result += 'y'; + } + return new Value(result); +} + +// 21.2.5.5 #sec-get-regexp.prototype.global +function RegExpProto_globalGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalFlags' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const flags = R.OriginalFlags; + if (flags.stringValue().includes('g')) { + return Value.true; + } + return Value.false; +} + +// 21.2.5.6 #sec-get-regexp.prototype.ignorecase +function RegExpProto_ignoreCaseGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalFlags' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const flags = R.OriginalFlags; + if (flags.stringValue().includes('i')) { + return Value.true; + } + return Value.false; +} + +// #sec-regexp.prototype-@@match +function RegExpProto_match([string = Value.undefined], { thisValue }) { + // 1. Let rx be the this value. + const rx = thisValue; + // 2. If Type(rx) is not Object, throw a TypeError exception. + if (Type(rx) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + // 3. Let S be ? ToString(string). + const S = Q(ToString(string)); + // 4. Let global be ! ToBoolean(? Get(rx, "global")). + const global = ToBoolean(Q(Get(rx, new Value('global')))); + // 5. If global is false, then + if (global === Value.false) { + // a. Return ? RegExpExec(rx, S). + return Q(RegExpExec(rx, S)); + } else { // 6. Else, + // a. Assert: global is true. + Assert(global === Value.true); + // b. Let fullUnicode be ! ToBoolean(? Get(rx, "unicode")). + const fullUnicode = ToBoolean(Q(Get(rx, new Value('unicode')))); + // c. Perform ? Set(rx, "lastIndex", 0, true). + Q(Set(rx, new Value('lastIndex'), new Value(0), Value.true)); + // d. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(new Value(0))); + // e. Let n be 0. + let n = 0; + // f. Repeat, + while (true) { + // i. Let result be ? RegExpExec(rx, S). + const result = Q(RegExpExec(rx, S)); + // ii. If result is null, then + if (result === Value.null) { + // 1. If n = 0, return null. + if (n === 0) { + return Value.null; + } + // 2. Return A. + return A; + } else { // iii. Else, + // 1. Let matchStr be ? ToString(? Get(result, "0")). + const matchStr = Q(ToString(Q(Get(result, new Value('0'))))); + // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(n), matchStr). + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(n))), matchStr)); + // 3. If matchStr is the empty String, then + if (matchStr.stringValue() === '') { + // a. Let thisIndex be ? ToLength(? Get(rx, "lastIndex")). + const thisIndex = Q(ToLength(Q(Get(rx, new Value('lastIndex'))))); + // b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). + const nextIndex = AdvanceStringIndex(S, thisIndex, fullUnicode); + // c. Perform ? Set(rx, "lastIndex", nextIndex, true). + Q(Set(rx, new Value('lastIndex'), nextIndex, Value.true)); + } + // 4. Set n to n + 1. + n += 1; + } + } + } +} + +// 21.2.5.8 #sec-regexp-prototype-matchall +function RegExpProto_matchAll([string = Value.undefined], { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const S = Q(ToString(string)); + const C = Q(SpeciesConstructor(R, surroundingAgent.intrinsic('%RegExp%'))); + const flags = Q(ToString(Q(Get(R, new Value('flags'))))); + const matcher = Q(Construct(C, [R, flags])); + const lastIndex = Q(ToLength(Q(Get(R, new Value('lastIndex'))))); + Q(Set(matcher, new Value('lastIndex'), lastIndex, Value.true)); + let global; + if (flags.stringValue().includes('g')) { + global = Value.true; + } else { + global = Value.false; + } + let fullUnicode; + if (flags.stringValue().includes('u')) { + fullUnicode = Value.true; + } else { + fullUnicode = Value.false; + } + return X(CreateRegExpStringIterator(matcher, S, global, fullUnicode)); +} + +// 21.2.5.9 #sec-get-regexp.prototype.multiline +function RegExpProto_multilineGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalFlags' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const flags = R.OriginalFlags; + if (flags.stringValue().includes('m')) { + return Value.true; + } + return Value.false; +} + +// 21.2.5.10 #sec-regexp.prototype-@@replace +function RegExpProto_replace([string = Value.undefined, replaceValue = Value.undefined], { thisValue }) { + const rx = thisValue; + if (Type(rx) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + const S = Q(ToString(string)); + const lengthS = S.stringValue().length; + const functionalReplace = IsCallable(replaceValue); + if (functionalReplace === Value.false) { + replaceValue = Q(ToString(replaceValue)); + } + const global = ToBoolean(Q(Get(rx, new Value('global')))); + let fullUnicode; + if (global === Value.true) { + fullUnicode = ToBoolean(Q(Get(rx, new Value('unicode')))); + Q(Set(rx, new Value('lastIndex'), new Value(0), Value.true)); + } + + const results = []; + let done = false; + while (!done) { + const result = Q(RegExpExec(rx, S)); + if (result === Value.null) { + done = true; + } else { + results.push(result); + if (global === Value.false) { + done = true; + } else { + const matchStr = Q(ToString(Q(Get(result, new Value('0'))))); + if (matchStr.stringValue() === '') { + const thisIndex = Q(ToLength(Q(Get(rx, new Value('lastIndex'))))); + const nextIndex = AdvanceStringIndex(S, thisIndex, fullUnicode); + Q(Set(rx, new Value('lastIndex'), nextIndex, Value.true)); + } + } + } + } + + let accumulatedResult = ''; + let nextSourcePosition = 0; + for (const result of results) { + let nCaptures = Q(LengthOfArrayLike(result)).numberValue(); + nCaptures = Math.max(nCaptures - 1, 0); + + const matched = Q(ToString(Q(Get(result, new Value('0'))))); + const matchLength = matched.stringValue().length; + + let position = Q(ToInteger(Q(Get(result, new Value('index'))))); + position = new Value(Math.max(Math.min(position.numberValue(), lengthS), 0)); + + let n = 1; + const captures = []; + while (n <= nCaptures) { + let capN = Q(Get(result, X(ToString(new Value(n))))); + if (capN !== Value.undefined) { + capN = Q(ToString(capN)); + } + captures.push(capN); + n += 1; + } + + let namedCaptures = Q(Get(result, new Value('groups'))); + + let replacement; + if (functionalReplace === Value.true) { + const replacerArgs = [matched]; + replacerArgs.push(...captures); + replacerArgs.push(position, S); + if (namedCaptures !== Value.undefined) { + replacerArgs.push(namedCaptures); + } + const replValue = Q(Call(replaceValue, Value.undefined, replacerArgs)); + replacement = Q(ToString(replValue)); + } else { + if (namedCaptures !== Value.undefined) { + namedCaptures = Q(ToObject(namedCaptures)); + } + replacement = Q(GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue)); + } + + if (position.numberValue() >= nextSourcePosition) { + accumulatedResult = accumulatedResult + S.stringValue().substring(nextSourcePosition, position.numberValue()) + replacement.stringValue(); + nextSourcePosition = position.numberValue() + matchLength; + } + } + + if (nextSourcePosition >= lengthS) { + return new Value(accumulatedResult); + } + + return new Value(accumulatedResult + S.stringValue().substring(nextSourcePosition)); +} + +// 21.2.5.11 #sec-regexp.prototype-@@search +function RegExpProto_search([string = Value.undefined], { thisValue }) { + const rx = thisValue; + if (Type(rx) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + const S = Q(ToString(string)); + + const previousLastIndex = Q(Get(rx, new Value('lastIndex'))); + if (SameValue(previousLastIndex, new Value(0)) === Value.false) { + Q(Set(rx, new Value('lastIndex'), new Value(0), Value.true)); + } + + const result = Q(RegExpExec(rx, S)); + const currentLastIndex = Q(Get(rx, new Value('lastIndex'))); + if (SameValue(currentLastIndex, previousLastIndex) === Value.false) { + Q(Set(rx, new Value('lastIndex'), previousLastIndex, Value.true)); + } + + if (result === Value.null) { + return new Value(-1); + } + + return Q(Get(result, new Value('index'))); +} + +// 21.2.5.12 #sec-get-regexp.prototype.source +function RegExpProto_sourceGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalSource' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return new Value('(?:)'); + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + Assert('OriginalFlags' in R); + const src = R.OriginalSource; + const flags = R.OriginalFlags; + return EscapeRegExpPattern(src, flags); +} + +// 21.2.5.13 #sec-regexp.prototype-@@split +function RegExpProto_split([string = Value.undefined, limit = Value.undefined], { thisValue }) { + const rx = thisValue; + if (Type(rx) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + const S = Q(ToString(string)); + + const C = Q(SpeciesConstructor(rx, surroundingAgent.intrinsic('%RegExp%'))); + const flagsValue = Q(Get(rx, new Value('flags'))); + const flags = Q(ToString(flagsValue)).stringValue(); + const unicodeMatching = flags.includes('u') ? Value.true : Value.false; + const newFlags = flags.includes('y') ? new Value(flags) : new Value(`${flags}y`); + const splitter = Q(Construct(C, [rx, newFlags])); + + const A = X(ArrayCreate(new Value(0))); + let lengthA = 0; + + let lim; + if (limit === Value.undefined) { + lim = (2 ** 32) - 1; + } else { + lim = Q(ToUint32(limit)).numberValue(); + } + + const size = S.stringValue().length; + let p = 0; + + if (lim === 0) { + return A; + } + + if (size === 0) { + const z = Q(RegExpExec(splitter, S)); + if (z !== Value.null) { + return A; + } + X(CreateDataProperty(A, new Value('0'), S)); + return A; + } + + let q = new Value(p); + while (q.numberValue() < size) { + Q(Set(splitter, new Value('lastIndex'), q, Value.true)); + const z = Q(RegExpExec(splitter, S)); + if (z === Value.null) { + q = AdvanceStringIndex(S, q, unicodeMatching); + } else { + const lastIndex = Q(Get(splitter, new Value('lastIndex'))); + let e = Q(ToLength(lastIndex)); + e = new Value(Math.min(e.numberValue(), size)); + if (e.numberValue() === p) { + q = AdvanceStringIndex(S, q, unicodeMatching); + } else { + const T = new Value(S.stringValue().substring(p, q.numberValue())); + X(CreateDataProperty(A, X(ToString(new Value(lengthA))), T)); + lengthA += 1; + if (lengthA === lim) { + return A; + } + p = e.numberValue(); + let numberOfCaptures = Q(LengthOfArrayLike(z)).numberValue(); + numberOfCaptures = Math.max(numberOfCaptures - 1, 0); + let i = 1; + while (i <= numberOfCaptures) { + const nextCapture = Q(Get(z, X(ToString(new Value(i))))); + X(CreateDataProperty(A, X(ToString(new Value(lengthA))), nextCapture)); + i += 1; + lengthA += 1; + if (lengthA === lim) { + return A; + } + } + q = new Value(p); + } + } + } + + const T = new Value(S.stringValue().substring(p, size)); + X(CreateDataProperty(A, X(ToString(new Value(lengthA))), T)); + return A; +} + +// 21.2.5.14 #sec-get-regexp.prototype.sticky +function RegExpProto_stickyGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalFlags' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const flags = R.OriginalFlags; + if (flags.stringValue().includes('y')) { + return Value.true; + } + return Value.false; +} + +// 21.2.5.15 #sec-regexp.prototype.test +function RegExpProto_test([S = Value.undefined], { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const string = Q(ToString(S)); + const match = Q(RegExpExec(R, string)); + if (match !== Value.null) { + return Value.true; + } + return Value.false; +} + +// 21.2.5.16 #sec-regexp.prototype.tostring +function RegExpProto_toString(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const pattern = Q(ToString(Q(Get(R, new Value('source'))))); + const flags = Q(ToString(Q(Get(R, new Value('flags'))))); + const result = `/${pattern.stringValue()}/${flags.stringValue()}`; + return new Value(result); +} + +// 21.2.5.17 #sec-get-regexp.prototype.unicode +function RegExpProto_unicodeGetter(args, { thisValue }) { + const R = thisValue; + if (Type(R) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalFlags' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const flags = R.OriginalFlags; + if (flags.stringValue().includes('u')) { + return Value.true; + } + return Value.false; +} + +export function BootstrapRegExpPrototype(realmRec) { + const proto = BootstrapPrototype( + realmRec, + [ + ['exec', RegExpProto_exec, 1], + ['dotAll', [RegExpProto_dotAllGetter]], + ['flags', [RegExpProto_flagsGetter]], + ['global', [RegExpProto_globalGetter]], + ['ignoreCase', [RegExpProto_ignoreCaseGetter]], + [wellKnownSymbols.match, RegExpProto_match, 1], + [wellKnownSymbols.matchAll, RegExpProto_matchAll, 1], + ['multiline', [RegExpProto_multilineGetter]], + [wellKnownSymbols.replace, RegExpProto_replace, 2], + [wellKnownSymbols.search, RegExpProto_search, 1], + ['source', [RegExpProto_sourceGetter]], + [wellKnownSymbols.split, RegExpProto_split, 2], + ['sticky', [RegExpProto_stickyGetter]], + ['test', RegExpProto_test, 1], + ['toString', RegExpProto_toString, 0], + ['unicode', [RegExpProto_unicodeGetter]], + ], + realmRec.Intrinsics['%Object.prototype%'], + ); + + realmRec.Intrinsics['%RegExp.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/RegExpStringIteratorPrototype.mjs b/engine262/src/intrinsics/RegExpStringIteratorPrototype.mjs new file mode 100644 index 0000000..4a8fe6d --- /dev/null +++ b/engine262/src/intrinsics/RegExpStringIteratorPrototype.mjs @@ -0,0 +1,81 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { + Assert, + OrdinaryObjectCreate, + CreateIterResultObject, + ToString, + ToLength, + Get, + Set, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { RegExpExec, AdvanceStringIndex } from './RegExpPrototype.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + + +// 21.2.5.8.1 #sec-createregexpstringiterator +export function CreateRegExpStringIterator(R, S, global, fullUnicode) { + Assert(Type(S) === 'String'); + Assert(Type(global) === 'Boolean'); + Assert(Type(fullUnicode) === 'Boolean'); + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%RegExpStringIteratorPrototype%'), [ + 'IteratingRegExp', + 'IteratedString', + 'Global', + 'Unicode', + 'Done', + ]); + iterator.IteratingRegExp = R; + iterator.IteratedString = S; + iterator.Global = global; + iterator.Unicode = fullUnicode; + iterator.Done = Value.false; + return iterator; +} + +// 21.2.7.1.1 #sec-%regexpstringiteratorprototype%.next +function RegExpStringIteratorPrototype_next(args, { thisValue }) { + const O = thisValue; + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp String Iterator', O); + } + if (!('IteratingRegExp' in O && 'IteratedString' in O && 'Global' in O && 'Unicode' in O && 'Done' in O)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp String Iterator', O); + } + if (O.Done === Value.true) { + return X(CreateIterResultObject(Value.undefined, Value.true)); + } + const R = O.IteratingRegExp; + const S = O.IteratedString; + const global = O.Global; + const fullUnicode = O.Unicode; + const match = Q(RegExpExec(R, S)); + if (match === Value.null) { + O.Done = Value.true; + return X(CreateIterResultObject(Value.undefined, Value.true)); + } else { + if (global === Value.true) { + const matchStrValue = Q(Get(match, new Value('0'))); + const matchStr = Q(ToString(matchStrValue)); + if (matchStr.stringValue() === '') { + const thisIndexValue = Q(Get(R, new Value('lastIndex'))); + const thisIndex = Q(ToLength(thisIndexValue)); + const nextIndex = X(AdvanceStringIndex(S, thisIndex, fullUnicode)); + Q(Set(R, new Value('lastIndex'), nextIndex, Value.true)); + } + return Q(CreateIterResultObject(match, Value.false)); + } else { + O.Done = Value.true; + return Q(CreateIterResultObject(match, Value.false)); + } + } +} + +export function BootstrapRegExpStringIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', RegExpStringIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%IteratorPrototype%'], 'RegExp String Iterator'); + + realmRec.Intrinsics['%RegExpStringIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Set.mjs b/engine262/src/intrinsics/Set.mjs new file mode 100644 index 0000000..49c1ede --- /dev/null +++ b/engine262/src/intrinsics/Set.mjs @@ -0,0 +1,69 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Call, + Get, + GetIterator, + IsCallable, + IteratorClose, + IteratorStep, + IteratorValue, + OrdinaryCreateFromConstructor, +} from '../abstract-ops/all.mjs'; +import { Value, wellKnownSymbols } from '../value.mjs'; +import { AbruptCompletion, Q } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-set-iterable +function SetConstructor([iterable = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »). + const set = Q(OrdinaryCreateFromConstructor(NewTarget, '%Set.prototype%', ['SetData'])); + // 3. Set set.[[SetData]] to a new empty List. + set.SetData = []; + // 4. If iterable is either undefined or null, return set. + if (iterable === Value.undefined || iterable === Value.null) { + return set; + } + // 5. Let adder be ? Get(set, "add"). + const adder = Q(Get(set, new Value('add'))); + // 6. If IsCallable(adder) is false, throw a TypeError exception. + if (IsCallable(adder) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + // 7. Let iteratorRecord be ? GetIterator(iterable). + const iteratorRecord = Q(GetIterator(iterable)); + // 8. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iteratorRecord). + const next = Q(IteratorStep(iteratorRecord)); + // b. If next is false, return set. + if (next === Value.false) { + return set; + } + // c. Let nextValue be ? IteratorValue(next). + const nextValue = Q(IteratorValue(next)); + // d. Let status be Call(adder, set, « nextValue »). + const status = Call(adder, set, [nextValue]); + // e. If status is an abrupt completion, return ? IteratorClose(iteratorRecord, status). + if (status instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, status)); + } + } +} + +// #sec-get-set-@@species +function Set_speciesGetter(args, { thisValue }) { + // Return the this value. + return thisValue; +} + +export function BootstrapSet(realmRec) { + const setConstructor = BootstrapConstructor(realmRec, SetConstructor, 'Set', 0, realmRec.Intrinsics['%Set.prototype%'], [ + [wellKnownSymbols.species, [Set_speciesGetter]], + ]); + + realmRec.Intrinsics['%Set%'] = setConstructor; +} diff --git a/engine262/src/intrinsics/SetIteratorPrototype.mjs b/engine262/src/intrinsics/SetIteratorPrototype.mjs new file mode 100644 index 0000000..1f241b2 --- /dev/null +++ b/engine262/src/intrinsics/SetIteratorPrototype.mjs @@ -0,0 +1,71 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + CreateArrayFromList, + CreateIterResultObject, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-%setiteratorprototype%.next +function SetIteratorPrototype_next(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Set Iterator.prototype.next', O); + } + // 3. If O does not have all of the internal slots of a Set Iterator Instance (23.2.5.3), throw a TypeError exception. + if (!('IteratedSet' in O && 'SetNextIndex' in O && 'SetIterationKind' in O)) { + return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Set Iterator.prototype.next', O); + } + // 4. Let s be O.[[IteratedSet]]. + const s = O.IteratedSet; + // 5. Let index be O.[[SetNextIndex]]. + let index = O.SetNextIndex; + // 6. Let itemKind be O.[[SetIterationKind]]. + const itemKind = O.SetIterationKind; + // 7. If s is undefined, return CreateIterResultObject(undefined, true). + if (s === Value.undefined) { + return CreateIterResultObject(Value.undefined, Value.true); + } + // 8. Assert: s has a [[SetData]] internal slot. + Assert('SetData' in s); + // 9. Let entries be the List that is s.[[SetData]]. + const entries = s.SetData; + // 10. Let numEntries be the number of elements of entries. + const numEntries = entries.length; + // 11. NOTE: numEntries must be redetermined each time this method is evaluated. + while (index < numEntries) { + // a. Repeat, while index is less than numEntries, + const e = entries[index]; + // b. Set index to index + 1. + index += 1; + // c. Set O.[[SetNextIndex]] to index. + O.SetNextIndex = index; + // e. If e is not empty, then + if (e !== undefined) { + // i. If itemKind is key+value, then + if (itemKind === 'key+value') { + // 1. If itemKind is key+value, then + return CreateIterResultObject(CreateArrayFromList([e, e]), Value.false); + } + // ii. Assert: itemKind is value. + Assert(itemKind === 'value'); + // iii. Return CreateIterResultObject(e, false). + return CreateIterResultObject(e, Value.false); + } + } + // 13. Set O.[[IteratedSet]] to undefined. + O.IteratedSet = Value.undefined; + // 14. Return CreateIterResultObject(undefined, true). + return CreateIterResultObject(Value.undefined, Value.true); +} + +export function BootstrapSetIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', SetIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%IteratorPrototype%'], 'Set Iterator'); + + realmRec.Intrinsics['%SetIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/SetPrototype.mjs b/engine262/src/intrinsics/SetPrototype.mjs new file mode 100644 index 0000000..7f39285 --- /dev/null +++ b/engine262/src/intrinsics/SetPrototype.mjs @@ -0,0 +1,194 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Call, + IsCallable, + OrdinaryObjectCreate, + SameValueZero, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// 23.2.5.1 #sec-createsetiterator +function CreateSetIterator(set, kind) { + Q(RequireInternalSlot(set, 'SetData')); + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%SetIteratorPrototype%'), [ + 'IteratedSet', + 'SetNextIndex', + 'SetIterationKind', + ]); + iterator.IteratedSet = set; + iterator.SetNextIndex = 0; + iterator.SetIterationKind = kind; + return iterator; +} + +// #sec-set.prototype.add +function SetProto_add([value = Value.undefined], { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 4. For each e that is an element of entries, do + for (const e of entries) { + // a. For each e that is an element of entries, do + if (e !== undefined && SameValueZero(e, value) === Value.true) { + // i. Return S. + return S; + } + } + // 5. If value is -0, set value to +0. + if (Type(value) === 'Number' && Object.is(value.numberValue(), -0)) { + value = new Value(0); + } + // 6. Append value as the last element of entries. + entries.push(value); + // 7. Return S. + return S; +} + +// #sec-set.prototype.clear +function SetProto_clear(args, { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 4. For each e that is an element of entries, do + for (let i = 0; i < entries.length; i += 1) { + // a. Replace the element of entries whose value is e with an element whose value is empty. + entries[i] = undefined; + } + // 5. Return undefined. + return Value.undefined; +} + +// #sec-set.prototype.delete +function SetProto_delete([value = Value.undefined], { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 4. For each e that is an element of entries, do + for (let i = 0; i < entries.length; i += 1) { + const e = entries[i]; + // a. If e is not empty and SameValueZero(e, value) is true, then + if (e !== undefined && SameValueZero(e, value) === Value.true) { + // i. Replace the element of entries whose value is e with an element whose value is empty. + entries[i] = undefined; + // ii. Return true. + return Value.true; + } + } + // 5. Return false. + return Value.false; +} + +// #sec-set.prototype.entries +function SetProto_entries(args, { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Return ? CreateSetIterator(S, key+value). + return Q(CreateSetIterator(S, 'key+value')); +} + +// #sec-set.prototype.foreach +function SetProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 4. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 5. For each e that is an element of entries, in original insertion order, do + for (const e of entries) { + // a. If e is not empty, then + if (e !== undefined) { + // i. Perform ? Call(callbackfn, thisArg, « e, e, S »). + Q(Call(callbackfn, thisArg, [e, e, S])); + } + } + // 6. Return undefined. + return Value.undefined; +} + +// #sec-set.prototype.has +function SetProto_has([value = Value.undefined], { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 4. Let entries be the List that is S.[[SetData]]. + for (const e of entries) { + // a. If e is not empty and SameValueZero(e, value) is true, return true. + if (e !== undefined && SameValueZero(e, value) === Value.true) { + return Value.true; + } + } + // 5. Return false. + return Value.false; +} + +// #sec-get-set.prototype.size +function SetProto_sizeGetter(args, { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 4. Let count be 0. + let count = 0; + // 5. For each e that is an element of entries, do + for (const e of entries) { + // a. If e is not empty, set count to count + 1 + if (e !== undefined) { + count += 1; + } + } + // 6. Return count. + return new Value(count); +} + +// #sec-set.prototype.values +function SetProto_values(args, { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Return ? CreateSetIterator(S, value). + return Q(CreateSetIterator(S, 'value')); +} + +export function BootstrapSetPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['add', SetProto_add, 1], + ['clear', SetProto_clear, 0], + ['delete', SetProto_delete, 1], + ['entries', SetProto_entries, 0], + ['forEach', SetProto_forEach, 1], + ['has', SetProto_has, 1], + ['size', [SetProto_sizeGetter]], + ['values', SetProto_values, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Set'); + + const valuesFunc = X(proto.GetOwnProperty(new Value('values'))); + X(proto.DefineOwnProperty(new Value('keys'), valuesFunc)); + X(proto.DefineOwnProperty(wellKnownSymbols.iterator, valuesFunc)); + + realmRec.Intrinsics['%Set.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/String.mjs b/engine262/src/intrinsics/String.mjs new file mode 100644 index 0000000..868bdde --- /dev/null +++ b/engine262/src/intrinsics/String.mjs @@ -0,0 +1,114 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Type, + Value, +} from '../value.mjs'; +import { + Get, + GetPrototypeFromConstructor, + IsInteger, + StringCreate, + SymbolDescriptiveString, + LengthOfArrayLike, + ToNumber, + ToObject, + ToString, + ToUint16, +} from '../abstract-ops/all.mjs'; +import { CodePointToUTF16CodeUnits } from '../static-semantics/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// 21.1.1.1 #sec-string-constructor-string-value +function StringConstructor([value], { NewTarget }) { + let s; + if (value === undefined) { + s = new Value(''); + } else { + if (NewTarget === Value.undefined && Type(value) === 'Symbol') { + return X(SymbolDescriptiveString(value)); + } + s = Q(ToString(value)); + } + if (NewTarget === Value.undefined) { + return s; + } + return X(StringCreate(s, Q(GetPrototypeFromConstructor(NewTarget, '%String.prototype%')))); +} + +// 21.1.2.1 #sec-string.fromcharcode +function String_fromCharCode(codeUnits) { + const length = codeUnits.length; + const elements = []; + let nextIndex = 0; + while (nextIndex < length) { + const next = codeUnits[nextIndex]; + const nextCU = Q(ToUint16(next)); + elements.push(nextCU); + nextIndex += 1; + } + const result = elements.reduce((previous, current) => previous + String.fromCharCode(current.numberValue()), ''); + return new Value(result); +} + +// 21.1.2.2 #sec-string.fromcodepoint +function String_fromCodePoint(codePoints) { + const length = codePoints.length; + const elements = []; + let nextIndex = 0; + while (nextIndex < length) { + const next = codePoints[nextIndex]; + const nextCP = Q(ToNumber(next)); + if (X(IsInteger(nextCP)) === Value.false) { + return surroundingAgent.Throw('RangeError', 'StringCodePointInvalid', next); + } + if (nextCP.numberValue() < 0 || nextCP.numberValue() > 0x10FFFF) { + return surroundingAgent.Throw('RangeError', 'StringCodePointInvalid', nextCP); + } + elements.push(...CodePointToUTF16CodeUnits(nextCP.numberValue())); + nextIndex += 1; + } + const result = elements.reduce((previous, current) => previous + String.fromCharCode(current), ''); + return new Value(result); +} + +// 21.1.2.4 #sec-string.raw +function String_raw([template = Value.undefined, ...substitutions]) { + const numberOfSubstitutions = substitutions.length; + const cooked = Q(ToObject(template)); + const raw = Q(ToObject(Q(Get(cooked, new Value('raw'))))); + const literalSegments = Q(LengthOfArrayLike(raw)).numberValue(); + if (literalSegments <= 0) { + return new Value(''); + } + // Not sure why the spec uses a List, but this is really just a String. + const stringElements = []; + let nextIndex = 0; + while (true) { + const nextKey = X(ToString(new Value(nextIndex))); + const nextSeg = Q(ToString(Q(Get(raw, nextKey)))); + stringElements.push(nextSeg.stringValue()); + if (nextIndex + 1 === literalSegments) { + return new Value(stringElements.join('')); + } + let next; + if (nextIndex < numberOfSubstitutions) { + next = substitutions[nextIndex]; + } else { + next = new Value(''); + } + const nextSub = Q(ToString(next)); + stringElements.push(nextSub.stringValue()); + nextIndex += 1; + } +} + +export function BootstrapString(realmRec) { + const stringConstructor = BootstrapConstructor(realmRec, StringConstructor, 'String', 1, realmRec.Intrinsics['%String.prototype%'], [ + ['fromCharCode', String_fromCharCode, 1], + ['fromCodePoint', String_fromCodePoint, 1], + ['raw', String_raw, 1], + ]); + + realmRec.Intrinsics['%String%'] = stringConstructor; +} diff --git a/engine262/src/intrinsics/StringIteratorPrototype.mjs b/engine262/src/intrinsics/StringIteratorPrototype.mjs new file mode 100644 index 0000000..b4fba34 --- /dev/null +++ b/engine262/src/intrinsics/StringIteratorPrototype.mjs @@ -0,0 +1,75 @@ +import { Type, Value } from '../value.mjs'; +import { + Assert, + CreateIterResultObject, + OrdinaryObjectCreate, +} from '../abstract-ops/all.mjs'; +import { CodePointAt } from '../static-semantics/all.mjs'; +import { X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + + +// #sec-createstringiterator +export function CreateStringIterator(string) { + // 1. Assert: Type(string) is String. + Assert(Type(string) === 'String'); + // 2. Let iterator be OrdinaryObjectCreate(%StringIteratorPrototype%, « [[IteratedString]], [[StringNextIndex]] »). + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%StringIteratorPrototype%'), [ + 'IteratedString', + 'StringNextIndex', + ]); + // 3. Set iterator.[[IteratedString]] to string. + iterator.IteratedString = string; + // 4. Set iterator.[[StringNextIndex]] to 0. + iterator.StringNextIndex = 0; + // 5. Return iterator. + return iterator; +} + +// #sec-%stringiteratorprototype%.next +function StringIteratorPrototype_next(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. If Type(O) is not Object, throw a TypeError exception. + if (Type(O) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'String Iterator', O); + } + // 3. If O does not have all of the internal slots of a String Iterator Instance (21.1.5.3), throw a TypeError exception. + if (!('IteratedString' in O && 'StringNextIndex' in O)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'String Iterator', O); + } + // 4. Let s be O.[[IteratedString]]. + const s = O.IteratedString; + // 5. Let s be O.[[IteratedString]]. + if (s === Value.undefined) { + return CreateIterResultObject(Value.undefined, Value.true); + } + // 6. If s is undefined, return CreateIterResultObject(undefined, true). + const position = O.StringNextIndex; + // 7. Let len be the length of s. + const len = s.stringValue().length; + // 8. If position ≥ len, then + if (position >= len) { + // a. Set O.[[IteratedString]] to undefined. + O.IteratedString = Value.undefined; + // b. Return CreateIterResultObject(undefined, true). + return CreateIterResultObject(Value.undefined, Value.true); + } + // 9. Let cp be ! CodePointAt(s, position). + const cp = X(CodePointAt(s.stringValue(), position)); + // 10. Let resultString be the String value containing cp.[[CodeUnitCount]] consecutive code units from s beginning with the code unit at index position. + const resultString = new Value(s.stringValue().substr(position, cp.CodeUnitCount)); + // 11. Set O.[[StringNextIndex]] to position + cp.[[CodeUnitCount]]. + O.StringNextIndex = position + cp.CodeUnitCount; + // 12. Return CreateIterResultObject(resultString, false). + return CreateIterResultObject(resultString, Value.false); +} + +export function BootstrapStringIteratorPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['next', StringIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%IteratorPrototype%'], 'String Iterator'); + + realmRec.Intrinsics['%StringIteratorPrototype%'] = proto; +} diff --git a/engine262/src/intrinsics/StringPrototype.mjs b/engine262/src/intrinsics/StringPrototype.mjs new file mode 100644 index 0000000..059e886 --- /dev/null +++ b/engine262/src/intrinsics/StringPrototype.mjs @@ -0,0 +1,720 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + ArrayCreate, + Assert, + Call, + CreateDataPropertyOrThrow, + Get, + GetMethod, + Invoke, + IsCallable, + IsRegExp, + RegExpCreate, + RequireObjectCoercible, + ToInteger, + ToNumber, + ToString, + ToUint32, + StringCreate, +} from '../abstract-ops/all.mjs'; +import { + GetSubstitution, + TrimString, + StringPad, + StringIndexOf, +} from '../runtime-semantics/all.mjs'; +import { CodePointAt } from '../static-semantics/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { CreateStringIterator } from './StringIteratorPrototype.mjs'; +import { assignProps } from './Bootstrap.mjs'; + + +function thisStringValue(value) { + if (Type(value) === 'String') { + return value; + } + if (Type(value) === 'Object' && 'StringData' in value) { + const s = value.StringData; + Assert(Type(s) === 'String'); + return s; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'String', value); +} + +// 21.1.3.1 #sec-string.prototype.charat +function StringProto_charAt([pos = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const position = Q(ToInteger(pos)).numberValue(); + const size = S.stringValue().length; + if (position < 0 || position >= size) { + return new Value(''); + } + return new Value(S.stringValue()[position]); +} + +// 21.1.3.2 #sec-string.prototype.charcodeat +function StringProto_charCodeAt([pos = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const position = Q(ToInteger(pos)).numberValue(); + const size = S.stringValue().length; + if (position < 0 || position >= size) { + return new Value(NaN); + } + return new Value(S.stringValue().charCodeAt(position)); +} + +// 21.1.3.3 #sec-string.prototype.codepointat +function StringProto_codePointAt([pos = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const position = Q(ToInteger(pos)).numberValue(); + const size = S.stringValue().length; + if (position < 0 || position >= size) { + return Value.undefined; + } + const cp = X(CodePointAt(S.stringValue(), position)); + return new Value(cp.CodePoint); +} + +// 21.1.3.4 #sec-string.prototype.concat +function StringProto_concat(args, { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + let R = S.stringValue(); + while (args.length > 0) { + const next = args.shift(); + const nextString = Q(ToString(next)); + R = `${R}${nextString.stringValue()}`; + } + return new Value(R); +} + +// 21.1.3.6 #sec-string.prototype.endswith +function StringProto_endsWith([searchString = Value.undefined, endPosition = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const isRegExp = Q(IsRegExp(searchString)); + if (isRegExp === Value.true) { + return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.endsWith'); + } + const searchStr = Q(ToString(searchString)).stringValue(); + const len = S.length; + let pos; + if (endPosition === Value.undefined) { + pos = len; + } else { + pos = Q(ToInteger(endPosition)).numberValue(); + } + const end = Math.min(Math.max(pos, 0), len); + const searchLength = searchStr.length; + const start = end - searchLength; + if (start < 0) { + return Value.false; + } + for (let i = 0; i < searchLength; i += 1) { + if (S.charCodeAt(start + i) !== searchStr.charCodeAt(i)) { + return Value.false; + } + } + return Value.true; +} + +// 21.1.3.7 #sec-string.prototype.includes +function StringProto_includes([searchString = Value.undefined, position = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const isRegExp = Q(IsRegExp(searchString)); + if (isRegExp === Value.true) { + return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.includes'); + } + const searchStr = Q(ToString(searchString)).stringValue(); + const pos = Q(ToInteger(position)); + Assert(!(position === Value.undefined) || pos.numberValue() === 0); + const len = S.length; + const start = Math.min(Math.max(pos.numberValue(), 0), len); + const searchLen = searchStr.length; + let k = start; + while (k + searchLen <= len) { + let match = true; + for (let j = 0; j < searchLen; j += 1) { + if (searchStr[j] !== S[k + j]) { + match = false; + break; + } + } + if (match) { + return Value.true; + } + k += 1; + } + return Value.false; +} + +// #sec-string.prototype.indexof +function StringProto_indexOf([searchString = Value.undefined, position = Value.undefined], { thisValue }) { + // 1. Let O be ? RequireObjectCoercible(this value). + const O = Q(RequireObjectCoercible(thisValue)); + // 2. Let S be ? ToString(O). + const S = Q(ToString(O)); + // 3. Let searchStr be ? ToString(searchString). + const searchStr = Q(ToString(searchString)); + // 4. Let pos be ? ToInteger(position). + const pos = Q(ToInteger(position)); + // 5. Assert: If position is undefined, then pos is 0. + Assert(!(position === Value.undefined) || pos.numberValue() === 0); + // 6. Let len be the length of S. + const len = S.stringValue().length; + // 7. Let start be min(max(pos, 0), len). + const start = Math.min(Math.max(pos.numberValue(), 0), len); + // 8. Return ! StringIndexOf(S, searchStr, start). + return X(StringIndexOf(S, searchStr, start)); +} + +// 21.1.3.9 #sec-string.prototype.lastindexof +function StringProto_lastIndexOf([searchString = Value.undefined, position = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const searchStr = Q(ToString(searchString)).stringValue(); + const numPos = Q(ToNumber(position)); + Assert(!(position === Value.undefined) || numPos.isNaN()); + let pos; + if (numPos.isNaN()) { + pos = new Value(Infinity); + } else { + pos = X(ToInteger(numPos)); + } + const len = S.length; + const start = Math.min(Math.max(pos.numberValue(), 0), len); + const searchLen = searchStr.length; + let k = start; + while (k >= 0) { + if (k + searchLen <= len) { + let match = true; + for (let j = 0; j < searchLen; j += 1) { + if (searchStr[j] !== S[k + j]) { + match = false; + break; + } + } + if (match) { + return new Value(k); + } + } + k -= 1; + } + return new Value(-1); +} + +// 21.1.3.10 #sec-string.prototype.localecompare +function StringProto_localeCompare([that = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const That = Q(ToString(that)).stringValue(); + if (S === That) { + return new Value(0); + } else if (S < That) { + return new Value(-1); + } else { + return new Value(1); + } +} + +// 21.1.3.11 #sec-string.prototype.match +function StringProto_match([regexp = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + + if (regexp !== Value.undefined && regexp !== Value.null) { + const matcher = Q(GetMethod(regexp, wellKnownSymbols.match)); + if (matcher !== Value.undefined) { + return Q(Call(matcher, regexp, [O])); + } + } + + const S = Q(ToString(O)); + const rx = Q(RegExpCreate(regexp, Value.undefined)); + return Q(Invoke(rx, wellKnownSymbols.match, [S])); +} + +// 21.1.3.12 #sec-string.prototype.matchall +function StringProto_matchAll([regexp = Value.undefined], { thisValue }) { + // 1. Let O be ? RequireObjectCoercible(this value). + const O = Q(RequireObjectCoercible(thisValue)); + // 2. If regexp is neither undefined nor null, then + if (regexp !== Value.undefined && regexp !== Value.null) { + // a. Let isRegExp be ? IsRegExp(regexp). + const isRegExp = Q(IsRegExp(regexp)); + // b. If isRegExp is true, then + if (isRegExp === Value.true) { + // i. Let flags be ? Get(regexp, "flags"). + const flags = Q(Get(regexp, new Value('flags'))); + // ii. Perform ? RequireObjectCoercible(flags). + Q(RequireObjectCoercible(flags)); + // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. + if (!Q(ToString(flags)).stringValue().includes('g')) { + return surroundingAgent.Throw('TypeError', 'StringPrototypeMethodGlobalRegExp', 'matchAll'); + } + } + // c. Let matcher be ? GetMethod(regexp, @@matchAll). + const matcher = Q(GetMethod(regexp, wellKnownSymbols.matchAll)); + // d. If matcher is not undefined, then + if (matcher !== Value.undefined) { + // i. Return ? Call(matcher, regexp, « O »). + return Q(Call(matcher, regexp, [O])); + } + } + // 3. Let S be ? ToString(O). + const S = Q(ToString(O)); + // 4. Let rx be ? RegExpCreate(regexp, "g"). + const rx = Q(RegExpCreate(regexp, new Value('g'))); + // 5. Return ? Invoke(rx, @@matchAll, « S »). + return Q(Invoke(rx, wellKnownSymbols.matchAll, [S])); +} + +// 21.1.3.13 #sec-string.prototype.normalize +function StringProto_normalize([form = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + if (form === Value.undefined) { + form = new Value('NFC'); + } else { + form = Q(ToString(form)); + } + const f = form.stringValue(); + if (!['NFC', 'NFD', 'NFKC', 'NFKD'].includes(f)) { + return surroundingAgent.Throw('RangeError', 'NormalizeInvalidForm'); + } + const ns = S.stringValue().normalize(f); + return new Value(ns); +} + +// 21.1.3.14 #sec-string.prototype.padend +function StringProto_padEnd([maxLength = Value.undefined, fillString = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + return Q(StringPad(O, maxLength, fillString, 'end')); +} + +// 21.1.3.15 #sec-string.prototype.padstart +function StringProto_padStart([maxLength = Value.undefined, fillString = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + return Q(StringPad(O, maxLength, fillString, 'start')); +} + +// 21.1.3.16 #sec-string.prototype.repeat +function StringProto_repeat([count = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const n = Q(ToInteger(count)); + if (n.numberValue() < 0) { + return surroundingAgent.Throw('RangeError', 'StringRepeatCount', n); + } + if (n.isInfinity()) { + return surroundingAgent.Throw('RangeError', 'StringRepeatCount', n); + } + if (n.numberValue() === 0) { + return new Value(''); + } + let T = ''; + for (let i = 0; i < n.numberValue(); i += 1) { + T += S.stringValue(); + } + return new Value(T); +} + +// 21.1.3.17 #sec-string.prototype.replace +function StringProto_replace([searchValue = Value.undefined, replaceValue = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + if (searchValue !== Value.undefined && searchValue !== Value.null) { + const replacer = Q(GetMethod(searchValue, wellKnownSymbols.replace)); + if (replacer !== Value.undefined) { + return Q(Call(replacer, searchValue, [O, replaceValue])); + } + } + const string = Q(ToString(O)); + const searchString = Q(ToString(searchValue)); + const functionalReplace = IsCallable(replaceValue); + if (functionalReplace === Value.false) { + replaceValue = Q(ToString(replaceValue)); + } + const pos = new Value(string.stringValue().indexOf(searchString.stringValue())); + const matched = searchString; + if (pos.numberValue() === -1) { + return string; + } + let replStr; + if (functionalReplace === Value.true) { + const replValue = Q(Call(replaceValue, Value.undefined, [matched, pos, string])); + replStr = Q(ToString(replValue)); + } else { + const captures = []; + replStr = X(GetSubstitution(matched, string, pos, captures, Value.undefined, replaceValue)); + } + const tailPos = pos.numberValue() + matched.stringValue().length; + const newString = string.stringValue().slice(0, pos.numberValue()) + replStr.stringValue() + string.stringValue().slice(tailPos); + return new Value(newString); +} + +// #sec-string.prototype.replaceall +function StringProto_replaceAll([searchValue = Value.undefined, replaceValue = Value.undefined], { thisValue }) { + // 1. Let O be ? RequireObjectCoercible(this value). + const O = Q(RequireObjectCoercible(thisValue)); + // 2.If searchValue is neither undefined nor null, then + if (searchValue !== Value.undefined && searchValue !== Value.null) { + // a. Let isRegExp be ? IsRegExp(searchValue). + const isRegExp = Q(IsRegExp(searchValue)); + // b. If isRegExp is true, then + if (isRegExp === Value.true) { + // i. Let flags be ? Get(searchValue, "flags"). + const flags = Q(Get(searchValue, new Value('flags'))); + // ii. Perform ? RequireObjectCoercible(flags). + Q(RequireObjectCoercible(flags)); + // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. + if (!Q(ToString(flags)).stringValue().includes('g')) { + return surroundingAgent.Throw('TypeError', 'StringPrototypeMethodGlobalRegExp', 'replaceAll'); + } + } + // c. Let replacer be ? GetMethod(searchValue, @@replace). + const replacer = Q(GetMethod(searchValue, wellKnownSymbols.replace)); + // d. If replacer is not undefined, then + if (replacer !== Value.undefined) { + // i. Return ? Call(replacer, searchValue, « O, replaceValue »). + return Q(Call(replacer, searchValue, [O, replaceValue])); + } + } + // 3. Let string be ? ToString(O). + const string = Q(ToString(O)); + // 4. Let searchString be ? ToString(searchValue). + const searchString = Q(ToString(searchValue)); + // 5. Let functionalReplace be IsCallable(replaceValue). + const functionalReplace = IsCallable(replaceValue); + // 6. If functionalReplace is false, then + if (functionalReplace === Value.false) { + // a. Let replaceValue be ? ToString(replaceValue). + replaceValue = Q(ToString(replaceValue)); + } + // 7. Let searchLength be the length of searchString. + const searchLength = searchString.stringValue().length; + // 8. Let advanceBy be max(1, searchLength). + const advanceBy = Math.max(1, searchLength); + // 9. Let matchPositions be a new empty List. + const matchPositions = []; + // 10. Let position be ! StringIndexOf(string, searchString, 0). + let position = X(StringIndexOf(string, searchString, 0)).numberValue(); + // 11. Repeat, while position is not -1 + while (position !== -1) { + // a. Append position to the end of matchPositions. + matchPositions.push(position); + // b. Let position be ! StringIndexOf(string, searchString, position + advanceBy). + position = X(StringIndexOf(string, searchString, position + advanceBy)).numberValue(); + } + // 12. Let endOfLastMatch be 0. + let endOfLastMatch = 0; + // 13. Let result be the empty string value. + let result = ''; + // 14. For each position in matchPositions, do + for (position of matchPositions) { + let replacement; + // a. If functionalReplace is true, then + if (functionalReplace === Value.true) { + // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, position, string »). + replacement = Q(ToString(Q(Call(replaceValue, Value.undefined, [searchString, new Value(position), string])))); + } else { // b. Else, + // i. Assert: Type(replaceValue) is String. + Assert(Type(replaceValue) === 'String'); + // ii. Let captures be a new empty List. + const captures = []; + // iii. Let replacement be GetSubstitution(searchString, string, position, captures, undefined, replaceValue). + replacement = GetSubstitution(searchString, string, new Value(position), captures, Value.undefined, replaceValue); + } + // c. Let stringSlice be the substring of string consisting of the code units from endOfLastMatch (inclusive) up through position (exclusive). + const stringSlice = string.stringValue().slice(endOfLastMatch, position); + // d. Let result be the string-concatenation of result, stringSlice, and replacement. + result = result + stringSlice + replacement.stringValue(); + // e. Let endOfLastMatch be position + searchLength. + endOfLastMatch = position + searchLength; + } + // 15. If endOfLastMatch < the length of string, then + if (endOfLastMatch < string.stringValue().length) { + // a. Let result be the string-concatenation of result and the substring of string consisting of the code units from endOfLastMatch (inclusive) up through the final code unit of string (inclusive). + result += string.stringValue().slice(endOfLastMatch); + } + // 16. Return result. + return new Value(result); +} + +// 21.1.3.19 #sec-string.prototype.slice +function StringProto_search([regexp = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + + if (regexp !== Value.undefined && regexp !== Value.null) { + const searcher = Q(GetMethod(regexp, wellKnownSymbols.search)); + if (searcher !== Value.undefined) { + return Q(Call(searcher, regexp, [O])); + } + } + + const string = Q(ToString(O)); + const rx = Q(RegExpCreate(regexp, Value.undefined)); + return Q(Invoke(rx, wellKnownSymbols.search, [string])); +} + +// 21.1.3.19 #sec-string.prototype.slice +function StringProto_slice([start = Value.undefined, end = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const len = S.length; + const intStart = Q(ToInteger(start)).numberValue(); + let intEnd; + if (end === Value.undefined) { + intEnd = len; + } else { + intEnd = Q(ToInteger(end)).numberValue(); + } + let from; + if (intStart < 0) { + from = Math.max(len + intStart, 0); + } else { + from = Math.min(intStart, len); + } + let to; + if (intEnd < 0) { + to = Math.max(len + intEnd, 0); + } else { + to = Math.min(intEnd, len); + } + const span = Math.max(to - from, 0); + return new Value(S.slice(from, from + span)); +} + +// #sec-string.prototype.split +function StringProto_split([separator = Value.undefined, limit = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + if (separator !== Value.undefined && separator !== Value.null) { + const splitter = Q(GetMethod(separator, wellKnownSymbols.split)); + if (splitter !== Value.undefined) { + return Q(Call(splitter, separator, [O, limit])); + } + } + const S = Q(ToString(O)); + const A = X(ArrayCreate(new Value(0))); + let lengthA = 0; + let lim; + if (limit === Value.undefined) { + lim = new Value((2 ** 32) - 1); + } else { + lim = Q(ToUint32(limit)); + } + const s = S.stringValue().length; + let p = 0; + const R = Q(ToString(separator)); + if (lim.numberValue() === 0) { + return A; + } + if (separator === Value.undefined) { + X(CreateDataPropertyOrThrow(A, new Value('0'), S)); + return A; + } + if (s === 0) { + if (R.stringValue() !== '') { + X(CreateDataPropertyOrThrow(A, new Value('0'), S)); + } + return A; + } + let q = p; + while (q !== s) { + const e = SplitMatch(S, q, R); + if (e === false) { + q += 1; + } else { + if (e === p) { + q += 1; + } else { + const T = new Value(S.stringValue().substring(p, q)); + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(lengthA))), T)); + lengthA += 1; + if (lengthA === lim.numberValue()) { + return A; + } + p = e; + q = p; + } + } + } + const T = new Value(S.stringValue().substring(p, s)); + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(lengthA))), T)); + return A; +} + +// 21.1.3.20.1 #sec-splitmatch +function SplitMatch(S, q, R) { + Assert(Type(R) === 'String'); + const r = R.stringValue().length; + const s = S.stringValue().length; + if (q + r > s) { + return false; + } + for (let i = 0; i < r; i += 1) { + if (S.stringValue().charCodeAt(q + i) !== R.stringValue().charCodeAt(i)) { + return false; + } + } + return q + r; +} + +// 21.1.3.21 #sec-string.prototype.startswith +function StringProto_startsWith([searchString = Value.undefined, position = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const isRegExp = Q(IsRegExp(searchString)); + if (isRegExp === Value.true) { + return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.startsWith'); + } + const searchStr = Q(ToString(searchString)).stringValue(); + const pos = Q(ToInteger(position)).numberValue(); + Assert(!(position === Value.undefined) || pos === 0); + const len = S.length; + const start = Math.min(Math.max(pos, 0), len); + const searchLength = searchStr.length; + if (searchLength + start > len) { + return Value.false; + } + for (let i = 0; i < searchLength; i += 1) { + if (S.charCodeAt(start + i) !== searchStr.charCodeAt(i)) { + return Value.false; + } + } + return Value.true; +} + +// 21.1.3.22 #sec-string.prototype.substring +function StringProto_substring([start = Value.undefined, end = Value.undefined], { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)).stringValue(); + const len = S.length; + const intStart = Q(ToInteger(start)).numberValue(); + let intEnd; + if (end === Value.undefined) { + intEnd = len; + } else { + intEnd = Q(ToInteger(end)).numberValue(); + } + const finalStart = Math.min(Math.max(intStart, 0), len); + const finalEnd = Math.min(Math.max(intEnd, 0), len); + const from = Math.min(finalStart, finalEnd); + const to = Math.max(finalStart, finalEnd); + return new Value(S.slice(from, to)); +} + +// 21.1.3.23 #sec-string.prototype.tolocalelowercase +function StringProto_toLocaleLowerCase(args, { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const L = S.stringValue().toLocaleLowerCase(); + return new Value(L); +} + +// 21.1.3.24 #sec-string.prototype.tolocaleuppercase +function StringProto_toLocaleUpperCase(args, { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const L = S.stringValue().toLocaleUpperCase(); + return new Value(L); +} + +// 21.1.3.25 #sec-string.prototype.tolowercase +function StringProto_toLowerCase(args, { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const L = S.stringValue().toLowerCase(); + return new Value(L); +} + +// 21.1.3.26 #sec-string.prototype.tostring +function StringProto_toString(args, { thisValue }) { + return Q(thisStringValue(thisValue)); +} + +// 21.1.3.27 #sec-string.prototype.touppercase +function StringProto_toUpperCase(args, { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + const L = S.stringValue().toUpperCase(); + return new Value(L); +} + +// 21.1.3.28 #sec-string.prototype.trim +function StringProto_trim(args, { thisValue }) { + const S = thisValue; + return Q(TrimString(S, 'start+end')); +} + +// 21.1.3.29 #sec-string.prototype.trimend +function StringProto_trimEnd(args, { thisValue }) { + const S = thisValue; + return Q(TrimString(S, 'end')); +} + +// 21.1.3.30 #sec-string.prototype.trimstart +function StringProto_trimStart(args, { thisValue }) { + const S = thisValue; + return Q(TrimString(S, 'start')); +} + +// 21.1.3.31 #sec-string.prototype.valueof +function StringProto_valueOf(args, { thisValue }) { + return Q(thisStringValue(thisValue)); +} + +// 21.1.3.32 #sec-string.prototype-@@iterator +function StringProto_iterator(args, { thisValue }) { + const O = Q(RequireObjectCoercible(thisValue)); + const S = Q(ToString(O)); + return Q(CreateStringIterator(S)); +} + +export function BootstrapStringPrototype(realmRec) { + const proto = StringCreate(new Value(''), realmRec.Intrinsics['%Object.prototype%']); + + assignProps(realmRec, proto, [ + ['charAt', StringProto_charAt, 1], + ['charCodeAt', StringProto_charCodeAt, 1], + ['codePointAt', StringProto_codePointAt, 1], + ['concat', StringProto_concat, 1], + ['endsWith', StringProto_endsWith, 1], + ['includes', StringProto_includes, 1], + ['indexOf', StringProto_indexOf, 1], + ['lastIndexOf', StringProto_lastIndexOf, 1], + ['localeCompare', StringProto_localeCompare, 1], + ['match', StringProto_match, 1], + ['matchAll', StringProto_matchAll, 1], + ['normalize', StringProto_normalize, 0], + ['padEnd', StringProto_padEnd, 1], + ['padStart', StringProto_padStart, 1], + ['repeat', StringProto_repeat, 1], + ['replace', StringProto_replace, 2], + ['replaceAll', StringProto_replaceAll, 2], + ['search', StringProto_search, 1], + ['slice', StringProto_slice, 2], + ['split', StringProto_split, 2], + ['startsWith', StringProto_startsWith, 1], + ['substring', StringProto_substring, 2], + ['toLocaleLowerCase', StringProto_toLocaleLowerCase, 0], + ['toLocaleUpperCase', StringProto_toLocaleUpperCase, 0], + ['toLowerCase', StringProto_toLowerCase, 0], + ['toString', StringProto_toString, 0], + ['toUpperCase', StringProto_toUpperCase, 0], + ['trim', StringProto_trim, 0], + ['trimEnd', StringProto_trimEnd, 0], + ['trimStart', StringProto_trimStart, 0], + ['valueOf', StringProto_valueOf, 0], + [wellKnownSymbols.iterator, StringProto_iterator, 0], + ]); + + realmRec.Intrinsics['%String.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/Symbol.mjs b/engine262/src/intrinsics/Symbol.mjs new file mode 100644 index 0000000..a7f718a --- /dev/null +++ b/engine262/src/intrinsics/Symbol.mjs @@ -0,0 +1,98 @@ +import { + Descriptor, + SymbolValue, + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + surroundingAgent, +} from '../engine.mjs'; +import { + SameValue, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +export const GlobalSymbolRegistry = []; + +// #sec-symbol-description +function SymbolConstructor([description = Value.undefined], { NewTarget }) { + // 1. If NewTarget is not undefined, throw a TypeError exception. + if (NewTarget !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. If description is undefined, let descString be undefined. + let descString; + if (description === Value.undefined) { + descString = Value.undefined; + } else { // 3. Else, let descString be ? ToString(description). + descString = Q(ToString(description)); + } + // 4. Return a new unique Symbol value whose [[Description]] value is descString. + return new SymbolValue(descString); +} + +// #sec-symbol.for +function Symbol_for([key = Value.undefined]) { + // 1. Let stringKey be ? ToString(key). + const stringKey = Q(ToString(key)); + // 2. For each element e of the GlobalSymbolRegistry List, do + for (const e of GlobalSymbolRegistry) { + // a. If SameValue(e.[[Key]], stringKey) is true, return e.[[Symbol]]. + if (SameValue(e.Key, stringKey) === Value.true) { + return e.Symbol; + } + } + // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for stringKey. + // 4. Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey. + const newSymbol = new SymbolValue(stringKey); + // 5. Append the Record { [[Key]]: stringKey, [[Symbol]]: newSymbol } to the GlobalSymbolRegistry List. + GlobalSymbolRegistry.push({ Key: stringKey, Symbol: newSymbol }); + // 6. Return newSymbol. + return newSymbol; +} + +// #sec-symbol.keyfor +function Symbol_keyFor([sym = Value.undefined]) { + // 1. If Type(sym) is not Symbol, throw a TypeError exception. + if (Type(sym) !== 'Symbol') { + return surroundingAgent.Throw('TypeError', 'NotASymbol', sym); + } + // 2. For each element e of the GlobalSymbolRegistry List, do + for (const e of GlobalSymbolRegistry) { + // a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]]. + if (SameValue(e.Symbol, sym) === Value.true) { + return e.Key; + } + } + // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym. + // 4. Return undefined. + return Value.undefined; +} + +export function BootstrapSymbol(realmRec) { + const symbolConstructor = BootstrapConstructor(realmRec, SymbolConstructor, 'Symbol', 0, realmRec.Intrinsics['%Symbol.prototype%'], [ + ['for', Symbol_for, 1], + ['keyFor', Symbol_keyFor, 1], + ]); + + for (const [name, sym] of Object.entries(wellKnownSymbols)) { + symbolConstructor.DefineOwnProperty(new Value(name), Descriptor({ + Value: sym, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + })); + } + + symbolConstructor.DefineOwnProperty(new Value('prototype'), Descriptor({ + Value: realmRec.Intrinsics['%Symbol.prototype%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + })); + + realmRec.Intrinsics['%Symbol%'] = symbolConstructor; +} diff --git a/engine262/src/intrinsics/SymbolPrototype.mjs b/engine262/src/intrinsics/SymbolPrototype.mjs new file mode 100644 index 0000000..393342b --- /dev/null +++ b/engine262/src/intrinsics/SymbolPrototype.mjs @@ -0,0 +1,79 @@ +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { + surroundingAgent, +} from '../engine.mjs'; +import { + Assert, + SymbolDescriptiveString, +} from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-thissymbolvalue +function thisSymbolValue(value) { + // 1. If Type(value) is Symbol, return value. + if (Type(value) === 'Symbol') { + return value; + } + // 2. If Type(value) is Object and value has a [[SymbolData]] internal slot, then + if (Type(value) === 'Object' && 'SymbolData' in value) { + // a. Let s be value.[[SymbolData]]. + const s = value.SymbolData; + // b. Assert: Type(s) is Symbol. + Assert(Type(s) === 'Symbol'); + // c. Return s. + return s; + } + // 3. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Symbol', value); +} + +// #sec-symbol.prototype.description +function SymbolProto_descriptionGetter(argList, { thisValue }) { + // 1. Let s be the this value. + const s = thisValue; + // 2. Let sym be ? thisSymbolValue(s). + const sym = Q(thisSymbolValue(s)); + // 3. Return sym.[[Description]]. + return sym.Description; +} + +// #sec-symbol.prototype.tostring +function SymbolProto_toString(argList, { thisValue }) { + // 1. Let sym be ? thisSymbolValue(this value). + const sym = Q(thisSymbolValue(thisValue)); + // 2. Return SymbolDescriptiveString(sym). + return SymbolDescriptiveString(sym); +} + +// #sec-symbol.prototype.valueof +function SymbolProto_valueOf(argList, { thisValue }) { + // 1. Return ? thisSymbolValue(this value). + return Q(thisSymbolValue(thisValue)); +} + +// #sec-symbol.prototype-@@toprimitive +function SymbolProto_toPrimitive(argList, { thisValue }) { + // 1. Return ? thisSymbolValue(this value). + return Q(thisSymbolValue(thisValue)); +} + +export function BootstrapSymbolPrototype(realmRec) { + const override = { + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }; + const proto = BootstrapPrototype(realmRec, [ + ['toString', SymbolProto_toString, 0], + ['description', [SymbolProto_descriptionGetter]], + ['valueOf', SymbolProto_valueOf, 0], + [wellKnownSymbols.toPrimitive, SymbolProto_toPrimitive, 1, override], + ], realmRec.Intrinsics['%Object.prototype%'], 'Symbol'); + + realmRec.Intrinsics['%Symbol.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/ThrowTypeError.mjs b/engine262/src/intrinsics/ThrowTypeError.mjs new file mode 100644 index 0000000..1f6c405 --- /dev/null +++ b/engine262/src/intrinsics/ThrowTypeError.mjs @@ -0,0 +1,36 @@ +import { surroundingAgent } from '../engine.mjs'; +import { CreateBuiltinFunction } from '../abstract-ops/all.mjs'; +import { Value, Descriptor } from '../value.mjs'; +import { X } from '../completion.mjs'; + +// #sec-%throwtypeerror% +function ThrowTypeError() { + // 1. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'StrictPoisonPill'); +} + +export function BootstrapThrowTypeError(realmRec) { + const f = X(CreateBuiltinFunction( + ThrowTypeError, [], realmRec, Value.null, + )); + + f.Extensible = Value.false; + + f.properties.set(new Value('length'), Descriptor({ + Value: new Value(0), + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + })); + + f.properties.set(new Value('name'), Descriptor({ + Value: new Value(''), + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + })); + + f.Prototype = realmRec.Intrinsics['%Function.prototype%']; + + realmRec.Intrinsics['%ThrowTypeError%'] = f; +} diff --git a/engine262/src/intrinsics/TypedArray.mjs b/engine262/src/intrinsics/TypedArray.mjs new file mode 100644 index 0000000..2da44a5 --- /dev/null +++ b/engine262/src/intrinsics/TypedArray.mjs @@ -0,0 +1,145 @@ +import { Q, X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { Value, wellKnownSymbols } from '../value.mjs'; +import { + Assert, + Call, + Get, + GetMethod, + IsCallable, + IsConstructor, + IterableToList, + Set, + LengthOfArrayLike, + ToObject, + ToString, + TypedArrayCreate, +} from '../abstract-ops/all.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-%typedarray%-intrinsic-object +function TypedArrayConstructor() { + // 1. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAConstructor', this); +} + +// #sec-%typedarray%.from +function TypedArray_from([source = Value.undefined, mapfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + // 1. Let C be the this value. + const C = thisValue; + // 2. If IsConstructor(C) is false, throw a TypeError exception. + if (IsConstructor(C) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + // 3. If mapfn is undefined, let mapping be false. + let mapping; + if (mapfn === Value.undefined) { + mapping = false; + } else { + // a. If IsCallable(mapfn) is false, throw a TypeError exception. + if (IsCallable(mapfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', mapfn); + } + // b. Let mapping be true. + mapping = true; + } + // 5. Let usingIterator be ? GetMethod(source, @@iterator). + const usingIterator = Q(GetMethod(source, wellKnownSymbols.iterator)); + // 6. If usingIterator is not undefined, then + if (usingIterator !== Value.undefined) { + const values = Q(IterableToList(source, usingIterator)); + const len = values.length; + const targetObj = Q(TypedArrayCreate(C, [new Value(len)])); + let k = 0; + while (k < len) { + const Pk = X(ToString(new Value(k))); + const kValue = values.shift(); + let mappedValue; + if (mapping) { + mappedValue = Q(Call(mapfn, thisArg, [kValue, new Value(k)])); + } else { + mappedValue = kValue; + } + Q(Set(targetObj, Pk, mappedValue, Value.true)); + k += 1; + } + Assert(values.length === 0); + return targetObj; + } + // 7. NOTE: source is not an Iterable so assume it is already an array-like object. + // 8. Let arrayLike be ! ToObject(source). + const arrayLike = X(ToObject(source)); + // 9. Let len be ? LengthOfArrayLike(arrayLike). + const len = Q(LengthOfArrayLike(arrayLike)).numberValue(); + // 10. Let targetObj be ? TypedArrayCreate(C, « len »). + const targetObj = Q(TypedArrayCreate(C, [new Value(len)])); + // 11. Let k be 0. + let k = 0; + // 12. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // b. Let kValue be ? Get(arrayLike, Pk). + const kValue = Q(Get(arrayLike, Pk)); + let mappedValue; + // c. If mapping is true, then + if (mapping) { + // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, k »). + mappedValue = Q(Call(mapfn, thisArg, [kValue, new Value(k)])); + } else { + // d. Else, let mappedValue be kValue. + mappedValue = kValue; + } + // e. Perform ? Set(targetObj, Pk, mappedValue, true). + Q(Set(targetObj, Pk, mappedValue, Value.true)); + // f. Set k to k + 1. + k += 1; + } + // 13. Return targetObj. + return targetObj; +} + +// #sec-%typedarray%.of +function TypedArray_of(items, { thisValue }) { + // 1. Let len be the actual number of arguments passed to this function. + // 2. Let items be the List of arguments passed to this function. + const len = items.length; + // 3. Let C be the this value. + const C = thisValue; + // 4. If IsConstructor(C) is false, throw a TypeError exception. + if (IsConstructor(C) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + // 5. Let newObj be ? TypedArrayCreate(C, « len »). + const newObj = Q(TypedArrayCreate(C, [new Value(len)])); + // 6. Let k be 0. + let k = 0; + // 7. Repeat, while k < len + while (k < len) { + // a. Let kValue be items[k]. + const kValue = items[k]; + // b. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // c. Perform ? Set(newObj, Pk, kValue, true). + Q(Set(newObj, Pk, kValue, Value.true)); + // d. Set k to k + 1. + k += 1; + } + // 8. Return newObj. + return newObj; +} + +// #sec-get-%typedarray%-@@species +function TypedArray_speciesGetter(args, { thisValue }) { + return thisValue; +} + +export function BootstrapTypedArray(realmRec) { + const typedArrayConstructor = BootstrapConstructor(realmRec, TypedArrayConstructor, 'TypedArray', 0, realmRec.Intrinsics['%TypedArray.prototype%'], [ + ['from', TypedArray_from, 1], + ['of', TypedArray_of, 0], + [wellKnownSymbols.species, [TypedArray_speciesGetter]], + ]); + + realmRec.Intrinsics['%TypedArray%'] = typedArrayConstructor; +} diff --git a/engine262/src/intrinsics/TypedArrayConstructors.mjs b/engine262/src/intrinsics/TypedArrayConstructors.mjs new file mode 100644 index 0000000..65ee261 --- /dev/null +++ b/engine262/src/intrinsics/TypedArrayConstructors.mjs @@ -0,0 +1,286 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value, wellKnownSymbols } from '../value.mjs'; +import { + AllocateArrayBuffer, + AllocateTypedArray, + AllocateTypedArrayBuffer, + Assert, + CloneArrayBuffer, + Get, + GetMethod, + GetValueFromBuffer, + IsDetachedBuffer, + IsSharedArrayBuffer, + IterableToList, + SameValue, + Set, + SetValueInBuffer, + SpeciesConstructor, + LengthOfArrayLike, + ToIndex, + ToString, + typedArrayInfoByName, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +export function BootstrapTypedArrayConstructors(realmRec) { + Object.entries(typedArrayInfoByName).forEach(([TypedArray, info]) => { + // #sec-typedarray-constructors + function TypedArrayConstructor(args, { NewTarget }) { + if (args.length === 0) { + // #sec-typedarray + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let constructorName be the String value of the Constructor Name value specified in Table 61 for this TypedArray constructor. + const constructorName = new Value(TypedArray); + // 3. Return ? AllocateTypedArray(constructorName, NewTarget, "%TypedArray.prototype%", 0). + return Q(AllocateTypedArray(constructorName, NewTarget, `%${TypedArray}.prototype%`, new Value(0))); + } else if (Type(args[0]) !== 'Object') { + // #sec-typedarray-length + const [length] = args; + // 1. Assert: Type(length) is not Object. + Assert(Type(length) !== 'Object'); + // 2. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 3. Let elementLength be ? ToIndex(length). + const elementLength = Q(ToIndex(length)); + // 4. Let constructorName be the String value of the Constructor Name value specified in Table 61 for this TypedArray constructor. + const constructorName = new Value(TypedArray); + // 5. Return ? AllocateTypedArray(constructorName, NewTarget, "%TypedArray.prototype%", elementLength). + return Q(AllocateTypedArray(constructorName, NewTarget, `%${TypedArray}.prototype%`, elementLength)); + } else if ('TypedArrayName' in args[0]) { + // #sec-typedarray-typedarray + const [typedArray] = args; + // 1. Assert: Type(typedArray) is Object and typedArray has a [[TypedArrayName]] internal slot. + Assert(Type(typedArray) === 'Object' && 'TypedArrayName' in typedArray); + // 2. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 3. Let constructorName be the String value of the Constructor Name value specified in Table 61 for this TypedArray constructor. + const constructorName = new Value(TypedArray); + // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget, "%TypedArray.prototype%"). + const O = Q(AllocateTypedArray(constructorName, NewTarget, `%${TypedArray}.prototype%`)); + // 5. Let srcArray be typedArray. + const srcArray = typedArray; + // 6. Let srcData be srcArray.[[ViewedArrayBuffer]]. + const srcData = srcArray.ViewedArrayBuffer; + // 7. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. + if (IsDetachedBuffer(srcData) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 8. Let elementType be the Element Type value in Table 61 for constructorName. + const elementType = new Value(info.ElementType); + // 9. Let elementLength be srcArray.[[ArrayLength]]. + const elementLength = srcArray.ArrayLength; + // 10. Let srcName be the String value of srcArray.[[TypedArrayName]]. + const srcName = srcArray.TypedArrayName.stringValue(); + // 11. Let srcType be the Element Type value in Table 61 for srcName. + const srcType = new Value(typedArrayInfoByName[srcName].ElementType); + // 12. Let srcElementSize be the Element Size value specified in Table 61 for srcName. + const srcElementSize = typedArrayInfoByName[srcName].ElementSize; + // 13. Let srcByteOffset be srcArray.[[ByteOffset]]. + const srcByteOffset = srcArray.ByteOffset; + // 14. Let elementSize be the Element Size value specified in Table 61 for constructorName. + const elementSize = info.ElementSize; + // 15. Let byteLength be elementSize × elementLength. + const byteLength = new Value(elementSize * elementLength.numberValue()); + // 16. If IsSharedArrayBuffer(srcData) is false, then + let bufferConstructor; + if (IsSharedArrayBuffer(srcData) === Value.false) { + bufferConstructor = Q(SpeciesConstructor(srcData, surroundingAgent.intrinsic('%ArrayBuffer%'))); + } else { + // 17. Else, Let bufferConstructor be %ArrayBuffer%. + bufferConstructor = surroundingAgent.intrinsic('%ArrayBuffer%'); + } + // 18. If elementType is the same as srcType, then + let data; + if (SameValue(elementType, srcType) === Value.true) { + // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength, bufferConstructor). + data = Q(CloneArrayBuffer(srcData, srcByteOffset, byteLength, bufferConstructor)); + } else { + // a. Let data be ? AllocateArrayBuffer(bufferConstructor, byteLength). + data = Q(AllocateArrayBuffer(bufferConstructor, byteLength)); + // b. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. + if (IsDetachedBuffer(srcData) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // c. If srcArray.[[ContentType]] is not equal to O.[[ContentType]], throw a TypeError exception. + if (srcArray.ContentType !== O.ContentType) { + return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch'); + } + // d. Let srcByteIndex be srcByteOffset. + let srcByteIndex = srcByteOffset.numberValue(); + // e. Let targetByteIndex be 0. + let targetByteIndex = 0; + // f. Let count be elementLength. + let count = elementLength.numberValue(); + // g. Repeat, while count > 0 + while (count > 0) { + // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). + const value = GetValueFromBuffer(srcData, new Value(srcByteIndex), srcType.stringValue(), true, 'Unordered'); + // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). + SetValueInBuffer(data, new Value(targetByteIndex), elementType.stringValue(), value, true, 'Unordered'); + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + srcByteIndex += srcElementSize; + // iv. Set targetByteIndex to targetByteIndex + elementSize. + targetByteIndex += elementSize; + // v. Set count to count - 1. + count -= 1; + } + } + // 20. Set O.[[ViewedArrayBuffer]] to data. + O.ViewedArrayBuffer = data; + // 21. Set O.[[ByteLength]] to byteLength. + O.ByteLength = byteLength; + // 22. Set O.[[ByteOffset]] to 0. + O.ByteOffset = new Value(0); + // 23. Set O.[[ArrayLength]] to elementLength. + O.ArrayLength = elementLength; + // 24. Return O. + return O; + } else if (!('TypedArrayName' in args[0]) && !('ArrayBufferData' in args[0])) { + // 22.2.4.4 #sec-typedarray-object + const [object] = args; + // 1. Assert: Type(object) is Object and object does not have either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. + Assert(Type(object) === 'Object' && !('TypedArrayName' in object) && !('ArrayBufferData' in object)); + // 2. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 3. Let constructorName be the String value of the Constructor Name value specified in Table 61 for this TypedArray constructor. + const constructorName = new Value(TypedArray); + // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget, "%TypedArray.prototype%"). + const O = Q(AllocateTypedArray(constructorName, NewTarget, `%${TypedArray}.prototype%`)); + // 5. Let usingIterator be ? GetMethod(object, @@iterator). + const usingIterator = Q(GetMethod(object, wellKnownSymbols.iterator)); + // 6. If usingIterator is not undefined, then + if (usingIterator !== Value.undefined) { + // a. Let values be ? IterableToList(object, usingIterator). + const values = Q(IterableToList(object, usingIterator)); + // b. Let len be the number of elements in values. + const len = values.length; + // c. Perform ? AllocateTypedArrayBuffer(O, len). + Q(AllocateTypedArrayBuffer(O, new Value(len))); + // d. Let k be 0. + let k = 0; + // e. Repeat, while k < len + while (k < len) { + // i. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // ii. Let kValue be the first element of values and remove that element from values. + const kValue = values.shift(); + // iii. Perform ? Set(O, Pk, kValue, true). + Q(Set(O, Pk, kValue, Value.true)); + // iv. Set k to k + 1. + k += 1; + } + // f. Assert: values is now an empty List. + Assert(values.length === 0); + // g. Return O. + return O; + } + // 7. NOTE: object is not an Iterable so assume it is already an array-like object. + // 8. Let arrayLike be object. + const arrayLike = object; + // 9. Let len be ? LengthOfArrayLike(arrayLike). + const len = Q(LengthOfArrayLike(arrayLike)).numberValue(); + // 10. Perform ? AllocateTypedArrayBuffer(O, len). + Q(AllocateTypedArrayBuffer(O, new Value(len))); + // 11. Let k be 0. + let k = 0; + // 12. Repeat, while k < len. + while (k < len) { + // a. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // b. Let kValue be ? Get(arrayLike, Pk). + const kValue = Q(Get(arrayLike, Pk)); + // c. Perform ? Set(O, Pk, kValue, true). + Q(Set(O, Pk, kValue, Value.true)); + // d. Set k to k + 1. + k += 1; + } + // 13. Return O. + return O; + } else { + // #sec-typedarray-buffer-byteoffset-length + const [buffer = Value.undefined, byteOffset = Value.undefined, length = Value.undefined] = args; + // 1. Assert: Type(buffer) is Object and buffer has an [[ArrayBufferData]] internal slot. + Assert(Type(buffer) === 'Object' && 'ArrayBufferData' in buffer); + // 2. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 3. Let constructorName be the String value of the Constructor Name value specified in Table 61 for this TypedArray constructor. + const constructorName = new Value(TypedArray); + // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget, "%TypedArray.prototype%"). + const O = Q(AllocateTypedArray(constructorName, NewTarget, `%${TypedArray}.prototype%`)); + // 5. Let elementSize be the Element Size value specified in Table 61 for constructorName. + const elementSize = info.ElementSize; + // 6. Let offset be ? ToIndex(byteOffset). + const offset = Q(ToIndex(byteOffset)); + // 7. If offset modulo elementSize ≠ 0, throw a RangeError exception. + if (offset.numberValue() % elementSize !== 0) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOffsetAlignment', TypedArray, elementSize); + } + // 8. If length is not undefined, then + let newLength; + if (length !== Value.undefined) { + // Let newLength be ? ToIndex(length). + newLength = Q(ToIndex(length)).numberValue(); + } + // 9. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 10. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. + const bufferByteLength = buffer.ArrayBufferByteLength.numberValue(); + // 11. If length is undefined, then + let newByteLength; + if (length === Value.undefined) { + // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. + if (bufferByteLength % elementSize !== 0) { + return surroundingAgent.Throw('RangeError', 'TypedArrayLengthAlignment', TypedArray, elementSize); + } + // b. Let newByteLength be bufferByteLength - offset. + newByteLength = bufferByteLength - offset.numberValue(); + // c. If newByteLength < 0, throw a RangeError exception. + if (newByteLength < 0) { + return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB'); + } + } else { + // a. Let newByteLength be newLength × elementSize. + newByteLength = newLength * elementSize; + // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. + if (offset.numberValue() + newByteLength > bufferByteLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB'); + } + } + // 13. Set O.[[ViewedArrayBuffer]] to buffer. + O.ViewedArrayBuffer = buffer; + // 14. Set O.[[ByteLength]] to newByteLength. + O.ByteLength = new Value(newByteLength); + // 15. Set O.[[ByteOffset]] to offset. + O.ByteOffset = offset; + // 16. Set O.[[ArrayLength]] to newByteLength / elementSize. + O.ArrayLength = new Value(newByteLength / elementSize); + // 17. Return O. + return O; + } + } + + const taConstructor = BootstrapConstructor(realmRec, TypedArrayConstructor, TypedArray, 3, realmRec.Intrinsics[`%${TypedArray}.prototype%`], [ + ['BYTES_PER_ELEMENT', new Value(info.ElementSize), undefined, { + Writable: Value.false, + Configurable: Value.false, + }], + ]); + X(taConstructor.SetPrototypeOf(realmRec.Intrinsics['%TypedArray%'])); + realmRec.Intrinsics[`%${TypedArray}%`] = taConstructor; + }); +} diff --git a/engine262/src/intrinsics/TypedArrayPrototype.mjs b/engine262/src/intrinsics/TypedArrayPrototype.mjs new file mode 100644 index 0000000..78e1f1c --- /dev/null +++ b/engine262/src/intrinsics/TypedArrayPrototype.mjs @@ -0,0 +1,841 @@ +import { + Assert, + Call, + CloneArrayBuffer, + CreateArrayIterator, + Get, + GetValueFromBuffer, + IsCallable, + IsDetachedBuffer, + IsSharedArrayBuffer, + SameValue, + Set, + SetValueInBuffer, + LengthOfArrayLike, + ToBoolean, + ToBigInt, + ToInteger, + ToNumber, + ToObject, + ToString, + TypedArraySpeciesCreate, + ValidateTypedArray, + RequireInternalSlot, + typedArrayInfoByName, + typedArrayInfoByType, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { + Descriptor, Type, Value, wellKnownSymbols, +} from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; +import { ArrayProto_sortBody, BootstrapArrayPrototypeShared } from './ArrayPrototypeShared.mjs'; + +// #sec-get-%typedarray%.prototype.buffer +function TypedArrayProto_buffer(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. Return buffer. + return buffer; +} + +// #sec-get-%typedarray%.prototype.bytelength +function TypedArrayProto_byteLength(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. If IsDetachedBuffer(buffer) is true, return 0. + if (IsDetachedBuffer(buffer) === Value.true) { + return new Value(0); + } + // 6. Let size be O.[[ByteLength]]. + const size = O.ByteLength; + // 7. Return size. + return size; +} + +// #sec-get-%typedarray%.prototype.byteoffset +function TypedArrayProto_byteOffset(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. If IsDetachedBuffer(buffer) is true, return 0. + if (IsDetachedBuffer(buffer) === Value.true) { + return new Value(0); + } + // 6. Let offset be O.[[ByteOffset]]. + const offset = O.ByteOffset; + // 7. Return offset. + return offset; +} + +// #sec-%typedarray%.prototype.copywithin +function TypedArrayProto_copyWithin([target = Value.undefined, start = Value.undefined, end = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Let len be O.[[ArrayLength]]. + const len = O.ArrayLength.numberValue(); + // 4. Let relativeTarget be ? ToInteger(target). + const relativeTarget = Q(ToInteger(target)).numberValue(); + // 5. If relativeTarget < 0, let to be max((len + relativeTarget), 0); else let to be min(relativeTarget, len). + let to; + if (relativeTarget < 0) { + to = Math.max(len + relativeTarget, 0); + } else { + to = Math.min(relativeTarget, len); + } + // 6. Let relativeStart be ? ToInteger(start). + const relativeStart = Q(ToInteger(start)).numberValue(); + // 7. If relativeStart < 0, let from be max((len + relativeStart), 0); else let from be min(relativeStart, len). + let from; + if (relativeStart < 0) { + from = Math.max(len + relativeStart, 0); + } else { + from = Math.min(relativeStart, len); + } + // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToInteger(end). + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + // 9. If relativeEnd < 0, let final be max((len + relativeEnd), 0); else let final be min(relativeEnd, len). + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + // 10. Let count be min(final - from, len - to). + const count = Math.min(final - from, len - to); + // 11. If count > 0, then + if (count > 0) { + // a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data. + // b. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // d. Let typedArrayName be the String value of O.[[TypedArrayName]]. + const typedArrayName = O.TypedArrayName.stringValue(); + // e. Let elementSize be the Element Size value specified in Table 61 for typedArrayName. + const elementSize = typedArrayInfoByName[typedArrayName].ElementSize; + // f. Let byteOffset be O.[[ByteOffset]. + const byteOffset = O.ByteOffset.numberValue(); + // g. Let toByteIndex be to × elementSize + byteOffset. + let toByteIndex = to * elementSize + byteOffset; + // h. Let fromByteIndex be from × elementSize + byteOffset. + let fromByteIndex = from * elementSize + byteOffset; + // i. Let countBytes be count × elementSize. + let countBytes = count * elementSize; + // j. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then + let direction; + if (fromByteIndex < toByteIndex && toByteIndex < fromByteIndex + countBytes) { + // i. Let direction be -1. + direction = -1; + // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. + fromByteIndex = fromByteIndex + countBytes - 1; + // iii. Set toByteIndex to toByteIndex + countBytes - 1. + toByteIndex = toByteIndex + countBytes - 1; + } else { + // i. Let direction be 1. + direction = 1; + } + // l. Repeat, while countBytes > 0 + while (countBytes > 0) { + // i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered). + const value = GetValueFromBuffer(buffer, new Value(fromByteIndex), 'Uint8', Value.true, 'Unordered'); + // ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered). + SetValueInBuffer(buffer, new Value(toByteIndex), 'Uint8', value, Value.true, 'Unordered'); + // iii. Set fromByteIndex to fromByteIndex + direction. + fromByteIndex += direction; + // iv. Set toByteIndex to toByteIndex + direction. + toByteIndex += direction; + // v. Set countBytes to countBytes - 1. + countBytes -= 1; + } + } + // 12. Return O. + return O; +} + +// #sec-%typedarray%.prototype.entries +function TypedArrayProto_entries(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Return CreateArrayIterator(O, key+value). + return CreateArrayIterator(O, 'key+value'); +} + +// #sec-%typedarray%.prototype.fill +function TypedArrayProto_fill([value = Value.undefined, start = Value.undefined, end = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Let len be O.[[ArrayLength]] + const len = O.ArrayLength.numberValue(); + // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). + // 5. Else, set value to ? ToNumber(value). + if (O.ContentType === 'BigInt') { + value = Q(ToBigInt(value)); + } else { + value = Q(ToNumber(value)); + } + // 6. Let relativeStart be ? ToInteger(start). + const relativeStart = Q(ToInteger(start)).numberValue(); + // 7. If relativeStart < 0, let k be max((len + relativeStart), 0); else let k be min(relativeStart, len). + let k; + if (relativeStart < 0) { + k = Math.max(len + relativeStart, 0); + } else { + k = Math.min(relativeStart, len); + } + // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToInteger(end). + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + // 9. If relativeEnd < 0, let final be max((len + relativeEnd), 0); else let final be min(relativeEnd, len). + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + // 10. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. + if (IsDetachedBuffer(O.ViewedArrayBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 11. Repeat, while k < final + while (k < final) { + // a. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // b. Perform ! Set(O, Pk, value, true). + X(Set(O, Pk, value, Value.true)); + // c. Set k to k + 1. + k += 1; + } + // 12. Return O. + return O; +} + +// #sec-%typedarray%.prototype.filter +function TypedArrayProto_filter([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Let len be O.[[ArrayLength]]. + const len = O.ArrayLength.numberValue(); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 5. Let kept be a new empty List. + const kept = []; + // 6. Let k be 0. + let k = 0; + // 7. Let captured be 0. + let captured = 0; + // 8. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // b. Let kValue be ? Get(O, Pk). + const kValue = Q(Get(O, Pk)); + // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, k, O »)). + const selected = ToBoolean(Q(Call(callbackfn, thisArg, [kValue, new Value(k), O]))); + // d. If selected is true, then + if (selected === Value.true) { + // i. Append kValue to the end of kept. + kept.push(kValue); + // ii. Setp captured to captured + 1. + captured += 1; + } + // e. Set k to k + 1. + k += 1; + } + // 9. Let A be ? TypedArraySpeciesCreate(O, « captured »). + const A = Q(TypedArraySpeciesCreate(O, [new Value(captured)])); + // 10. Let n be 0. + let n = 0; + // 11. For each element e of kept, do + for (const e of kept) { + // a. Perform ! Set(A, ! ToString(n), e, true). + X(Set(A, X(ToString(new Value(n))), e, Value.true)); + // b. Set n to n + 1. + n += 1; + } + // 12. Return A. + return A; +} + +// #sec-%typedarray%.prototype.keys +function TypedArrayProto_keys(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Return CreateArrayIterator(O, key). + return CreateArrayIterator(O, 'key'); +} + +// #sec-get-%typedarray%.prototype.length +function TypedArrayProto_length(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. + Assert('ViewedArrayBuffer' in O && 'ArrayLength' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. If IsDetachedBuffer(buffer) is true, return 0. + if (IsDetachedBuffer(buffer) === Value.true) { + return new Value(0); + } + // 6. Let length be O.[[ArrayLength]]. + const length = O.ArrayLength; + // 8. Return length. + return length; +} + +// #sec-%typedarray%.prototype.map +function TypedArrayProto_map([callbackfn = Value.undefined, thisArg = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Let len be O.[[ArrayLength]]. + const len = O.ArrayLength; + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (IsCallable(callbackfn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 5. Let A be ? TypedArraySpeciesCreate(O, « len »). + const A = Q(TypedArraySpeciesCreate(O, [len])); + // 6. Let k be 0. + let k = 0; + // 7. Repeat, while k < len + while (k < len.numberValue()) { + // a. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // b. Let kValue be ? Get(O, Pk). + const kValue = Q(Get(O, Pk)); + // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, k, O »). + const mappedValue = Q(Call(callbackfn, thisArg, [kValue, new Value(k), O])); + // d. Perform ? Set(A, Pk, mappedValue, true). + Q(Set(A, Pk, mappedValue, Value.true)); + // e. Set k to k + 1. + k += 1; + } + // 8. Return A. + return A; +} + +// #sec-%typedarray%.prototype.set-overloaded-offset +function TypedArrayProto_set([overloaded = Value.undefined, offset = Value.undefined], { thisValue }) { + if (Type(overloaded) !== 'Object' || !('TypedArrayName' in overloaded)) { + // #sec-%typedarray%.prototype.set-array-offset + const array = overloaded; + // 1. Assert: array is any ECMAScript language value other than an Object with a [[TypedArrayName]] internal slot. + // 2. Let target be the this value. + const target = thisValue; + // 3. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). + Q(RequireInternalSlot(target, 'TypedArrayName')); + // 4. Assert: target has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in target); + // 5. Let targetOffset be ? ToInteger(offset). + const targetOffset = Q(ToInteger(offset)).numberValue(); + // 6. If targetOffset < 0, throw a RangeError exception. + if (targetOffset < 0) { + return surroundingAgent.Throw('RangeError', 'NegativeIndex', 'Offset'); + } + // 7. Let targetBuffer be target.[[ViewedArrayBuffer]]. + const targetBuffer = target.ViewedArrayBuffer; + // 8. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(targetBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 9. Let targetLength be target.[[ArrayLength]]. + const targetLength = target.ArrayLength.numberValue(); + // 10. Let targetName be the String value of target.[[TypedArrayName]]. + const targetName = target.TypedArrayName.stringValue(); + // 11. Let targetElementSize be the Element Size value specified in Table 61 for targetName. + const targetElementSize = typedArrayInfoByName[targetName].ElementSize; + // 12. Let targetType be the Element Type value in Table 61 for targetName. + const targetType = typedArrayInfoByName[targetName].ElementType; + // 13. Let targetByteOffset be target.[[ByteOffset]]. + const targetByteOffset = target.ByteOffset.numberValue(); + // 14. Let src be ? ToObject(array). + const src = Q(ToObject(array)); + // 15. Let srcLength be ? LengthOfArrayLike(src). + const srcLength = Q(LengthOfArrayLike(src)).numberValue(); + // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. + if (srcLength + targetOffset > targetLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + // 17. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. + let targetByteIndex = targetOffset * targetElementSize + targetByteOffset; + // 18. Let k be 0. + let k = 0; + // 19. Let limit be targetByteIndex + targetElementSize × srcLength. + const limit = targetByteIndex + targetElementSize * srcLength; + // 20. Repeat, while targetByteIndex < limit + while (targetByteIndex < limit) { + // a. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // b. Let value be ? Get(src, Pk). + let value = Q(Get(src, Pk)); + // c. If target.[[ContentType]] is BigInt, set value to ? ToBigInt(value). + // d. Otherwise, set value to ? ToNumber(value). + if (target.ContentType === 'BigInt') { + value = Q(ToBigInt(value)); + } else { + value = Q(ToNumber(value)); + } + // e. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(targetBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // f. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). + SetValueInBuffer(targetBuffer, new Value(targetByteIndex), targetType, value, Value.true, 'Unordered'); + // g. Set k to k + 1. + k += 1; + // h. Set targetByteIndex to targetByteIndex + targetElementSize. + targetByteIndex += targetElementSize; + } + // 21. Return undefined. + return Value.undefined; + } else { + // #sec-%typedarray%.prototype.set-typedarray-offset + const typedArray = overloaded; + // 1. Assert: typedArray has a [[TypedArrayName]] internal slot. + Assert('TypedArrayName' in typedArray); + // 2. Let target be the this value. + const target = thisValue; + // 3. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). + Q(RequireInternalSlot(target, 'TypedArrayName')); + // 4. Assert: target has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in target); + // 5. Let targetOffset be ? ToInteger(offset). + const targetOffset = Q(ToInteger(offset)).numberValue(); + // 6. If targetOffset < 0, throw a RangeError exception. + if (targetOffset < 0) { + return surroundingAgent.Throw('RangeError', 'NegativeIndex', 'Offset'); + } + // 7. Let targetBuffer be target.[[ViewedArrayBuffer]]. + const targetBuffer = target.ViewedArrayBuffer; + // 8. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(targetBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 9. Let targetLength be target.[[ArrayLength]]. + const targetLength = target.ArrayLength.numberValue(); + // 10. Let srcBuffer be typedArray.[[ViewedArrayBuffer]]. + let srcBuffer = typedArray.ViewedArrayBuffer; + // 11. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(srcBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 12. Let targetName be the String value of target.[[TypedArrayName]]. + const targetName = target.TypedArrayName.stringValue(); + // 13. Let targetType be the Element Type value in Table 61 for targetName. + const targetType = typedArrayInfoByName[targetName].ElementType; + // 14. Let targetElementSize be the Element Size value specified in Table 61 for targetName. + const targetElementSize = typedArrayInfoByName[targetName].ElementSize; + // 15. Let targetByteOffset be target.[[ByteOffset]]. + const targetByteOffset = target.ByteOffset.numberValue(); + // 16. Let srcName be the String value of typedArray.[[TypedArrayName]]. + const srcName = typedArray.TypedArrayName.stringValue(); + // 17. Let srcType be the Element Type value in Table 61 for srcName. + const srcType = typedArrayInfoByName[srcName].ElementType; + // 18. Let srcElementSize be the Element Size value specified in Table 61 for srcName. + const srcElementSize = typedArrayInfoByName[srcName].ElementSize; + // 19. Let srcLength be typedArray.[[ArrayLength]]. + const srcLength = typedArray.ArrayLength.numberValue(); + // 20. Let srcByteOffset be typedArray.[[ByteOffset]]. + const srcByteOffset = typedArray.ByteOffset.numberValue(); + // 21. If srcLength + targetOffset > targetLength, throw a RangeError exception. + if (srcLength + targetOffset > targetLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + // 22. If target.[[ContentType]] is not equal to typedArray.[[ContentType]], throw a TypeError exception. + if (target.ContentType !== typedArray.ContentType) { + return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch'); + } + // 23. If both IsSharedArrayBuffer(srcBuffer) and IsSharedArrayBuffer(targetBuffer) are true, then + let same; + if (IsSharedArrayBuffer(srcBuffer) === Value.true && IsSharedArrayBuffer(targetBuffer) === Value.true) { + Assert(false); + } else { + same = SameValue(srcBuffer, targetBuffer); + } + // 25. If same is true, then + let srcByteIndex; + if (same === Value.true) { + // a. Let srcByteLength be typedArray.[[ByteLength]]. + const srcByteLength = typedArray.ByteLength; + // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%). + srcBuffer = Q(CloneArrayBuffer(srcBuffer, new Value(srcByteOffset), srcByteLength, surroundingAgent.intrinsic('%ArrayBuffer%'))); + // c. NOTE: %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. + // d. Let srcByteIndex be 0. + srcByteIndex = 0; + } else { + // 26. Else, let srcByteIndex be srcByteOffset. + srcByteIndex = srcByteOffset; + } + // 27. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. + let targetByteIndex = targetOffset * targetElementSize + targetByteOffset; + // 28. Let limit be targetByteIndex + targetElementSize × srcLength. + const limit = targetByteIndex + targetElementSize * srcLength; + // 29. If srcType is the same as targetType, then + if (srcType === targetType) { + // a. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // b. Repeat, while targetByteIndex < limit + while (targetByteIndex < limit) { + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + const value = GetValueFromBuffer(srcBuffer, new Value(srcByteIndex), 'Uint8', Value.true, 'Unordered'); + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + SetValueInBuffer(targetBuffer, new Value(targetByteIndex), 'Uint8', value, Value.true, 'Unordered'); + // iii. Set srcByteIndex to srcByteIndex + 1. + srcByteIndex += 1; + // iv. Set targetByteIndex to targetByteIndex + 1. + targetByteIndex += 1; + } + } else { + // a. Repeat, while targetByteIndex < limit + while (targetByteIndex < limit) { + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, Unordered). + const value = GetValueFromBuffer(srcBuffer, new Value(srcByteIndex), srcType, Value.true, 'Unordered'); + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). + SetValueInBuffer(targetBuffer, new Value(targetByteIndex), targetType, value, Value.true, 'Unordered'); + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + srcByteIndex += srcElementSize; + // iv. Set targetByteIndex to targetByteIndex + targetElementSize. + targetByteIndex += targetElementSize; + } + } + // 31. Return undefined. + return Value.undefined; + } +} + +// #sec-%typedarray%.prototype.slice +function TypedArrayProto_slice([start = Value.undefined, end = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // 3. Let len be O.[[ArrayLength]]. + const len = O.ArrayLength.numberValue(); + // 4. Let relativeStart be ? ToInteger(start). + const relativeStart = Q(ToInteger(start)).numberValue(); + // 5. If relativeStart < 0, let k be max((len + relativeStart), 0); else let k be min(relativeStart, len). + let k; + if (relativeStart < 0) { + k = Math.max(len + relativeStart, 0); + } else { + k = Math.min(relativeStart, len); + } + // 6. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToInteger(end). + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + // 7. If relativeEnd < 0, let final be max((len + relativeEnd), 0); else let final be min(relativeEnd, len). + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + // 8. Let count be max(final - k, 0). + const count = Math.max(final - k, 0); + // 9. Let A be ? TypedArraySpeciesCreate(O, « count »). + const A = Q(TypedArraySpeciesCreate(O, [new Value(count)])); + // 10. Let srcName be the String value of O.[[TypedArrayName]]. + const srcName = O.TypedArrayName.stringValue(); + // 11. Let srcType be the Element Type value in Table 61 for srcName. + const srcType = typedArrayInfoByName[srcName].ElementType; + // 12. Let targetName be the String value of A.[[TypedArrayName]]. + const targetName = A.TypedArrayName.stringValue(); + // 13. Let targetType be the Element Type value in Table 61 for targetName. + const targetType = typedArrayInfoByName[targetName].ElementType; + // 14. If srcType is different from targetType, then + if (srcType !== targetType) { + // a. Let n be 0. + let n = 0; + // b. Repeat, while k < final + while (k < final) { + // i. Let Pk be ! ToString(k). + const Pk = X(ToString(new Value(k))); + // ii. Let kValue be ? Get(O, Pk). + const kValue = Q(Get(O, Pk)); + // iii. Perform ! Set(A, ! ToString(n), kValue, true). + X(Set(A, X(ToString(new Value(n))), kValue, Value.true)); + // iv. Set k to k + 1. + k += 1; + // v. Set n to n + 1. + n += 1; + } + } else if (count > 0) { + // a. Let srcBuffer be O.[[ViewedArrayBuffer]]. + const srcBuffer = O.ViewedArrayBuffer; + // b. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(srcBuffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // c. Let targetBuffer be A.[[ViewedArrayBuffer]]. + const targetBuffer = A.ViewedArrayBuffer; + // d. Let elementSize be the Element Size value specified in Table 61 for Element Type srcType. + const elementSize = typedArrayInfoByType[srcType].ElementSize; + // e. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // f. Let srcByteOffet be O.[[ByteOffset]]. + const srcByteOffset = O.ByteOffset.numberValue(); + // g. Let targetByteIndex be A.[[ByteOffset]]. + let targetByteIndex = A.ByteOffset.numberValue(); + // h. Let srcByteIndex be (k × elementSize) + srcByteOffet. + let srcByteIndex = (k * elementSize) + srcByteOffset; + // i. Let limit be targetByteIndex + count × elementSize. + const limit = targetByteIndex + count * elementSize; + // j. Repeat, while targetByteIndex < limit + while (targetByteIndex < limit) { + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + const value = GetValueFromBuffer(srcBuffer, new Value(srcByteIndex), 'Uint8', Value.true, 'Unordered'); + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + SetValueInBuffer(targetBuffer, new Value(targetByteIndex), 'Uint8', value, Value.true, 'Unordered'); + // iii. Set srcByteIndex to srcByteIndex + 1. + srcByteIndex += 1; + // iv. Set targetByteIndex to targetByteIndex + 1. + targetByteIndex += 1; + } + } + // 16. Return A. + return A; +} + +// 22.2.3.26 #sec-%typedarray%.prototype.sort +function TypedArrayProto_sort([comparefn = Value.undefined], { thisValue }) { + // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. + if (comparefn !== Value.undefined && IsCallable(comparefn) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', comparefn); + } + // 2. Let obj be the this value. + const obj = Q(ToObject(thisValue)); + // 3. Let buffer be ? ValidateTypedArray(obj). + const buffer = Q(ValidateTypedArray(obj)); + // 4. Let len be obj.[[ArrayLength]]. + const len = obj.ArrayLength; + + return ArrayProto_sortBody(obj, len, (x, y) => TypedArraySortCompare(x, y, comparefn, buffer), true); +} + +function TypedArraySortCompare(x, y, comparefn, buffer) { + // 1. Assert: Both Type(x) and Type(y) are Number or both are BigInt. + Assert((Type(x) === 'Number' && Type(y) === 'Number') + || (Type(x) === 'BigInt' && Type(y) === 'BigInt')); + // 2. If comparefn is not undefined, then + if (comparefn !== Value.undefined) { + // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). + const v = Q(ToNumber(Q(Call(comparefn, Value.undefined, [x, y])))); + // b. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer) === Value.true) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // c. If v is NaN, return +0. + if (v.isNaN()) { + return new Value(+0); + } + // d. Return v. + return v; + } + // 3. If x and y are both NaN, return +0. + if (x.isNaN() && y.isNaN()) { + return new Value(+0); + } + // 4. If x is NaN, return 1. + if (x.isNaN()) { + return new Value(1); + } + // 5. If y is NaN, return -1. + if (y.isNaN()) { + return new Value(-1); + } + x = x.numberValue ? x.numberValue() : x.bigintValue(); + y = y.numberValue ? y.numberValue() : y.bigintValue(); + // 6. If x < y, return -1. + if (x < y) { + return new Value(-1); + } + // 7. If x > y, return 1. + if (x > y) { + return new Value(1); + } + // 8. If x is -0 and y is +0, return -1. + if (Object.is(x, -0) && Object.is(y, +0)) { + return new Value(-1); + } + // 9. If x is +0 and y is -0, return 1. + if (Object.is(x, +0) && Object.is(y, -0)) { + return new Value(1); + } + // 10. Return +0. + return new Value(+0); +} + +// #sec-%typedarray%.prototype.subarray +function TypedArrayProto_subarray([begin = Value.undefined, end = Value.undefined], { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + const buffer = O.ViewedArrayBuffer; + // 5. Let srcLength be O.[[ArrayLength]]. + const srcLength = O.ArrayLength.numberValue(); + // 6. Let relativeBegin be ? ToInteger(begin). + const relativeBegin = Q(ToInteger(begin)).numberValue(); + // 7. If relativeBegin < 0, let beginIndex be max((srcLength + relativeBegin), 0); else let beginIndex be min(relativeBegin, srcLength). + let beginIndex; + if (relativeBegin < 0) { + beginIndex = Math.max(srcLength + relativeBegin, 0); + } else { + beginIndex = Math.min(relativeBegin, srcLength); + } + // 8. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToInteger(end). + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = srcLength; + } else { + relativeEnd = Q(ToInteger(end)).numberValue(); + } + // 9. If relativeEnd < 0, let endIndex be max((srcLength + relativeEnd), 0); else let endIndex be min(relativeEnd, srcLength). + let endIndex; + if (relativeEnd < 0) { + endIndex = Math.max(srcLength + relativeEnd, 0); + } else { + endIndex = Math.min(relativeEnd, srcLength); + } + // 10. Let newLength be max(endIndex - beginIndex, 0). + const newLength = Math.max(endIndex - beginIndex, 0); + // 11. Let constructorName be the String value of O.[[TypedArrayName]]. + const constructorName = O.TypedArrayName.stringValue(); + // 12. Let elementSize be the Element Size value specified in Table 61 for constructorName. + const elementSize = typedArrayInfoByName[constructorName].ElementSize; + // 13. Let srcByteOffset be O.[[ByteOffset]]. + const srcByteOffset = O.ByteOffset.numberValue(); + // 14. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. + const beginByteOffset = srcByteOffset + beginIndex * elementSize; + // 15. Let argumentsList be « buffer, beginByteOffset, newLength ». + const argumentsList = [buffer, new Value(beginByteOffset), new Value(newLength)]; + // 16. Return ? TypedArraySpeciesCreate(O, argumentsList). + return Q(TypedArraySpeciesCreate(O, argumentsList)); +} + +// #sec-%typedarray%.prototype.values +function TypedArrayProto_values(args, { thisValue }) { + // 1. Let o be the this value. + const O = thisValue; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O)); + // Return CreateArrayIterator(O, value). + return CreateArrayIterator(O, 'value'); +} + +// #sec-get-%typedarray%.prototype-@@tostringtag +function TypedArrayProto_toStringTag(args, { thisValue }) { + // 1. Let O be the this value. + const O = thisValue; + // 2. If Type(O) is not Object, return undefined. + if (Type(O) !== 'Object') { + return Value.undefined; + } + // 3. If O does not have a [[TypedArrayName]] internal slot, return undefined. + if (!('TypedArrayName' in O)) { + return Value.undefined; + } + // 4. Let name be O.[[TypedArrayName]]. + const name = O.TypedArrayName; + // 5. Assert: Type(name) is String. + Assert(Type(name) === 'String'); + // 6. Return name. + return name; +} + +export function BootstrapTypedArrayPrototype(realmRec) { + const ArrayProto_toString = X(Get(realmRec.Intrinsics['%Array.prototype%'], new Value('toString'))); + Assert(Type(ArrayProto_toString) === 'Object'); + + const proto = BootstrapPrototype(realmRec, [ + ['buffer', [TypedArrayProto_buffer]], + ['byteLength', [TypedArrayProto_byteLength]], + ['byteOffset', [TypedArrayProto_byteOffset]], + ['copyWithin', TypedArrayProto_copyWithin, 2], + ['entries', TypedArrayProto_entries, 0], + ['fill', TypedArrayProto_fill, 1], + ['filter', TypedArrayProto_filter, 1], + ['keys', TypedArrayProto_keys, 0], + ['length', [TypedArrayProto_length]], + ['map', TypedArrayProto_map, 1], + ['set', TypedArrayProto_set, 1], + ['slice', TypedArrayProto_slice, 2], + ['sort', TypedArrayProto_sort, 1], + ['subarray', TypedArrayProto_subarray, 2], + ['values', TypedArrayProto_values, 0], + ['toString', ArrayProto_toString], + [wellKnownSymbols.toStringTag, [TypedArrayProto_toStringTag]], + ], realmRec.Intrinsics['%Object.prototype%']); + + BootstrapArrayPrototypeShared( + realmRec, + proto, + (thisValue) => { + Q(ValidateTypedArray(thisValue)); + }, + (O) => O.ArrayLength, + ); + + // 22.2.3.31 #sec-%typedarray%.prototype-@@iterator + { + const fn = X(Get(proto, new Value('values'))); + X(proto.DefineOwnProperty(wellKnownSymbols.iterator, Descriptor({ + Value: fn, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + + realmRec.Intrinsics['%TypedArray.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/TypedArrayPrototypes.mjs b/engine262/src/intrinsics/TypedArrayPrototypes.mjs new file mode 100644 index 0000000..a10bb1f --- /dev/null +++ b/engine262/src/intrinsics/TypedArrayPrototypes.mjs @@ -0,0 +1,16 @@ +import { typedArrayInfoByName } from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-properties-of-typedarray-prototype-objects +export function BootstrapTypedArrayPrototypes(realmRec) { + Object.entries(typedArrayInfoByName).forEach(([TypedArray, info]) => { + const proto = BootstrapPrototype(realmRec, [ + ['BYTES_PER_ELEMENT', new Value(info.ElementSize), undefined, { + Writable: Value.false, + Configurable: Value.false, + }], + ], realmRec.Intrinsics['%TypedArray.prototype%']); + realmRec.Intrinsics[`%${TypedArray}.prototype%`] = proto; + }); +} diff --git a/engine262/src/intrinsics/URIHandling.mjs b/engine262/src/intrinsics/URIHandling.mjs new file mode 100644 index 0000000..f5dd9c7 --- /dev/null +++ b/engine262/src/intrinsics/URIHandling.mjs @@ -0,0 +1,325 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + Assert, + CreateBuiltinFunction, + SetFunctionLength, + SetFunctionName, + ToString, +} from '../abstract-ops/all.mjs'; +import { CodePointAt } from '../static-semantics/all.mjs'; +import { isHexDigit } from '../parser/Lexer.mjs'; +import { Q, X } from '../completion.mjs'; + +function utf8Encode(utf) { + if (utf <= 0x7F) { + return [utf]; + } + if (utf <= 0x07FF) { + return [ + (((utf >> 6) & 0x1F) | 0xC0), + (((utf >> 0) & 0x3F) | 0x80), + ]; + } + if (utf <= 0xFFFF) { + return [ + (((utf >> 12) & 0x0F) | 0xE0), + (((utf >> 6) & 0x3F) | 0x80), + (((utf >> 0) & 0x3F) | 0x80), + ]; + } + if (utf <= 0x10FFFF) { + return [ + (((utf >> 18) & 0x07) | 0xF0), + (((utf >> 12) & 0x3F) | 0x80), + (((utf >> 6) & 0x3F) | 0x80), + (((utf >> 0) & 0x3F) | 0x80), + ]; + } + return null; +} + +function utf8Decode(octets) { + const b0 = octets[0]; + if (b0 <= 0x7F) { + return b0; + } + if (b0 < 0xC2 || b0 > 0xF4) { + return null; + } + const b1 = octets[1]; + + switch (b0) { + case 0xE0: + if (b1 < 0xA0 || b1 > 0xBF) { + return null; + } + break; + case 0xED: + if (b1 < 0x80 || b1 > 0x9F) { + return null; + } + break; + case 0xF0: + if (b1 < 0x90 || b1 > 0xBF) { + return null; + } + break; + case 0xF4: + if (b1 < 0x80 || b1 > 0x8F) { + return null; + } + break; + default: + if (b1 < 0x80 || b1 > 0xBF) { + return null; + } + break; + } + + if (b0 <= 0xDF) { + return ((b0 & 0x1F) << 6) + | (b0 & 0x3F); + } + + const b2 = octets[2]; + if (b2 < 0x80 || b2 > 0xBF) { + return null; + } + if (b0 <= 0xEF) { + return ((b0 & 0x0F) << 12) + | ((b1 & 0x3F) << 6) + | (b2 & 0x3F); + } + + const b3 = octets[3]; + if (b3 < 0x80 || b3 > 0xBF) { + return null; + } + + return ((b0 & 0x07) << 18) + | ((b1 & 0x3F) << 12) + | ((b2 & 0x3F) << 6) + | (b3 & 0x3F); +} + +const uriReserved = ';/?:@&=+$,'; +const uriAlpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; +const uriMark = '-_.!~*\'()'; +const DecimalDigit = '0123456789'; +const uriUnescaped = uriAlpha + DecimalDigit + uriMark; + +// #sec-encode +function Encode(string, unescapedSet) { + string = string.stringValue(); + // 1. Let strLen be the number of code units in string. + const strLen = string.length; + // 2. Let R be the empty String. + let R = ''; + // 3. Let k be 0. + let k = 0; + // 4. Repeat, + while (true) { + // a. If k equals strLen, return R. + if (k === strLen) { + return new Value(R); + } + // b. Let C be the code unit at index k within string. + const C = string[k]; + // c. If C is in unescapedSet, then + if (unescapedSet.includes(C)) { + // i. Set k to k + 1. + k += 1; + // ii. Set R to the string-concatenation of the previous value of R and C. + R = `${R}${C}`; + } else { // d. Else, + // i. Let cp be ! CodePointAt(string, k). + const cp = X(CodePointAt(string, k)); + // ii. If cp.[[IsUnpairedSurrogate]] is true, throw a URIError exception. + if (cp.IsUnpairedSurrogate) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // iii. Set k to k + cp.[[CodeUnitCount]]. + k += cp.CodeUnitCount; + // iv. Let Octets be the List of octets resulting by applying the UTF-8 transformation to cp.[[CodePoint]]. + const Octets = utf8Encode(cp.CodePoint); + // v. For each element octet of Octets in List order, do + Octets.forEach((octet) => { + // 1. Set R to the string-concatenation of: + // * the previous value of R + // * "%" + // * the String representation of octet, formatted as a two-digit uppercase hexadecimal number, padded to the left with a zero if necessary + R = `${R}%${octet.toString(16).toUpperCase().padStart(2, '0')}`; + }); + } + } +} + +// #sec-decode +function Decode(string, reservedSet) { + string = string.stringValue(); + // 1. Let strLen be the number of code units in string. + const strLen = string.length; + // 2. Let R be the empty String. + let R = ''; + // 3. Let k be 0. + let k = 0; + // 4. Repeat, + while (true) { + // a. If k equals strLen, return R. + if (k === strLen) { + return new Value(R); + } + // b. Let C be the code unit at index k within string. + const C = string[k]; + let S; + // c. If C is not the code unit 0x0025 (PERCENT SIGN), then + if (C !== '\u{0025}') { + S = C; + } else { // d. Else, + // i. Let start be k. + const start = k; + // ii. If k + 2 is greater than or equal to strLen, throw a URIError exception. + if (k + 2 >= strLen) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // iii. If the code units at index (k + 1) and (k + 2) within string do not represent hexadecimal digits, throw a URIError exception. + if (!isHexDigit(string[k + 1]) || !isHexDigit(string[k + 2])) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // iv. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2). + const B = Number.parseInt(string.slice(k + 1, k + 3), 16); + // v. Set k to k + 2. + k += 2; + // vi. If the most significant bit in B is 0, then + if ((B & 0b10000000) === 0) { + // 1. Let C be the code unit whose value is B. + const innerC = String.fromCharCode(B); + // 2. If C is not in reservedSet, then + if (!reservedSet.includes(C)) { + // a. Let S be the String value containing only the code unit C. + S = innerC; + } else { // 3. Else, + // a. Let S be the substring of string from index start to index k inclusive. + S = string.slice(start, k + 1); + } + } else { // vii. Else, + // 1. Assert: the most significant bit in B is 1. + Assert(B & 0b10000000); + // 2. Let n be the smallest nonnegative integer such that (B << n) & 0x80 is equal to 0. + let n = 0; + while (((B << n) & 0x80) !== 0) { + n += 1; + if (n > 4) { + break; + } + } + // 3. If n equals 1 or n is greater than 4, throw a URIError exception. + if (n === 1 || n > 4) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // 4. Let Octets be a List of 8-bit integers of size n. + const Octets = []; + // 5. Set Octets[0] to B. + Octets[0] = B; + // 6. If k + (3 × (n - 1)) is greater than or equal to strLen, throw a URIError exception. + if (k + (3 * (n - 1)) >= strLen) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // 7. Let j be 1. + let j = 1; + // 8. Repeat, while j < n, + while (j < n) { + // a. Set k to k + 1. + k += 1; + // b. If the code unit at index k within string is not the code unit 0x0025 (PERCENT SIGN), throw a URIError exception. + if (string[k] !== '\u{0025}') { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // c. If the code units at index (k + 1) and (k + 2) within string do not represent hexadecimal digits, throw a URIError exception. + if (!isHexDigit(string[k + 1]) || !isHexDigit(string[k + 2])) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // d. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2). + const innerB = Number.parseInt(string.slice(k + 1, k + 3), 16); + // e. If the two most significant bits in B are not 10, throw a URIError exception. + if (innerB >> 6 !== 0b10) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // f. Set k to k + 2. + k += 2; + // g. Set Octets[j] to B. + Octets[j] = innerB; + // h. Set j to j + 1. + j += 1; + } + // 9. If Octets does not contain a valid UTF-8 encoding of a Unicode code point, throw a URIError exception. + // 10. Let V be the value obtained by applying the UTF-8 transformation to Octets, that is, from a List of octets into a 21-bit value. + const V = utf8Decode(Octets); + if (V === null) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // 11. Let S be the String value whose code units are, in order, the elements in UTF16Encoding(V). + S = String.fromCodePoint(V); + } + } + // e. Set R to the string-concatenation of the previous value of R and S. + R = `${R}${S}`; + // f. Set k to k + 1. + k += 1; + } +} + +// #sec-decodeuri-encodeduri +function decodeURI([encodedURI = Value.undefined]) { + // 1. Let uriString be ? ToString(encodedURI). + const uriString = Q(ToString(encodedURI)); + // 2. Let reservedURISet be a String containing one instance of each code unit valid in uriReserved plus "#". + const reservedURISet = `${uriReserved}#`; + // 3. Return ? Decode(uriString, reservedURISet). + return Q(Decode(uriString, reservedURISet)); +} + +// #sec-decodeuricomponent-encodeduricomponent +function decodeURIComponent([encodedURIComponent = Value.undefined]) { + // 1. Let componentString be ? ToString(encodedURIComponent). + const componentString = Q(ToString(encodedURIComponent)); + // 2. Let reservedURIComponentSet be the empty String. + const reservedURIComponentSet = ''; + // 3. Return ? Decode(componentString, reservedURIComponentSet). + return Q(Decode(componentString, reservedURIComponentSet)); +} + +// #sec-encodeuri-uri +function encodeURI([uri = Value.undefined]) { + // 1. Let uriString be ? ToString(uri). + const uriString = Q(ToString(uri)); + // 2. Let unescapedURISet be a String containing one instance of each code unit valid in uriReserved and uriUnescaped plus "#". + const unescapedURISet = `${uriReserved}${uriUnescaped}#`; + // 3. Return ? Encode(uriString, unescapedURISet). + return Q(Encode(uriString, unescapedURISet)); +} + +// #sec-encodeuricomponent-uricomponent +function encodeURIComponent([uriComponent = Value.undefined]) { + // 1. Let componentString be ? ToString(uriComponent). + const componentString = Q(ToString(uriComponent)); + // 2. Let unescapedURIComponentSet be a String containing one instance of each code unit valid in uriUnescaped. + const unescapedURIComponentSet = uriUnescaped; + // 3. Return ? Encode(componentString, unescapedURIComponentSet). + return Q(Encode(componentString, unescapedURIComponentSet)); +} + +export function BootstrapURIHandling(realmRec) { + [ + ['decodeURI', decodeURI, 1], + ['decodeURIComponent', decodeURIComponent, 1], + ['encodeURI', encodeURI, 1], + ['encodeURIComponent', encodeURIComponent, 1], + ].forEach(([name, f, length]) => { + const fn = CreateBuiltinFunction(f, [], realmRec); + X(SetFunctionName(fn, new Value(name))); + X(SetFunctionLength(fn, new Value(length))); + realmRec.Intrinsics[`%${name}%`] = fn; + }); +} diff --git a/engine262/src/intrinsics/WeakMap.mjs b/engine262/src/intrinsics/WeakMap.mjs new file mode 100644 index 0000000..b474e28 --- /dev/null +++ b/engine262/src/intrinsics/WeakMap.mjs @@ -0,0 +1,39 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Get, + OrdinaryCreateFromConstructor, +} from '../abstract-ops/all.mjs'; +import { + Value, +} from '../value.mjs'; +import { + Q, +} from '../completion.mjs'; +import { AddEntriesFromIterable } from './Map.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-weakmap-constructor +function WeakMapConstructor([iterable = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »). + const map = Q(OrdinaryCreateFromConstructor(NewTarget, '%WeakMap.prototype%', ['WeakMapData'])); + // 3. Set map.[[WeakMapData]] to a new empty List. + map.WeakMapData = []; + // 4. If iterable is either undefined or null, return map. + if (iterable === Value.undefined || iterable === Value.null) { + return map; + } + // 5. Let adder be ? Get(map, "set"). + const adder = Q(Get(map, new Value('set'))); + // 6. Return ? AddEntriesFromIterable(map, iterable, adder). + return Q(AddEntriesFromIterable(map, iterable, adder)); +} + +export function BootstrapWeakMap(realmRec) { + const c = BootstrapConstructor(realmRec, WeakMapConstructor, 'WeakMap', 0, realmRec.Intrinsics['%WeakMap.prototype%'], []); + + realmRec.Intrinsics['%WeakMap%'] = c; +} diff --git a/engine262/src/intrinsics/WeakMapPrototype.mjs b/engine262/src/intrinsics/WeakMapPrototype.mjs new file mode 100644 index 0000000..2961ca4 --- /dev/null +++ b/engine262/src/intrinsics/WeakMapPrototype.mjs @@ -0,0 +1,127 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + SameValue, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, +} from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-weakmap.prototype.delete +function WeakMapProto_delete([key = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. Let entries be the List that is M.[[WeakMapData]]. + const entries = M.WeakMapData; + // 4. If Type(key) is not Object, return false. + if (Type(key) !== 'Object') { + return Value.false; + } + // 5. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (let i = 0; i < entries.length; i += 1) { + const p = entries[i]; + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then + if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) { + // i. Set p.[[Key]] to empty. + p.Key = undefined; + // ii. Set p.[[Value]] to empty. + p.Value = undefined; + // iii. return true. + return Value.true; + } + } + // 6. Return false. + return Value.false; +} + +// #sec-weakmap.prototype.get +function WeakMapProto_get([key = Value.undefined], { thisValue }) { + // 1. Let m be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. Let entries be the List that is M.[[WeakMapData]]. + const entries = M.WeakMapData; + // 4. If Type(key) is not Object, return undefined. + if (Type(key) !== 'Object') { + return Value.undefined; + } + // 5. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]]. + if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) { + return p.Value; + } + } + // 6. Return undefined. + return Value.undefined; +} + +// #sec-weakmap.prototype.has +function WeakMapProto_has([key = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. Let entries be the List that is M.[[WeakMapData]]. + const entries = M.WeakMapData; + // 4. If Type(key) is not Object, return false. + if (Type(key) !== 'Object') { + return Value.false; + } + // 5. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return true. + if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) { + return Value.true; + } + } + // 6. Return false. + return Value.false; +} + +// #sec-weakmap.prototype.set +function WeakMapProto_set([key = Value.undefined, value = Value.undefined], { thisValue }) { + // 1. Let M be the this value. + const M = thisValue; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. Let entries be the List that is M.[[WeakMapData]]. + const entries = M.WeakMapData; + // 4. If Type(key) is not Object, throw a TypeError exception. + if (Type(key) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'WeakCollectionNotObject', key); + } + // 5. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + for (const p of entries) { + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then + if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) { + // i. Set p.[[Value]] to value. + p.Value = value; + // ii. Return M. + return M; + } + } + // 6. Let p be the Record { [[Key]]: key, [[Value]]: value }. + const p = { Key: key, Value: value }; + // 7. Append p as the last element of entries. + entries.push(p); + // 8. Return M. + return M; +} + +export function BootstrapWeakMapPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['delete', WeakMapProto_delete, 1], + ['get', WeakMapProto_get, 1], + ['has', WeakMapProto_has, 1], + ['set', WeakMapProto_set, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'WeakMap'); + + realmRec.Intrinsics['%WeakMap.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/WeakRef.mjs b/engine262/src/intrinsics/WeakRef.mjs new file mode 100644 index 0000000..cc1c558 --- /dev/null +++ b/engine262/src/intrinsics/WeakRef.mjs @@ -0,0 +1,31 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { AddToKeptObjects, OrdinaryCreateFromConstructor } from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-weak-ref-target +function WeakRefConstructor([target = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRefPrototype%", « [[WeakRefTarget]] »). + const weakRef = Q(OrdinaryCreateFromConstructor(NewTarget, '%WeakRef.prototype%', ['WeakRefTarget'])); + // 4. Perfom ! AddToKeptObjects(target). + X(AddToKeptObjects(target)); + // 5. Set weakRef.[[WeakRefTarget]] to target. + weakRef.WeakRefTarget = target; + // 6. Return weakRef + return weakRef; +} + +export function BootstrapWeakRef(realmRec) { + const bigintConstructor = BootstrapConstructor(realmRec, WeakRefConstructor, 'WeakRef', 1, realmRec.Intrinsics['%WeakRef.prototype%'], []); + + realmRec.Intrinsics['%WeakRef%'] = bigintConstructor; +} diff --git a/engine262/src/intrinsics/WeakRefPrototype.mjs b/engine262/src/intrinsics/WeakRefPrototype.mjs new file mode 100644 index 0000000..3e0dc25 --- /dev/null +++ b/engine262/src/intrinsics/WeakRefPrototype.mjs @@ -0,0 +1,21 @@ +import { RequireInternalSlot, WeakRefDeref } from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-weak-ref.prototype.deref +function WeakRefProto_deref(args, { thisValue }) { + // 1. Let weakRef be the this value. + const weakRef = thisValue; + // 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]). + Q(RequireInternalSlot(weakRef, 'WeakRefTarget')); + // 3. Return ! WeakRefDeref(weakRef). + return X(WeakRefDeref(weakRef)); +} + +export function BootstrapWeakRefPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['deref', WeakRefProto_deref, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'WeakRef'); + + realmRec.Intrinsics['%WeakRef.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/WeakSet.mjs b/engine262/src/intrinsics/WeakSet.mjs new file mode 100644 index 0000000..f590f68 --- /dev/null +++ b/engine262/src/intrinsics/WeakSet.mjs @@ -0,0 +1,60 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + IsCallable, + OrdinaryCreateFromConstructor, + Call, + Get, + GetIterator, + IteratorStep, + IteratorValue, + IteratorClose, +} from '../abstract-ops/all.mjs'; +import { Q, AbruptCompletion } from '../completion.mjs'; +import { BootstrapConstructor } from './Bootstrap.mjs'; + +// #sec-weakset-iterable +function WeakSetConstructor([iterable = Value.undefined], { NewTarget }) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakSet.prototype%", « [[WeakSetData]] »). + const set = Q(OrdinaryCreateFromConstructor(NewTarget, '%WeakSet.prototype%', ['WeakSetData'])); + // 3. Set set.[[WeakSetData]] to a new empty List. + set.WeakSetData = []; + // 4. If iterable is either undefined or null, return set. + if (iterable === Value.undefined || iterable === Value.null) { + return set; + } + // 5. Let adder be ? Get(set, "add"). + const adder = Q(Get(set, new Value('add'))); + // 6. If IsCallable(adder) is false, throw a TypeError exception. + if (IsCallable(adder) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + // 7. Let iteratorRecord be ? GetIterator(iterable). + const iteratorRecord = Q(GetIterator(iterable)); + // 8. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iteratorRecord). + const next = Q(IteratorStep(iteratorRecord)); + // b. If next is false, return set. + if (next === Value.false) { + return set; + } + // c. Let nextValue be ? IteratorValue(next). + const nextValue = Q(IteratorValue(next)); + // d. Let status be Call(adder, set, « nextValue »). + const status = Call(adder, set, [nextValue]); + // e. If status is an abrupt completion, return ? IteratorClose(iteratorRecord, status). + if (status instanceof AbruptCompletion) { + return Q(IteratorClose(iteratorRecord, status)); + } + } +} + +export function BootstrapWeakSet(realmRec) { + const c = BootstrapConstructor(realmRec, WeakSetConstructor, 'WeakSet', 0, realmRec.Intrinsics['%WeakSet.prototype%'], []); + realmRec.Intrinsics['%WeakSet%'] = c; +} diff --git a/engine262/src/intrinsics/WeakSetPrototype.mjs b/engine262/src/intrinsics/WeakSetPrototype.mjs new file mode 100644 index 0000000..a5541af --- /dev/null +++ b/engine262/src/intrinsics/WeakSetPrototype.mjs @@ -0,0 +1,97 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + SameValue, + RequireInternalSlot, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, +} from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { BootstrapPrototype } from './Bootstrap.mjs'; + +// #sec-weakset.prototype.add +function WeakSetProto_add([value = Value.undefined], { thisValue }) { + // 1. Let S be this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). + Q(RequireInternalSlot(S, 'WeakSetData')); + // 3. If Type(value) is not Object, throw a TypeError exception. + if (Type(value) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'WeakCollectionNotObject', value); + } + // 4. Let entries be the List that is S.[[WeakSetData]]. + const entries = S.WeakSetData; + // 5. For each e that is an element of entries, do + for (const e of entries) { + // a. If e is not empty and SameValue(e, value) is true, then + if (e !== undefined && SameValue(e, value) === Value.true) { + // i. Return S. + return S; + } + } + // 6. Append value as the last element of entries. + entries.push(value); + // 7. Return S. + return S; +} + +// #sec-weakset.prototype.delete +function WeakSetProto_delete([value = Value.undefined], { thisValue }) { + // 1. Let S be the this value.` + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). + Q(RequireInternalSlot(S, 'WeakSetData')); + // 3. If Type(value) is not Object, return false. + if (Type(value) !== 'Object') { + return Value.false; + } + // 4. Let entries be the List that is S.[[WeakSetData]]. + const entries = S.WeakSetData; + // 5. For each e that is an element of entries, do + for (let i = 0; i < entries.length; i += 1) { + const e = entries[i]; + // i. If e is not empty and SameValue(e, value) is true, then + if (e !== undefined && SameValue(e, value) === Value.true) { + // i. Replace the element of entries whose value is e with an element whose value is empty. + entries[i] = undefined; + // ii. Return true. + return Value.true; + } + } + // 6. Return false. + return Value.false; +} + +// #sec-weakset.prototype.has +function WeakSetProto_has([value = Value.undefined], { thisValue }) { + // 1. Let S be the this value. + const S = thisValue; + // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). + Q(RequireInternalSlot(S, 'WeakSetData')); + // 3. Let entries be the List that is S.[[WeakSetData]]. + const entries = S.WeakSetData; + // 4. If Type(value) is not Object, return false. + if (Type(value) !== 'Object') { + return Value.false; + } + // 5. For each e that is an element of entries, do + for (const e of entries) { + // a. If e is not empty and SameValue(e, value) is true, return true. + if (e !== undefined && SameValue(e, value) === Value.true) { + return Value.true; + } + } + // 6. Return false. + return Value.false; +} + +export function BootstrapWeakSetPrototype(realmRec) { + const proto = BootstrapPrototype(realmRec, [ + ['add', WeakSetProto_add, 1], + ['delete', WeakSetProto_delete, 1], + ['has', WeakSetProto_has, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'WeakSet'); + + realmRec.Intrinsics['%WeakSet.prototype%'] = proto; +} diff --git a/engine262/src/intrinsics/eval.mjs b/engine262/src/intrinsics/eval.mjs new file mode 100644 index 0000000..c9a18d5 --- /dev/null +++ b/engine262/src/intrinsics/eval.mjs @@ -0,0 +1,30 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Q } from '../completion.mjs'; +import { Value } from '../value.mjs'; +import { + Assert, + CreateBuiltinFunction, + PerformEval, + SetFunctionLength, + SetFunctionName, +} from '../abstract-ops/all.mjs'; + +// #sec-eval-x +function Eval([x = Value.undefined]) { + // 1. Assert: The execution context stack has at least two elements. + Assert(surroundingAgent.executionContextStack.length >= 2); + // 2. Let callerContext be the second to top element of the execution context stack. + const callerContext = surroundingAgent.executionContextStack[surroundingAgent.executionContextStack.length - 2]; + // 3. Let callerRealm be callerContext's Realm. + const callerRealm = callerContext.Realm; + // 4. Return ? PerformEval(x, callerRealm, false, false). + return Q(PerformEval(x, callerRealm, false, false)); +} + +export function BootstrapEval(realmRec) { + const it = CreateBuiltinFunction(Eval, [], realmRec); + SetFunctionName(it, new Value('eval')); + SetFunctionLength(it, new Value(1)); + + realmRec.Intrinsics['%eval%'] = it; +} diff --git a/engine262/src/intrinsics/isFinite.mjs b/engine262/src/intrinsics/isFinite.mjs new file mode 100644 index 0000000..9c261ac --- /dev/null +++ b/engine262/src/intrinsics/isFinite.mjs @@ -0,0 +1,27 @@ +import { + ToNumber, + CreateBuiltinFunction, + SetFunctionName, + SetFunctionLength, +} from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-isfinite-number +function IsFinite([number = Value.undefined]) { + // 1. Let num be ? ToNumber(number). + const num = Q(ToNumber(number)); + // 2. If num is NaN, +∞, or -∞, return false. + if (num.isNaN() || num.isInfinity()) { + return Value.false; + } + // 3. Otherwise, return true. + return Value.true; +} + +export function BootstrapIsFinite(realmRec) { + const fn = CreateBuiltinFunction(IsFinite, [], realmRec); + X(SetFunctionName(fn, new Value('isFinite'))); + X(SetFunctionLength(fn, new Value(1))); + realmRec.Intrinsics['%isFinite%'] = fn; +} diff --git a/engine262/src/intrinsics/isNaN.mjs b/engine262/src/intrinsics/isNaN.mjs new file mode 100644 index 0000000..6984c27 --- /dev/null +++ b/engine262/src/intrinsics/isNaN.mjs @@ -0,0 +1,27 @@ +import { + ToNumber, + CreateBuiltinFunction, + SetFunctionName, + SetFunctionLength, +} from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-isnan-number +function IsNaN([number = Value.undefined]) { + // 1. Let num be ? ToNumber(number). + const num = Q(ToNumber(number)); + // 2. If num is NaN, return true. + if (num.isNaN()) { + return Value.true; + } + // 3. Otherwise, return false. + return Value.false; +} + +export function BootstrapIsNaN(realmRec) { + const fn = CreateBuiltinFunction(IsNaN, [], realmRec); + X(SetFunctionName(fn, new Value('isNaN'))); + X(SetFunctionLength(fn, new Value(1))); + realmRec.Intrinsics['%isNaN%'] = fn; +} diff --git a/engine262/src/intrinsics/parseFloat.mjs b/engine262/src/intrinsics/parseFloat.mjs new file mode 100644 index 0000000..8d3e230 --- /dev/null +++ b/engine262/src/intrinsics/parseFloat.mjs @@ -0,0 +1,81 @@ +import { + CreateBuiltinFunction, + SetFunctionName, + SetFunctionLength, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { Value } from '../value.mjs'; +import { + TrimString, +} from '../runtime-semantics/all.mjs'; + +// #sec-parsefloat-string +function ParseFloat([string = Value.undefined]) { + // 1. Let inputString be ? ToString(string). + const inputString = Q(ToString(string)); + // 2. Let trimmedString be ! TrimString(inputString, start). + const trimmedString = X(TrimString(inputString, 'start')).stringValue(); + // 3. If neither trimmedString nor any prefix of trimmedString satisfies the syntax of a StrDecimalLiteral (see 7.1.4.1), return NaN. + // 4. Let numberString be the longest prefix of trimmedString, which might be trimmedString itself, that satisfies the syntax of a StrDecimalLiteral. + // 5. Let mathFloat be MV of numberString. + // 6. If mathFloat = 0ℝ, then + // a. If the first code unit of trimmedString is the code unit 0x002D (HYPHEN-MINUS), return -0. + // b. Return +0. + // 7. Return the Number value for mathFloat. + let numberString = trimmedString; + if (/^[+-]/.test(numberString)) { + numberString = numberString.slice(1); + } + const multiplier = trimmedString.startsWith('-') ? -1 : 1; + if (numberString.startsWith('Infinity')) { + return new Value(Infinity * multiplier); + } + let index = 0; + done: { // eslint-disable-line no-labels + // Eat leading zeros + while (numberString[index] === '0') { + index += 1; + if (index === numberString.length) { + return new Value(0 * multiplier); + } + } + // Eat integer part + if (numberString[index] !== '.') { + while (/[0-9]/.test(numberString[index])) { + index += 1; + } + } + // Eat fractional part + if (numberString[index] === '.') { + if (!/[0-9eE]/.test(numberString[index + 1])) { + break done; // eslint-disable-line no-labels + } + index += 1; + while (/[0-9]/.test(numberString[index])) { + index += 1; + } + } + // Eat exponent part + if (numberString[index] === 'e' || numberString[index] === 'E') { + if (!/[-+0-9]/.test(numberString[index + 1])) { + break done; // eslint-disable-line no-labels + } + index += 1; + if (numberString[index] === '-' || numberString[index] === '+') { + index += 1; + } + while (/[0-9]/.test(numberString[index])) { + index += 1; + } + } + } + return new Value(parseFloat(numberString.slice(0, index)) * multiplier); +} + +export function BootstrapParseFloat(realmRec) { + const fn = CreateBuiltinFunction(ParseFloat, [], realmRec); + X(SetFunctionName(fn, new Value('parseFloat'))); + X(SetFunctionLength(fn, new Value(1))); + realmRec.Intrinsics['%parseFloat%'] = fn; +} diff --git a/engine262/src/intrinsics/parseInt.mjs b/engine262/src/intrinsics/parseInt.mjs new file mode 100644 index 0000000..8fd0121 --- /dev/null +++ b/engine262/src/intrinsics/parseInt.mjs @@ -0,0 +1,104 @@ +import { + Assert, + CreateBuiltinFunction, + SetFunctionName, + SetFunctionLength, + ToInt32, + ToString, +} from '../abstract-ops/all.mjs'; +import { TrimString } from '../runtime-semantics/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { Value } from '../value.mjs'; + +function digitToNumber(digit) { + digit = digit.charCodeAt(0); + if (digit < 0x30 /* 0 */) { + return NaN; + } + if (digit <= 0x39 /* 9 */) { + return digit - 0x30; + } + // Convert to lower case. + digit &= ~0x20; // eslint-disable-line no-bitwise + if (digit < 0x41 /* A */) { + return NaN; + } + if (digit <= 0x5a /* Z */) { + return digit - 0x41 /* A */ + 10; + } + return NaN; +} + +function stringToRadixNumber(str, R) { + let num = 0; + for (let i = 0; i < str.length; i += 1) { + const power = str.length - i - 1; + const multiplier = R ** power; + const dig = digitToNumber(str[i]); + Assert(!Number.isNaN(dig) && dig < R); + num += dig * multiplier; + } + return num; +} + +function searchNotRadixDigit(str, R) { + for (let i = 0; i < str.length; i += 1) { + const num = digitToNumber(str[i]); + if (Number.isNaN(num) || num >= R) { + return i; + } + } + return str.length; +} + +// #sec-parseint-string-radix +function ParseInt([string = Value.undefined, radix = Value.undefined]) { + const inputString = Q(ToString(string)); + let S = X(TrimString(inputString, 'start')).stringValue(); + let sign = 1; + if (S !== '' && S[0] === '\x2D') { + sign = -1; + } + if (S !== '' && (S[0] === '\x2B' || S[0] === '\x2D')) { + S = S.slice(1); + } + + let R = Q(ToInt32(radix)).numberValue(); + let stripPrefix = true; + if (R !== 0) { + if (R < 2 || R > 36) { + return new Value(NaN); + } + if (R !== 16) { + stripPrefix = false; + } + } else { + R = 10; + } + if (stripPrefix === true) { + if (S.length >= 2 && (S.startsWith('0x') || S.startsWith('0X'))) { + S = S.slice(2); + R = 16; + } + } + const Z = S.slice(0, searchNotRadixDigit(S, R)); + if (Z === '') { + return new Value(NaN); + } + const mathInt = stringToRadixNumber(Z, R); + if (mathInt === 0) { + if (sign === -1) { + return new Value(-0); + } + return new Value(+0); + } + const number = mathInt; + return new Value(sign * number); +} + +export function BootstrapParseInt(realmRec) { + const fn = CreateBuiltinFunction(ParseInt, [], realmRec); + X(SetFunctionName(fn, new Value('parseInt'))); + X(SetFunctionLength(fn, new Value(2))); + realmRec.Intrinsics['%parseInt%'] = fn; +} diff --git a/engine262/src/messages.mjs b/engine262/src/messages.mjs new file mode 100644 index 0000000..f1f1563 --- /dev/null +++ b/engine262/src/messages.mjs @@ -0,0 +1,160 @@ +import { surroundingAgent } from './engine.mjs'; +import { Value } from './value.mjs'; +import { inspect } from './api.mjs'; + +function i(V) { + if (V instanceof Value) { + return inspect(V, surroundingAgent.currentRealmRecord, true); + } + return `${V}`; +} + +export const Raw = (s) => s; + +export const AlreadyDeclared = (n) => `${i(n)} is already declared`; +export const ArrayBufferDetached = () => 'Attempt to access detached ArrayBuffer'; +export const ArrayBufferShared = () => 'Attempt to access shared ArrayBuffer'; +export const ArrayPastSafeLength = () => 'Cannot make length of array-like object surpass the bounds of an integer index'; +export const ArrayEmptyReduce = () => 'Cannot reduce an empty array with no initial value'; +export const AssignmentToConstant = (n) => `Assignment to constant variable ${i(n)}`; +export const AwaitInFormalParameters = () => 'await is not allowed in function parameters'; +export const AwaitNotInAsyncFunction = () => 'await is only valid in async functions'; +export const BigIntDivideByZero = () => 'Division by zero'; +export const BigIntNegativeExponent = () => 'Exponent must be positive'; +export const BigIntUnsignedRightShift = () => 'BigInt has no unsigned right shift, use >> instead'; +export const BufferContentTypeMismatch = () => 'Newly created TypedArray did not match exemplar\'s content type'; +export const BufferDetachKeyMismatch = (k, b) => `${i(k)} is not the [[ArrayBufferDetachKey]] of ${i(b)}`; +export const CannotAllocateDataBlock = () => 'Cannot allocate memory'; +export const CannotCreateProxyWith = (x, y) => `Cannot create a proxy with a ${x} as ${y}`; +export const CannotConvertDecimalToBigInt = (n) => `Cannot convert ${i(n)} to a BigInt because it is not an integer`; +export const CannotConvertSymbol = (t) => `Cannot convert a Symbol value to a ${t}`; +export const CannotConvertToBigInt = (v) => `Cannot convert ${i(v)} to a BigInt`; +export const CannotConvertToObject = (t) => `Cannot convert ${t} to object`; +export const CannotDefineProperty = (p) => `Cannot define property ${i(p)}`; +export const CannotDeleteProperty = (p) => `Cannot delete property ${i(p)}`; +export const CannotDeleteSuper = () => 'Cannot delete a super property'; +export const CannotJSONSerializeBigInt = () => 'Cannot serialize a BigInt to JSON'; +export const CannotMixBigInts = () => 'Cannot mix BigInt and other types, use explicit conversions'; +export const CannotResolvePromiseWithItself = () => 'Cannot resolve a promise with itself'; +export const CannotSetProperty = (p, o) => `Cannot set property ${i(p)} on ${i(o)}`; +export const ClassMissingBindingIdentifier = () => 'Class declaration missing binding identifier'; +export const ConstDeclarationMissingInitializer = () => 'Missing initialization of const declaration'; +export const ConstructorNonCallable = (f) => `${i(f)} cannot be invoked without new`; +export const CouldNotResolveModule = (s) => `Could not resolve module ${i(s)}`; +export const DataViewOOB = () => 'Offset is outside the bounds of the DataView'; +export const DeleteIdentifier = () => 'Delete of identifier in strict mode'; +export const DateInvalidTime = () => 'Invalid time'; +export const DerivedConstructorReturnedNonObject = () => 'Derived constructors may only return object or undefined'; +export const DuplicateConstructor = () => 'A class may only have one constructor'; +export const DuplicateExports = () => 'Module cannot contain duplicate exports'; +export const FunctionDeclarationStatement = () => 'Functions can only be declared at top level or inside a block'; +export const GeneratorRunning = () => 'Cannot manipulate a running generator'; +export const IllegalBreakContinue = (isBreak) => `Illegal ${isBreak ? 'break' : 'continue'} statement`; +export const IllegalOctalEscape = () => 'Illegal octal escape'; +export const InternalSlotMissing = (o, s) => `Internal slot ${s} is missing for ${i(o)}`; +export const InvalidArrayLength = (l) => `Invalid array length: ${i(l)}`; +export const InvalidAssignmentTarget = () => 'Invalid assignment target'; +export const InvalidCodePoint = () => 'Not a valid code point'; +export const InvalidHint = (v) => `Invalid hint: ${i(v)}`; +export const InvalidMethodName = (name) => `Method cannot be named '${i(name)}'`; +export const InvalidPropertyDescriptor = () => 'Invalid property descriptor. Cannot both specify accessors and a value or writable attribute'; +export const InvalidRadix = () => 'Radix must be between 2 and 36, inclusive'; +export const InvalidReceiver = (f, v) => `${f} called on invalid receiver: ${i(v)}`; +export const InvalidRegExpFlags = (f) => `Invalid RegExp flags: ${f}`; +export const InvalidSuperCall = () => '`super` not expected here'; +export const InvalidSuperProperty = () => '`super` not expected here'; +export const InvalidTemplateEscape = () => 'Invalid escapes are only allowed in tagged templates'; +export const InvalidThis = () => 'Invalid `this` access'; +export const InvalidUnicodeEscape = () => 'Invalid unicode escape'; +export const IteratorThrowMissing = () => 'The iterator does not provide a throw method'; +export const JSONCircular = () => 'Cannot JSON stringify a circular structure'; +export const JSONUnexpectedToken = () => 'Unexpected token in JSON'; +export const JSONUnexpectedChar = (c) => `Unexpected character ${c} in JSON`; +export const JSONExpected = (e, a) => `Expected character ${e} but got ${a} in JSON`; +export const LetInLexicalBinding = () => '\'let\' is not allowed to be used as a name in lexical declarations'; +export const ModuleExportNameInvalidUnicode = () => 'Export name is not valid unicode'; +export const ModuleUndefinedExport = (n) => `Export '${i(n)}' is not defined in module`; +export const NegativeIndex = (n) => `${n} cannot be negative`; +export const NewlineAfterThrow = () => 'Illegal newline after throw'; +export const NormalizeInvalidForm = () => 'Invalid normalization form'; +export const NotAConstructor = (v) => `${i(v)} is not a constructor`; +export const NotAFunction = (v) => `${i(v)} is not a function`; +export const NotATypeObject = (t, v) => `${i(v)} is not a ${t} object`; +export const NotAnObject = (v) => `${i(v)} is not an object`; +export const NotASymbol = (v) => `${i(v)} is not a symbol`; +export const NotDefined = (n) => `${i(n)} is not defined`; +export const NotInitialized = (n) => `${i(n)} cannot be used before initialization`; +export const NotPropertyName = (p) => `${i(p)} is not a valid property name`; +export const NumberFormatRange = (m) => `Invalid format range for ${m}`; +export const ObjectToPrimitive = () => 'Cannot convert object to primitive value'; +export const ObjectPrototypeType = () => 'Object prototype must be an Object or null'; +export const ObjectSetPrototype = () => 'Could not set prototype of object'; +export const OutOfRange = (n) => `${n} is out of range`; +export const PromiseAnyRejected = () => 'No promises passed to Promise.any were fulfilled'; +export const PromiseCapabilityFunctionAlreadySet = (f) => `Promise ${f} function already set`; +export const PromiseRejectFunction = (v) => `Promise reject function ${i(v)} is not callable`; +export const PromiseResolveFunction = (v) => `Promise resolve function ${i(v)} is not callable`; +export const ProxyRevoked = (n) => `Cannot perform '${n}' on a proxy that has been revoked`; +export const ProxyDefinePropertyNonConfigurable = (p) => `'defineProperty' on proxy: trap returned truish for defining non-configurable property ${i(p)} which is either non-existent or configurable in the proxy target`; +export const ProxyDefinePropertyNonConfigurableWritable = (p) => `'defineProperty' on proxy: trap returned truish for defining non-configurable property ${i(p)} which cannot be non-writable, unless there exists a corresponding non-configurable, non-writable own property of the target object`; +export const ProxyDefinePropertyNonExtensible = (p) => `'defineProperty' on proxy: trap returned truish for adding property ${i(p)} to the non-extensible proxy target`; +export const ProxyDefinePropertyIncompatible = (p) => `'defineProperty' on proxy: trap returned truish for adding property ${i(p)} that is incompatible with the existing property in the proxy target`; +export const ProxyDeletePropertyNonConfigurable = (p) => `'deleteProperty' on proxy: trap returned truthy for property ${i(p)} which is non-configurable in the proxy target`; +export const ProxyDeletePropertyNonExtensible = (p) => `'deleteProperty' on proxy: trap returned truthy for property ${i(p)} but the proxy target is non-extensible`; +export const ProxyGetNonConfigurableData = (p) => `'get' on proxy: property ${i(p)} is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value`; +export const ProxyGetNonConfigurableAccessor = (p) => `'get' on proxy: property ${i(p)} is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined'`; +export const ProxyGetPrototypeOfInvalid = () => '\'getPrototypeOf\' on proxy: trap returned neither object nor null'; +export const ProxyGetPrototypeOfNonExtensible = () => '\'getPrototypeOf\' on proxy: proxy target is non-extensible but the trap did not return its actual prototype'; +export const ProxyGetOwnPropertyDescriptorIncompatible = (p) => `'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property ${i(p)} that is incompatible with the existing property in the proxy target`; +export const ProxyGetOwnPropertyDescriptorInvalid = (p) => `'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined for property ${i(p)}`; +export const ProxyGetOwnPropertyDescriptorUndefined = (p) => `'getOwnPropertyDescriptor' on proxy: trap returned undefined for property ${i(p)} which is non-configurable in the proxy target`; +export const ProxyGetOwnPropertyDescriptorNonExtensible = (p) => `'getOwnPropertyDescriptor' on proxy: trap returned undefined for property ${i(p)} which exists in the non-extensible target`; +export const ProxyGetOwnPropertyDescriptorNonConfigurable = (p) => `'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property ${i(p)} which is either non-existent or configurable in the proxy target`; +export const ProxyGetOwnPropertyDescriptorNonConfigurableWritable = (p) => `'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property ${i(p)} which is writable or configurable in the proxy target`; +export const ProxyHasNonConfigurable = (p) => `'has' on proxy: trap returned falsy for property ${i(p)} which exists in the proxy target as non-configurable`; +export const ProxyHasNonExtensible = (p) => `'has' on proxy: trap returned falsy for property ${i(p)} but the proxy target is not extensible`; +export const ProxyIsExtensibleInconsistent = (e) => `'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is ${i(e)})`; +export const ProxyOwnKeysMissing = (p) => `'ownKeys' on proxy: trap result did not include ${i(p)}`; +export const ProxyOwnKeysNonExtensible = () => '\'ownKeys\' on proxy: trap result returned extra keys but proxy target is non-extensible'; +export const ProxyOwnKeysDuplicateEntries = () => '\'ownKeys\' on proxy: trap returned duplicate entries'; +export const ProxyPreventExtensionsExtensible = () => '\'preventExtensions\' on proxy: trap returned truthy but the proxy target is extensible'; +export const ProxySetPrototypeOfNonExtensible = () => '\'setPrototypeOf\' on proxy: trap returned truthy for setting a new prototype on the non-extensible proxy target'; +export const ProxySetFrozenData = (p) => `'set' on proxy: trap returned truthy for property ${i(p)} which exists in the proxy target as a non-configurable and non-writable data property with a different value`; +export const ProxySetFrozenAccessor = (p) => `'set' on proxy: trap returned truish for property ${i(p)} which exists in the proxy target as a non-configurable and non-writable accessor property without a setter`; +export const RegExpArgumentNotAllowed = (m) => `First argument to ${m} must not be a regular expression`; +export const RegExpExecNotObject = (o) => `${i(o)} is not object or null`; +export const ResolutionNullOrAmbiguous = (r, n, m) => (r === null + ? `Could not resolve import ${i(n)} from ${m.HostDefined.specifier}` + : `Star export ${i(n)} from ${m.HostDefined.specifier} is ambiguous`); +export const SpeciesNotConstructor = () => 'object.constructor[Symbol.species] is not a constructor'; +export const StrictModeDelete = (n) => `Cannot not delete property ${i(n)}`; +export const StrictPoisonPill = () => 'The caller, callee, and arguments properties may not be accessed on functions or the arguments objects for calls to them'; +export const StringRepeatCount = (v) => `Count ${i(v)} is invalid`; +export const StringCodePointInvalid = (n) => `Invalid code point ${i(n)}`; +export const StringPrototypeMethodGlobalRegExp = (m) => `The RegExp passed to String.prototype.${m} must have the global flag`; +export const SubclassLengthTooSmall = (v) => `Subclass constructor returned a smaller-than-requested object ${i(v)}`; +export const SubclassSameValue = (v) => `Subclass constructor returned the same object ${i(v)}`; +export const TargetMatchesHeldValue = (v) => `heldValue ${i(v)} matches target`; +export const TemplateInOptionalChain = () => 'Templates are not allowed in optional chains'; +export const TryMissingCatchOrFinally = () => 'Missing catch or finally after try'; +export const TypedArrayCreationOOB = () => 'Sum of start offset and byte length should be less than the size of underlying buffer'; +export const TypedArrayLengthAlignment = (n, m) => `Size of ${n} should be a multiple of ${m}`; +export const TypedArrayOOB = () => 'Sum of start offset and byte length should be less than the size of the TypedArray'; +export const TypedArrayOffsetAlignment = (n, m) => `Start offset of ${n} should be a multiple of ${m}`; +export const TypedArrayTooSmall = () => 'Derived TypedArray constructor created an array which was too small'; +export const UnableToSeal = (o) => `Unable to seal object ${i(o)}`; +export const UnableToFreeze = (o) => `Unable to freeze object ${i(o)}`; +export const UnableToPreventExtensions = (o) => `Unable to prevent extensions on object ${i(o)}`; +export const UnterminatedComment = () => 'Missing */ after comment'; +export const UnterminatedRegExp = () => 'Missing / after RegExp literal'; +export const UnterminatedString = () => 'Missing \' or " after string literal'; +export const UnterminatedTemplate = () => 'Missing ` after template literal'; +export const UnexpectedEOS = () => 'Unexpected end of source'; +export const UnexpectedEvalOrArguments = () => '`arguments` and `eval` are not valid in this context'; +export const UnexpectedToken = () => 'Unexpected token'; +export const UnexpectedReservedWordStrict = () => 'Unexpected reserved word in strict mode'; +export const UseStrictNonSimpleParameter = () => 'Function with \'use strict\' directive has non-simple parameter list'; +export const URIMalformed = () => 'URI malformed'; +export const WeakCollectionNotObject = (v) => `${i(v)} is not a valid weak collectection entry object`; +export const YieldInFormalParameters = () => 'yield is not allowed in function parameters'; +export const YieldNotInGenerator = () => 'yield is only valid in generators'; diff --git a/engine262/src/modules.mjs b/engine262/src/modules.mjs new file mode 100644 index 0000000..26460fc --- /dev/null +++ b/engine262/src/modules.mjs @@ -0,0 +1,519 @@ +import { NewModuleEnvironment } from './environment.mjs'; +import { Value, Type } from './value.mjs'; +import { ExecutionContext, HostResolveImportedModule, surroundingAgent } from './engine.mjs'; +import { + Assert, + Call, + NewPromiseCapability, + GetModuleNamespace, + InnerModuleEvaluation, + InnerModuleLinking, + SameValue, + GetAsyncCycleRoot, + AsyncBlockStart, + PromiseCapabilityRecord, +} from './abstract-ops/all.mjs'; +import { + VarScopedDeclarations, + LexicallyScopedDeclarations, + BoundNames, + IsConstantDeclaration, +} from './static-semantics/all.mjs'; +import { InstantiateFunctionObject } from './runtime-semantics/all.mjs'; +import { + Completion, + NormalCompletion, + AbruptCompletion, + EnsureCompletion, + Q, X, +} from './completion.mjs'; +import { ValueSet, unwind } from './helpers.mjs'; +import { Evaluate } from './evaluator.mjs'; + +// #resolvedbinding-record +export class ResolvedBindingRecord { + constructor({ Module, BindingName }) { + Assert(Module instanceof AbstractModuleRecord); + Assert(BindingName === 'namespace' || BindingName === 'default' || Type(BindingName) === 'String'); + this.Module = Module; + this.BindingName = BindingName; + } + + mark(m) { + m(this.Module); + } +} + +// 15.2.1.15 #sec-abstract-module-records +export class AbstractModuleRecord { + constructor({ + Realm, + Environment, + Namespace, + HostDefined, + }) { + this.Realm = Realm; + this.Environment = Environment; + this.Namespace = Namespace; + this.HostDefined = HostDefined; + } + + mark(m) { + m(this.Realm); + m(this.Environment); + m(this.Namespace); + } +} + +// 15.2.1.16 #sec-cyclic-module-records +export class CyclicModuleRecord extends AbstractModuleRecord { + constructor(init) { + super(init); + this.Status = init.Status; + this.EvaluationError = init.EvaluationError; + this.DFSIndex = init.DFSIndex; + this.DFSAncestorIndex = init.DFSAncestorIndex; + this.RequestedModules = init.RequestedModules; + this.Async = init.Async; + this.AsyncEvaluating = init.AsyncEvaluating; + this.TopLevelCapability = init.TopLevelCapability; + this.AsyncParentModules = init.AsyncParentModules; + this.PendingAsyncDependencies = init.PendingAsyncDependencies; + } + + // #sec-moduledeclarationlinking + Link() { + // 1. Let module be this Cyclic Module Record. + const module = this; + // 2. Assert: module.[[Status]] is not linking or evaluating. + Assert(module.Status !== 'linking' && module.Status !== 'evaluating'); + // 3. Let stack be a new empty List. + const stack = []; + // 4. Let result be InnerModuleLinking(module, stack, 0). + const result = InnerModuleLinking(module, stack, 0); + // 5. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // a. For each Cyclic Module Record m in stack, do + for (const m of stack) { + // i. Assert: m.[[Status]] is linking. + Assert(m.Status === 'linking'); + // ii. Set m.[[Status]] to unlinked. + m.Status = 'unlinked'; + // iii. Set m.[[Environment]] to undefined. + m.Environment = Value.undefined; + // iv. Set m.[[DFSIndex]] to undefined. + m.DFSIndex = Value.undefined; + // v. Set m.[[DFSAncestorIndex]] to undefined. + m.DFSAncestorIndex = Value.undefined; + } + // b. Assert: module.[[Status]] is unlinked. + Assert(module.Status === 'unlinked'); + // c. Return result. + return result; + } + // 6. Assert: module.[[Status]] is linked or evaluated. + Assert(module.Status === 'linked' || module.Status === 'evaluated'); + // 7. Assert: stack is empty. + Assert(stack.length === 0); + // 8. Return undefined. + return Value.undefined; + } + + // #sec-moduleevaluation + Evaluate() { + // 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. + // 2. Let module be this Cyclic Module Record. + let module = this; + // 3. Assert: module.[[Status]] is linked or evaluated. + Assert(module.Status === 'linked' || module.Status === 'evaluated'); + // (*TopLevelAwait) 3. If module.[[Status]] is "evaluated", set module to GetAsyncCycleRoot(module). + if (module.Status === 'evaluated') { + module = GetAsyncCycleRoot(module); + } + // (*TopLevelAwait) 4. If module.[[TopLevelCapability]] is not undefined, then + if (module.TopLevelCapability !== Value.undefined) { + // a. Return module.[[TopLevelCapability]].[[Promise]]. + return module.TopLevelCapability.Promise; + } + // 4. Let stack be a new empty List. + const stack = []; + // (*TopLevelAwait) 6. Let capability be ! NewPromiseCapability(%Promise%). + const capability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // (*TopLevelAwait) 7. Set module.[[TopLevelCapability]] to capability. + module.TopLevelCapability = capability; + // 5. Let result be InnerModuleEvaluation(module, stack, 0). + const result = InnerModuleEvaluation(module, stack, 0); + // 6. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // a. For each Cyclic Module Record m in stack, do + for (const m of stack) { + // i. Assert: m.[[Status]] is evaluating. + Assert(m.Status === 'evaluating'); + // ii. Set m.[[Status]] to evaluated. + m.Status = 'evaluated'; + // iii. Set m.[[EvaluationError]] to result. + m.EvaluationError = result; + } + // b. Assert: module.[[Status]] is evaluated and module.[[EvaluationError]] is result. + Assert(module.Status === 'evaluated' && module.EvaluationError === result); + // c. Return result. + // c. (*TopLevelAwait) Perform ! Call(capability.[[Reject]], undefined, «result.[[Value]]»). + X(Call(capability.Reject, Value.undefined, [result.Value])); + } else { // (*TopLevelAwait) 10. Otherwise, + // a. Assert: module.[[Status]] is "evaluated" and module.[[EvaluationError]] is undefined. + Assert(module.Status === 'evaluated' && module.EvaluationError === Value.undefined); + // b. If module.[[AsyncEvaluating]] is false, then + if (module.AsyncEvaluating === Value.false) { + // i. Perform ! Call(capability.[[Resolve]], undefined, «undefined»). + X(Call(capability.Resolve, Value.undefined, [Value.undefined])); + } + // c. Assert: stack is empty. + Assert(stack.length === 0); + } + // 9. Return undefined. + // (*TopLevelAwait) 11. Return capability.[[Promise]]. + return capability.Promise; + } + + mark(m) { + super.mark(m); + m(this.EvaluationError); + } +} + +// 15.2.1.17 #sec-source-text-module-records +export class SourceTextModuleRecord extends CyclicModuleRecord { + constructor(init) { + super(init); + + this.ImportMeta = init.ImportMeta; + this.ECMAScriptCode = init.ECMAScriptCode; + this.Context = init.Context; + this.ImportEntries = init.ImportEntries; + this.LocalExportEntries = init.LocalExportEntries; + this.IndirectExportEntries = init.IndirectExportEntries; + this.StarExportEntries = init.StarExportEntries; + } + + // #sec-getexportednames + GetExportedNames(exportStarSet) { + // 1. If exportStarSet is not present, set exportStarSet to a new empty List. + if (!exportStarSet) { + exportStarSet = []; + } + // 2. Assert: exportStarSet is a List of Source Text Module Records. + Assert(Array.isArray(exportStarSet) && exportStarSet.every((e) => e instanceof SourceTextModuleRecord)); + // 3. Let module be this Source Text Module Record. + const module = this; + // 4. If exportStarSet contains module, then + if (exportStarSet.includes(module)) { + // a. Assert: We've reached the starting point of an import * circularity. + // b. Return a new empty List. + return []; + } + // 5. Append module to exportStarSet. + exportStarSet.push(module); + // 6. Let exportedNames be a new empty List. + const exportedNames = []; + // 7. For each ExportEntry Record e in module.[[LocalExportEntries]], do + for (const e of module.LocalExportEntries) { + // a. Assert: module provides the direct binding for this export. + // b. Append e.[[ExportName]] to exportedNames. + exportedNames.push(e.ExportName); + } + // 8. For each ExportEntry Record e in module.[[IndirectExportEntries]], do + for (const e of module.IndirectExportEntries) { + // a. Assert: module imports a specific binding for this export. + // b. Append e.[[ExportName]] to exportedNames. + exportedNames.push(e.ExportName); + } + // 9. For each ExportEntry Record e in module.[[StarExportEntries]], do + for (const e of module.StarExportEntries) { + // a. Let requestedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]). + const requestedModule = Q(HostResolveImportedModule(module, e.ModuleRequest)); + // b. Let starNames be ? requestedModule.GetExportedNames(exportStarSet). + const starNames = Q(requestedModule.GetExportedNames(exportStarSet)); + // c. For each element n of starNames, do + for (const n of starNames) { + // i. If SameValue(n, "default") is false, then + if (SameValue(n, new Value('default')) === Value.false) { + // 1. If n is not an element of exportedNames, then + if (!exportedNames.includes(n)) { + // a. Append n to exportedNames. + exportedNames.push(n); + } + } + } + } + // 10. Return exportedNames. + return exportedNames; + } + + // #sec-resolveexport + ResolveExport(exportName, resolveSet) { + // 1. If resolveSet is not present, set resolveSet to a new empty List. + if (!resolveSet) { + resolveSet = []; + } + // 2. Assert: resolveSet is a List of Record { [[Module]], [[ExportName]] }. + Assert(Array.isArray(resolveSet) && resolveSet.every((e) => 'Module' in e && 'ExportName' in e)); + // 3. Let module be this Source Text Module Record. + const module = this; + // 4. For each Record { [[Module]], [[ExportName]] } r in resolveSet, do + for (const r of resolveSet) { + // a. If module and r.[[Module]] are the same Module Record and SameValue(exportName, r.[[ExportName]]) is true, then + if (module === r.Module && SameValue(exportName, r.ExportName) === Value.true) { + // i. Assert: This is a circular import request. + // ii. Return null. + return null; + } + } + // 5. Append the Record { [[Module]]: module, [[ExportName]]: exportName } to resolveSet. + resolveSet.push({ Module: module, ExportName: exportName }); + // 6. For each ExportEntry Record e in module.[[LocalExportEntries]], do + for (const e of module.LocalExportEntries) { + // a. If SameValue(exportName, e.[[ExportName]]) is true, then + if (SameValue(exportName, e.ExportName) === Value.true) { + // i. Assert: module provides the direct binding for this export. + // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + return new ResolvedBindingRecord({ + Module: module, + BindingName: e.LocalName, + }); + } + } + // 7. For each ExportEntry Record e in module.[[IndirectExportEntries]], do + for (const e of module.IndirectExportEntries) { + // a. If SameValue(exportName, e.[[ExportName]]) is true, then + if (SameValue(exportName, e.ExportName) === Value.true) { + // i. Let importedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]). + const importedModule = Q(HostResolveImportedModule(module, e.ModuleRequest)); + // ii. If e.[[ImportName]] is ~star~, then + if (e.ImportName === 'star') { + // 1. Assert: module does not provide the direct binding for this export + // 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: ~namespace~ }. + return new ResolvedBindingRecord({ + Module: importedModule, + BindingName: 'namespace', + }); + } else { // iii. Else, + // 1. Assert: module imports a specific binding for this export. + // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). + return importedModule.ResolveExport(e.ImportName, resolveSet); + } + } + } + // 8. If SameValue(exportName, "default") is true, then + if (SameValue(exportName, new Value('default')) === Value.true) { + // a. Assert: A default export was not explicitly defined by this module. + // b. Return null. + return null; + // c. NOTE: A default export cannot be provided by an export * or export * from "mod" declaration. + } + // 9. Let starResolution be null. + let starResolution = null; + // 10. For each ExportEntry Record e in module.[[StarExportEntries]], do + for (const e of module.StarExportEntries) { + // a. Let importedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]). + const importedModule = Q(HostResolveImportedModule(module, e.ModuleRequest)); + // b. Let resolution be ? importedModule.ResolveExport(exportName, resolveSet). + const resolution = Q(importedModule.ResolveExport(exportName, resolveSet)); + // c. If resolution is "ambiguous", return "ambiguous". + if (resolution === 'ambiguous') { + return 'ambiguous'; + } + // d. If resolution is not null, then + if (resolution !== null) { + // a. Assert: resolution is a ResolvedBinding Record. + Assert(resolution instanceof ResolvedBindingRecord); + // b. If starResolution is null, set starResolution to resolution. + if (starResolution === null) { + starResolution = resolution; + } else { // c. Else, + // 1. Assert: There is more than one * import that includes the requested name. + // 2. If resolution.[[Module]] and starResolution.[[Module]] are not the same Module Record or SameValue(resolution.[[BindingName]], starResolution.[[BindingName]]) is false, return "ambiguous". + if (resolution.Module !== starResolution.Module || SameValue(resolution.BindingName, starResolution.BindingName) === Value.false) { + return 'ambiguous'; + } + } + } + } + // 11. Return starResolution. + return starResolution; + } + + // #sec-source-text-module-record-initialize-environment + InitializeEnvironment() { + // 1. Let module be this Source Text Module Record. + const module = this; + // 2. For each ExportEntry Record e in module.[[IndirectExportEntries]], do + for (const e of module.IndirectExportEntries) { + // a. Let resolution be ? module.ResolveExport(e.[[ExportName]]). + const resolution = Q(module.ResolveExport(e.ExportName)); + // b. If resolution is null or "ambiguous", throw a SyntaxError exception. + if (resolution === null || resolution === 'ambiguous') { + return surroundingAgent.Throw( + 'SyntaxError', + 'ResolutionNullOrAmbiguous', + resolution, + e.ExportName, + module, + ); + } + // c. Assert: resolution is a ResolvedBinding Record. + Assert(resolution instanceof ResolvedBindingRecord); + } + // 3. Assert: All named exports from module are resolvable. + // 4. Let realm be module.[[Realm]]. + const realm = module.Realm; + // 5. Assert: realm is not undefined. + Assert(realm !== Value.undefined); + // 6. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). + const env = NewModuleEnvironment(realm.GlobalEnv); + // 7. Set module.[[Environment]] to env. + module.Environment = env; + // 8. For each ImportEntry Record in in module.[[ImportEntries]], do + for (const ie of module.ImportEntries) { + // a. Let importedModule be ! HostResolveImportedModule(module, in.[[ModuleRequest]]). + const importedModule = X(HostResolveImportedModule(module, ie.ModuleRequest)); + // b. NOTE: The above call cannot fail because imported module requests are a subset of module.[[RequestedModules]], and these have been resolved earlier in this algorithm. + // c. If in.[[ImportName]] is ~star~, then + if (ie.ImportName === 'star') { + // i. Let namespace be ? GetModuleNamespace(importedModule). + const namespace = Q(GetModuleNamespace(importedModule)); + // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + X(env.CreateImmutableBinding(ie.LocalName, Value.true)); + // iii. Call env.InitializeBinding(in.[[LocalName]], namespace). + env.InitializeBinding(ie.LocalName, namespace); + } else { // d. Else, + // i. Let resolution be ? importedModule.ResolveExport(in.[[ImportName]]). + const resolution = Q(importedModule.ResolveExport(ie.ImportName)); + // ii. If resolution is null or "ambiguous", throw a SyntaxError exception. + if (resolution === null || resolution === 'ambiguous') { + return surroundingAgent.Throw( + 'SyntaxError', + 'ResolutionNullOrAmbiguous', + resolution, + ie.ImportName, + importedModule, + ); + } + // iii. If resolution.[[BindingName]] is ~namespace~, then + if (resolution.BindingName === 'namespace') { + // 1. Let namespace be ? GetModuleNamespace(resolution.[[Module]]). + const namespace = Q(GetModuleNamespace(resolution.Module)); + // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + X(env.CreateImmutableBinding(ie.LocalName, Value.true)); + // 3. Call env.InitializeBinding(in.[[LocalName]], namespace). + env.InitializeBinding(ie.LocalName, namespace); + } else { // iv. Else, + // 1. Call env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]). + env.CreateImportBinding(ie.LocalName, resolution.Module, resolution.BindingName); + } + } + } + // 9. Let moduleContext be a new ECMAScript code execution context. + const moduleContext = new ExecutionContext(); + // 10. Set the Function of moduleContext to null. + moduleContext.Function = Value.null; + // 11. Assert: module.[[Realm]] is not undefined. + Assert(module.Realm !== Value.undefined); + // 12. Set the Realm of moduleContext to module.[[Realm]]. + moduleContext.Realm = module.Realm; + // 13. Set the ScriptOrModule of moduleContext to module. + moduleContext.ScriptOrModule = module; + // 14. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + moduleContext.VariableEnvironment = module.Environment; + // 15. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + moduleContext.LexicalEnvironment = module.Environment; + // 16. Set module.[[Context]] to moduleContext. + module.Context = moduleContext; + // 17. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + surroundingAgent.executionContextStack.push(moduleContext); + // 18. Let code be module.[[ECMAScriptCode]]. + const code = module.ECMAScriptCode; + // 19. Let varDeclarations be the VarScopedDeclarations of code. + const varDeclarations = VarScopedDeclarations(code); + // 20. Let declaredVarNames be a new empty List. + const declaredVarNames = new ValueSet(); + // 21. For each element d in varDeclarations, do + for (const d of varDeclarations) { + // a. For each element dn of the BoundNames of d, do + for (const dn of BoundNames(d)) { + // i. If dn is not an element of declaredVarNames, then + if (!declaredVarNames.has(dn)) { + // 1. Perform ! env.CreateMutableBinding(dn, false). + X(env.CreateMutableBinding(dn, Value.false)); + // 2. Call env.InitializeBinding(dn, undefined). + env.InitializeBinding(dn, Value.undefined); + // 3. Append dn to declaredVarNames. + declaredVarNames.add(dn); + } + } + } + // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. + const lexDeclarations = LexicallyScopedDeclarations(code); + // 23. For each element d in lexDeclarations, do + for (const d of lexDeclarations) { + // a. For each element dn of the BoundNames of d, do + for (const dn of BoundNames(d)) { + // i. If IsConstantDeclaration of d is true, then + if (IsConstantDeclaration(d)) { + // 1. Perform ! env.CreateImmutableBinding(dn, true). + Q(env.CreateImmutableBinding(dn, Value.true)); + } else { // ii. Else, + // 1. Perform ! env.CreateMutableBinding(dn, false). + Q(env.CreateMutableBinding(dn, Value.false)); + } + // iii. If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then + if (d.type === 'FunctionDeclaration' + || d.type === 'GeneratorDeclaration' + || d.type === 'AsyncFunctionDeclaration' + || d.type === 'AsyncGeneratorDeclaration') { + // 1. Let fo be InstantiateFunctionObject of d with argument env. + const fo = InstantiateFunctionObject(d, env); + // 2. Call env.InitializeBinding(dn, fo). + env.InitializeBinding(dn, fo); + } + } + } + // 24. Remove moduleContext from the execution context stack. + surroundingAgent.executionContextStack.pop(moduleContext); + // 25. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + // #sec-source-text-module-record-execute-module + ExecuteModule(capability) { + // 1. Let module be this Source Text Module Record. + const module = this; + // 2. Suspend the currently running execution context. + // 3. Let moduleContext be module.[[Context]]. + const moduleContext = module.Context; + if (module.Async === Value.false) { + Assert(capability === undefined); + // 4. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + surroundingAgent.executionContextStack.push(moduleContext); + // 5. Let result be the result of evaluating module.[[ECMAScriptCode]]. + const result = EnsureCompletion(unwind(Evaluate(module.ECMAScriptCode))); + // 6. Suspend moduleContext and remove it from the execution context stack. + // 7. Resume the context that is now on the top of the execution context stack as the running execution context. + surroundingAgent.executionContextStack.pop(moduleContext); + // 8. Return Completion(result). + return Completion(result); + } else { // (*TopLevelAwait) + // a. Assert: capability is a PromiseCapability Record. + Assert(capability instanceof PromiseCapabilityRecord); + // b. Perform ! AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleCxt). + X(AsyncBlockStart(capability, module.ECMAScriptCode, moduleContext)); + // c. Return. + return Value.undefined; + } + } + + mark(m) { + super.mark(m); + m(this.ImportMeta); + m(this.Context); + } +} diff --git a/engine262/src/parse.mjs b/engine262/src/parse.mjs new file mode 100644 index 0000000..2d11928 --- /dev/null +++ b/engine262/src/parse.mjs @@ -0,0 +1,185 @@ +import { Parser } from './parser/Parser.mjs'; +import { RegExpParser } from './parser/RegExpParser.mjs'; +import { surroundingAgent } from './engine.mjs'; +import { SourceTextModuleRecord } from './modules.mjs'; +import { Value } from './value.mjs'; +import { Get, Set } from './abstract-ops/all.mjs'; +import { X } from './completion.mjs'; +import { + ModuleRequests, + ImportEntries, + ExportEntries, + ImportedLocalNames, +} from './static-semantics/all.mjs'; +import { ValueSet } from './helpers.mjs'; + +function handleError(e) { + if (e.name === 'SyntaxError') { + const v = surroundingAgent.Throw('SyntaxError', 'Raw', e.message).Value; + if (e.decoration) { + const stackString = new Value('stack'); + const stack = X(Get(v, stackString)).stringValue(); + const newStackString = `${e.decoration}\n${stack}`; + X(Set(v, stackString, new Value(newStackString), Value.true)); + } + return v; + } else { + throw e; + } +} + +export function wrappedParse(init, f) { + const p = new Parser(init); + + try { + const r = f(p); + if (p.earlyErrors.size > 0) { + return [...p.earlyErrors].map((e) => handleError(e)); + } + return r; + } catch (e) { + return [handleError(e)]; + } +} + +export function ParseScript(sourceText, realm, hostDefined = {}) { + // 1. Assert: sourceText is an ECMAScript source text (see clause 10). + // 2. Parse sourceText using Script as the goal symbol and analyse the parse result for + // any Early Error conditions. If the parse was successful and no early errors were found, + // let body be the resulting parse tree. Otherwise, let body be a List of one or more + // SyntaxError objects representing the parsing errors and/or early errors. Parsing and + // early error detection may be interweaved in an implementation-dependent manner. If more + // than one parsing error or early error is present, the number and ordering of error + // objects in the list is implementation-dependent, but at least one must be present. + const body = wrappedParse({ source: sourceText, specifier: hostDefined.specifier }, (p) => p.parseScript()); + // 3. If body is a List of errors, return body. + if (Array.isArray(body)) { + return body; + } + // 4. Return Script Record { [[Realm]]: realm, [[Environment]]: undefined, [[ECMAScriptCode]]: body, [[HostDefined]]: hostDefined }. + return { + Realm: realm, + Environment: Value.undefined, + ECMAScriptCode: body, + HostDefined: hostDefined, + mark(m) { + m(this.Realm); + m(this.Environment); + }, + }; +} + +export function ParseModule(sourceText, realm, hostDefined = {}) { + // 1. Assert: sourceText is an ECMAScript source text (see clause 10). + // 2. Parse sourceText using Module as the goal symbol and analyse the parse result for + // any Early Error conditions. If the parse was successful and no early errors were found, + // let body be the resulting parse tree. Otherwise, let body be a List of one or more + // SyntaxError objects representing the parsing errors and/or early errors. Parsing and + // early error detection may be interweaved in an implementation-dependent manner. If more + // than one parsing error or early error is present, the number and ordering of error + // objects in the list is implementation-dependent, but at least one must be present. + const body = wrappedParse({ source: sourceText, specifier: hostDefined.specifier }, (p) => p.parseModule()); + // 3. If body is a List of errors, return body. + if (Array.isArray(body)) { + return body; + } + // 4. Let requestedModules be the ModuleRequests of body. + const requestedModules = ModuleRequests(body); + // 5. Let importEntries be ImportEntries of body. + const importEntries = ImportEntries(body); + // 6. Let importedBoundNames be ImportedLocalNames(importEntries). + const importedBoundNames = new ValueSet(ImportedLocalNames(importEntries)); + // 7. Let indirectExportEntries be a new empty List. + const indirectExportEntries = []; + // 8. Let localExportEntries be a new empty List. + const localExportEntries = []; + // 9. Let starExportEntries be a new empty List. + const starExportEntries = []; + // 10. Let exportEntries be ExportEntries of body. + const exportEntries = ExportEntries(body); + // 11. For each ExportEntry Record ee in exportEntries, do + for (const ee of exportEntries) { + // a. If ee.[[ModuleRequest]] is null, then + if (ee.ModuleRequest === Value.null) { + // i. If ee.[[LocalName]] is not an element of importedBoundNames, then + if (!importedBoundNames.has(ee.LocalName)) { + // 1. Append ee to localExportEntries. + localExportEntries.push(ee); + } else { // ii. Else, + // 1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]]. + const ie = importEntries.find((e) => e.LocalName.stringValue() === ee.LocalName.stringValue()); + // 2. If ie.[[ImportName]] is ~star~, then + if (ie.ImportName === 'star') { + // a. NOTE: This is a re-export of an imported module namespace object. + // b. Append ee to localExportEntries. + localExportEntries.push(ee); + } else { // 3. Else, + // a. NOTE: This is a re-export of a single name. + // b. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries. + indirectExportEntries.push({ + ModuleRequest: ie.ModuleRequest, + ImportName: ie.ImportName, + LocalName: Value.null, + ExportName: ee.ExportName, + }); + } + } + } else if (ee.ImportName && ee.ImportName === 'star' && ee.ExportName === Value.null) { // b. Else if ee.[[ImportName]] is ~star~ and ee.[[ExportName]] is null, then + // i. Append ee to starExportEntries. + starExportEntries.push(ee); + } else { // c. Else, + // i. Append ee to indirectExportEntries. + indirectExportEntries.push(ee); + } + } + // 12. Return Source Text Module Record { [[Realm]]: realm, [[Environment]]: undefined, [[Namespace]]: undefined, [[Status]]: unlinked, [[EvaluationError]]: undefined, [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[Context]]: empty, [[ImportMeta]]: empty, [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries, [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSIndex]]: undefined, [[DFSAncestorIndex]]: undefined }. + return new (hostDefined.SourceTextModuleRecord || SourceTextModuleRecord)({ + Realm: realm, + Environment: Value.undefined, + Namespace: Value.undefined, + Status: 'unlinked', + EvaluationError: Value.undefined, + HostDefined: hostDefined, + ECMAScriptCode: body, + Context: undefined, + ImportMeta: undefined, + RequestedModules: requestedModules, + ImportEntries: importEntries, + LocalExportEntries: localExportEntries, + IndirectExportEntries: indirectExportEntries, + StarExportEntries: starExportEntries, + DFSIndex: Value.undefined, + DFSAncestorIndex: Value.undefined, + + Async: body.hasTopLevelAwait ? Value.true : Value.false, + AsyncEvaluating: Value.false, + TopLevelCapability: Value.undefined, + AsyncParentModules: Value.undefined, + PendingAsyncDependencies: Value.undefined, + }); +} + +// #sec-parsepattern +export function ParsePattern(patternText, u) { + const parse = (flags) => { + const p = new RegExpParser(patternText); + return p.scope(flags, () => p.parsePattern()); + }; + try { + // 1. If u is true, then + if (u) { + // a. Parse patternText using the grammars in 21.2.1. The goal symbol for the parse is Pattern[+U, +N]. + return parse({ U: true, N: true }); + } else { // 2. Else + // a. Parse patternText using the grammars in 21.2.1. The goal symbol for the parse is Pattern[~U, ~N]. + // If the result of parsing contains a GroupName, reparse with the goal symbol Pattern[~U, +N] and use this result instead. + const pattern = parse({ U: false, N: false }); + if (pattern.groupSpecifiers.size > 0) { + return parse({ U: false, N: true }); + } + return pattern; + } + } catch (e) { + return [handleError(e)]; + } +} diff --git a/engine262/src/parser/BaseParser.mjs b/engine262/src/parser/BaseParser.mjs new file mode 100644 index 0000000..9e0ea40 --- /dev/null +++ b/engine262/src/parser/BaseParser.mjs @@ -0,0 +1,3 @@ +import { Lexer } from './Lexer.mjs'; + +export class BaseParser extends Lexer {} diff --git a/engine262/src/parser/ExpressionParser.mjs b/engine262/src/parser/ExpressionParser.mjs new file mode 100644 index 0000000..e0dd6db --- /dev/null +++ b/engine262/src/parser/ExpressionParser.mjs @@ -0,0 +1,1235 @@ +import { TV, PropName } from '../static-semantics/all.mjs'; +import { + Token, TokenPrecedence, + isPropertyOrCall, + isMember, + isKeyword, + isKeywordRaw, + isReservedWordStrict, +} from './tokens.mjs'; +import { isLineTerminator } from './Lexer.mjs'; +import { FunctionParser, FunctionKind } from './FunctionParser.mjs'; +import { RegExpParser } from './RegExpParser.mjs'; + +export class ExpressionParser extends FunctionParser { + // Expression : + // AssignmentExpression + // Expression `,` AssignmentExpression + parseExpression() { + const node = this.startNode(); + const AssignmentExpression = this.parseAssignmentExpression(); + if (this.eat(Token.COMMA)) { + node.ExpressionList = [AssignmentExpression]; + do { + node.ExpressionList.push(this.parseAssignmentExpression()); + } while (this.eat(Token.COMMA)); + return this.finishNode(node, 'CommaOperator'); + } + return AssignmentExpression; + } + + // AssignmentExpression : + // ConditionalExpression + // [+Yield] YieldExpression + // ArrowFunction + // AsyncArrowFunction + // LeftHandSideExpression `=` AssignmentExpression + // LeftHandSideExpression AssignmentOperator AssignmentExpression + // LeftHandSideExpression LogicalAssignmentOperator AssignmentExpression + // + // AssignmentOperator : one of + // *= /= %= += -= <<= >>= >>>= &= ^= |= **= + // + // LogicalAssignmentOperator : one of + // &&= ||= ??= + parseAssignmentExpression() { + if (this.test(Token.YIELD) && this.scope.hasYield()) { + return this.parseYieldExpression(); + } + const node = this.startNode(); + + this.scope.pushAssignmentInfo('assign'); + const left = this.parseConditionalExpression(); + const assignmentInfo = this.scope.popAssignmentInfo(); + + if (left.type === 'IdentifierReference') { + // `async` [no LineTerminator here] IdentifierReference [no LineTerminator here] `=>` + if (left.name === 'async' + && this.test(Token.IDENTIFIER) + && !this.peek().hadLineTerminatorBefore + && this.testAhead(Token.ARROW) + && !this.peekAhead().hadLineTerminatorBefore) { + assignmentInfo.clear(); + return this.parseArrowFunction(node, { + Arguments: [this.parseIdentifierReference()], + }, FunctionKind.ASYNC); + } + // IdentifierReference [no LineTerminator here] `=>` + if (this.test(Token.ARROW) && !this.peek().hadLineTerminatorBefore) { + assignmentInfo.clear(); + return this.parseArrowFunction(node, { Arguments: [left] }, FunctionKind.NORMAL); + } + } + + // `async` [no LineTerminator here] Arguments [no LineTerminator here] `=>` + if (left.type === 'CallExpression' && left.arrowInfo && this.test(Token.ARROW) + && !this.peek().hadLineTerminatorBefore) { + const last = left.Arguments[left.Arguments.length - 1]; + if (!left.arrowInfo.trailingComma || (last && last.type !== 'AssignmentRestElement')) { + assignmentInfo.clear(); + return this.parseArrowFunction(node, left, FunctionKind.ASYNC); + } + } + + if (left.type === 'CoverParenthesizedExpressionAndArrowParameterList') { + assignmentInfo.clear(); + return this.parseArrowFunction(node, left, FunctionKind.NORMAL); + } + + switch (this.peek().type) { + case Token.ASSIGN: + case Token.ASSIGN_MUL: + case Token.ASSIGN_DIV: + case Token.ASSIGN_MOD: + case Token.ASSIGN_ADD: + case Token.ASSIGN_SUB: + case Token.ASSIGN_SHL: + case Token.ASSIGN_SAR: + case Token.ASSIGN_SHR: + case Token.ASSIGN_BIT_AND: + case Token.ASSIGN_BIT_XOR: + case Token.ASSIGN_BIT_OR: + case Token.ASSIGN_EXP: + case Token.ASSIGN_AND: + case Token.ASSIGN_OR: + case Token.ASSIGN_NULLISH: + assignmentInfo.clear(); + this.validateAssignmentTarget(left); + node.LeftHandSideExpression = left; + node.AssignmentOperator = this.next().value; + node.AssignmentExpression = this.parseAssignmentExpression(); + return this.finishNode(node, 'AssignmentExpression'); + default: + return left; + } + } + + validateAssignmentTarget(node) { + switch (node.type) { + case 'IdentifierReference': + if (this.isStrictMode() && (node.name === 'eval' || node.name === 'arguments')) { + break; + } + return; + case 'CoverInitializedName': + this.validateAssignmentTarget(node.IdentifierReference); + return; + case 'MemberExpression': + return; + case 'SuperProperty': + return; + case 'ParenthesizedExpression': + this.validateAssignmentTarget(node.Expression); + return; + case 'ArrayLiteral': + node.ElementList.forEach((p, i) => { + if (p.type === 'SpreadElement' && (i !== node.ElementList.length - 1 || node.hasTrailingComma)) { + this.raiseEarly('InvalidAssignmentTarget', p); + } + this.validateAssignmentTarget(p); + }); + return; + case 'ObjectLiteral': + node.PropertyDefinitionList.forEach((p, i) => { + if (p.type === 'PropertyDefinition' && !p.PropertyName + && i !== node.PropertyDefinitionList.length - 1) { + this.raiseEarly('InvalidAssignmentTarget', p); + } + this.validateAssignmentTarget(p); + }); + return; + case 'PropertyDefinition': + this.validateAssignmentTarget(node.AssignmentExpression); + return; + case 'AssignmentExpression': + this.validateAssignmentTarget(node.LeftHandSideExpression); + return; + case 'Elision': + return; + case 'SpreadElement': + if (node.AssignmentExpression.type === 'AssignmentExpression') { + break; + } + this.validateAssignmentTarget(node.AssignmentExpression); + return; + default: + break; + } + this.raiseEarly('InvalidAssignmentTarget', node); + } + + // YieldExpression : + // `yield` + // `yield` [no LineTerminator here] AssignmentExpression + // `yield` [no LineTerminator here] `*` AssignmentExpression + parseYieldExpression() { + if (this.scope.inParameters()) { + this.raiseEarly('YieldInFormalParameters'); + } + const node = this.startNode(); + this.expect(Token.YIELD); + if (this.peek().hadLineTerminatorBefore) { + node.hasStar = false; + node.AssignmentExpression = null; + } else { + node.hasStar = this.eat(Token.MUL); + if (node.hasStar) { + node.AssignmentExpression = this.parseAssignmentExpression(); + } else { + switch (this.peek().type) { + case Token.EOS: + case Token.SEMICOLON: + case Token.RBRACE: + case Token.RBRACK: + case Token.RPAREN: + case Token.COLON: + case Token.COMMA: + case Token.IN: + node.AssignmentExpression = null; + break; + default: + node.AssignmentExpression = this.parseAssignmentExpression(); + } + } + } + if (this.scope.arrowInfoStack.length > 0) { + this.scope.arrowInfoStack[this.scope.arrowInfoStack.length - 1].yieldExpressions.push(node); + } + return this.finishNode(node, 'YieldExpression'); + } + + // ConditionalExpression : + // ShortCircuitExpression + // ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression + parseConditionalExpression() { + const node = this.startNode(); + const ShortCircuitExpression = this.parseShortCircuitExpression(); + if (this.eat(Token.CONDITIONAL)) { + node.ShortCircuitExpression = ShortCircuitExpression; + this.scope.with({ in: true }, () => { + node.AssignmentExpression_a = this.parseAssignmentExpression(); + }); + this.expect(Token.COLON); + node.AssignmentExpression_b = this.parseAssignmentExpression(); + return this.finishNode(node, 'ConditionalExpression'); + } + return ShortCircuitExpression; + } + + // ShortCircuitExpression : + // LogicalORExpression + // CoalesceExpression + // + // CoalesceExpression : + // CoalesceExpressionHead `??` BitwiseORExpression + // + // CoalesceExpressionHead : + // CoalesceExpression + // BitwiseORExpression + parseShortCircuitExpression() { + // Start parse at BIT_OR, right above AND/OR/NULLISH + const expression = this.parseBinaryExpression(TokenPrecedence[Token.BIT_OR]); + switch (this.peek().type) { + case Token.AND: + case Token.OR: + // Drop into normal binary chain starting at OR + return this.parseBinaryExpression(TokenPrecedence[Token.OR], expression); + case Token.NULLISH: { + let x = expression; + while (this.eat(Token.NULLISH)) { + const node = this.startNode(); + node.CoalesceExpressionHead = x; + node.BitwiseORExpression = this.parseBinaryExpression(TokenPrecedence[Token.BIT_OR]); + x = this.finishNode(node, 'CoalesceExpression'); + } + return x; + } + default: + return expression; + } + } + + parseBinaryExpression(precedence, x = this.parseUnaryExpression()) { + let p = TokenPrecedence[this.peek().type]; + if (p >= precedence) { + do { + while (TokenPrecedence[this.peek().type] === p) { + const left = x; + const node = this.startNode(left); + if (this.peek().type === Token.IN && !this.scope.hasIn()) { + return left; + } + const op = this.next(); + const nextP = op.type === Token.EXP ? p : p + 1; + const right = this.parseBinaryExpression(nextP); + let name; + switch (op.type) { + case Token.EXP: + name = 'ExponentiationExpression'; + node.UpdateExpression = left; + node.ExponentiationExpression = right; + break; + case Token.MUL: + case Token.DIV: + case Token.MOD: + name = 'MultiplicativeExpression'; + node.MultiplicativeExpression = left; + node.MultiplicativeOperator = op.value; + node.ExponentiationExpression = right; + break; + case Token.ADD: + case Token.SUB: + name = 'AdditiveExpression'; + node.AdditiveExpression = left; + node.MultiplicativeExpression = right; + node.operator = op.value; + break; + case Token.SHL: + case Token.SAR: + case Token.SHR: + name = 'ShiftExpression'; + node.ShiftExpression = left; + node.AdditiveExpression = right; + node.operator = op.value; + break; + case Token.LT: + case Token.GT: + case Token.LTE: + case Token.GTE: + case Token.INSTANCEOF: + case Token.IN: + name = 'RelationalExpression'; + node.RelationalExpression = left; + node.ShiftExpression = right; + node.operator = op.value; + break; + case Token.EQ: + case Token.NE: + case Token.EQ_STRICT: + case Token.NE_STRICT: + name = 'EqualityExpression'; + node.EqualityExpression = left; + node.RelationalExpression = right; + node.operator = op.value; + break; + case Token.BIT_AND: + name = 'BitwiseANDExpression'; + node.A = left; + node.operator = op.value; + node.B = right; + break; + case Token.BIT_XOR: + name = 'BitwiseXORExpression'; + node.A = left; + node.operator = op.value; + node.B = right; + break; + case Token.BIT_OR: + name = 'BitwiseORExpression'; + node.A = left; + node.operator = op.value; + node.B = right; + break; + case Token.AND: + name = 'LogicalANDExpression'; + node.LogicalANDExpression = left; + node.BitwiseORExpression = right; + break; + case Token.OR: + name = 'LogicalORExpression'; + node.LogicalORExpression = left; + node.LogicalANDExpression = right; + break; + default: + this.unexpected(op); + } + x = this.finishNode(node, name); + } + p -= 1; + } while (p >= precedence); + } + return x; + } + + // UnaryExpression : + // UpdateExpression + // `delete` UnaryExpression + // `void` UnaryExpression + // `typeof` UnaryExpression + // `+` UnaryExpression + // `-` UnaryExpression + // `~` UnaryExpression + // `!` UnaryExpression + // [+Await] AwaitExpression + parseUnaryExpression() { + return this.scope.with({ in: true }, () => { + if (this.test(Token.AWAIT) && this.scope.hasAwait()) { + return this.parseAwaitExpression(); + } + const node = this.startNode(); + switch (this.peek().type) { + case Token.DELETE: + case Token.VOID: + case Token.TYPEOF: + case Token.ADD: + case Token.SUB: + case Token.BIT_NOT: + case Token.NOT: + node.operator = this.next().value; + node.UnaryExpression = this.parseUnaryExpression(); + if (this.isStrictMode() + && node.operator === 'delete' + && node.UnaryExpression.type === 'IdentifierReference') { + this.raiseEarly('DeleteIdentifier', node.UnaryExpression); + } + if (this.test(Token.EXP)) { + this.unexpected(); + } + return this.finishNode(node, 'UnaryExpression'); + default: + return this.parseUpdateExpression(); + } + }); + } + + // AwaitExpression : `await` UnaryExpression + parseAwaitExpression() { + if (this.scope.inParameters()) { + this.raiseEarly('AwaitInFormalParameters'); + } + const node = this.startNode(); + this.expect(Token.AWAIT); + node.UnaryExpression = this.parseUnaryExpression(); + if (this.scope.arrowInfoStack.length > 0) { + this.scope.arrowInfoStack[this.scope.arrowInfoStack.length - 1].awaitExpressions.push(node); + } else if (!this.scope.hasReturn()) { + this.state.hasTopLevelAwait = true; + } + return this.finishNode(node, 'AwaitExpression'); + } + + // UpdateExpression : + // LeftHandSideExpression + // LeftHandSideExpression [no LineTerminator here] `++` + // LeftHandSideExpression [no LineTerminator here] `--` + // `++` UnaryExpression + // `--` UnaryExpression + parseUpdateExpression() { + if (this.test(Token.INC) || this.test(Token.DEC)) { + const node = this.startNode(); + node.operator = this.next().value; + node.LeftHandSideExpression = null; + node.UnaryExpression = this.parseUnaryExpression(); + this.validateAssignmentTarget(node.UnaryExpression); + return this.finishNode(node, 'UpdateExpression'); + } + const argument = this.parseLeftHandSideExpression(); + if (!this.peek().hadLineTerminatorBefore) { + if (this.test(Token.INC) || this.test(Token.DEC)) { + this.validateAssignmentTarget(argument); + const node = this.startNode(); + node.operator = this.next().value; + node.LeftHandSideExpression = argument; + node.UnaryExpression = null; + return this.finishNode(node, 'UpdateExpression'); + } + } + return argument; + } + + // LeftHandSideExpression + parseLeftHandSideExpression(allowCalls = true) { + let result; + switch (this.peek().type) { + case Token.NEW: + result = this.parseNewExpression(); + break; + case Token.SUPER: { + const node = this.startNode(); + this.next(); + if (this.test(Token.LPAREN)) { + if (!this.scope.hasSuperCall()) { + this.raiseEarly('InvalidSuperCall'); + } + node.Arguments = this.parseArguments().Arguments; + result = this.finishNode(node, 'SuperCall'); + } else { + if (!this.scope.hasSuperProperty()) { + this.raiseEarly('InvalidSuperProperty'); + } + if (this.eat(Token.LBRACK)) { + node.Expression = this.parseExpression(); + this.expect(Token.RBRACK); + node.IdentifierName = null; + } else { + this.expect(Token.PERIOD); + node.Expression = null; + node.IdentifierName = this.parseIdentifierName(); + } + result = this.finishNode(node, 'SuperProperty'); + } + break; + } + case Token.IMPORT: { + const node = this.startNode(); + this.next(); + if (this.scope.hasImportMeta() && this.eat(Token.PERIOD)) { + this.expect('meta'); + result = this.finishNode(node, 'ImportMeta'); + } else { + if (!allowCalls) { + this.unexpected(); + } + this.expect(Token.LPAREN); + node.AssignmentExpression = this.parseAssignmentExpression(); + this.expect(Token.RPAREN); + result = this.finishNode(node, 'ImportCall'); + } + break; + } + default: + result = this.parsePrimaryExpression(); + break; + } + + const check = allowCalls ? isPropertyOrCall : isMember; + while (check(this.peek().type)) { + const node = this.startNode(result); + switch (this.peek().type) { + case Token.LBRACK: { + this.next(); + node.MemberExpression = result; + node.IdentifierName = null; + node.Expression = this.parseExpression(); + result = this.finishNode(node, 'MemberExpression'); + this.expect(Token.RBRACK); + break; + } + case Token.PERIOD: + this.next(); + node.MemberExpression = result; + node.IdentifierName = this.parseIdentifierName(); + node.Expression = null; + result = this.finishNode(node, 'MemberExpression'); + break; + case Token.LPAREN: { + // `async` [no LineTerminator here] `(` + const couldBeArrow = this.matches('async', this.currentToken) + && result.type === 'IdentifierReference' + && !this.peek().hadLineTerminatorBefore; + if (couldBeArrow) { + this.scope.pushArrowInfo(true); + } + const { Arguments, trailingComma } = this.parseArguments(); + node.CallExpression = result; + node.Arguments = Arguments; + if (couldBeArrow) { + node.arrowInfo = this.scope.popArrowInfo(); + node.arrowInfo.trailingComma = trailingComma; + } + result = this.finishNode(node, 'CallExpression'); + break; + } + case Token.OPTIONAL: + node.MemberExpression = result; + node.OptionalChain = this.parseOptionalChain(); + result = this.finishNode(node, 'OptionalExpression'); + break; + case Token.TEMPLATE: + node.MemberExpression = result; + node.TemplateLiteral = this.parseTemplateLiteral(true); + result = this.finishNode(node, 'TaggedTemplateExpression'); + break; + default: + this.unexpected(); + } + } + return result; + } + + // OptionalChain + parseOptionalChain() { + this.expect(Token.OPTIONAL); + let base = this.startNode(); + base.OptionalChain = null; + if (this.test(Token.LPAREN)) { + base.Arguments = this.parseArguments().Arguments; + } else if (this.eat(Token.LBRACK)) { + base.Expression = this.parseExpression(); + this.expect(Token.RBRACK); + } else if (this.test(Token.TEMPLATE)) { + this.raise('TemplateInOptionalChain'); + } else { + base.IdentifierName = this.parseIdentifierName(); + } + base = this.finishNode(base, 'OptionalChain'); + + while (true) { + const node = this.startNode(); + if (this.test(Token.LPAREN)) { + node.OptionalChain = base; + node.Arguments = this.parseArguments().Arguments; + base = this.finishNode(node, 'OptionalChain'); + } else if (this.eat(Token.LBRACK)) { + node.OptionalChain = base; + node.Expression = this.parseExpression(); + this.expect(Token.RBRACK); + base = this.finishNode(node, 'OptionalChain'); + } else if (this.test(Token.TEMPLATE)) { + this.raise('TemplateInOptionalChain'); + } else if (this.eat(Token.PERIOD)) { + node.OptionalChain = base; + node.IdentifierName = this.parseIdentifierName(); + base = this.finishNode(node, 'OptionalChain'); + } else { + return base; + } + } + } + + // NewExpression + parseNewExpression() { + const node = this.startNode(); + this.expect(Token.NEW); + if (this.scope.hasNewTarget() && this.eat(Token.PERIOD)) { + this.expect('target'); + return this.finishNode(node, 'NewTarget'); + } + node.MemberExpression = this.parseLeftHandSideExpression(false); + if (this.test(Token.LPAREN)) { + node.Arguments = this.parseArguments().Arguments; + } else { + node.Arguments = null; + } + return this.finishNode(node, 'NewExpression'); + } + + // PrimaryExpression : + // ... + parsePrimaryExpression() { + switch (this.peek().type) { + case Token.IDENTIFIER: + case Token.ESCAPED_KEYWORD: + case Token.YIELD: + case Token.AWAIT: + // `async` [no LineTerminator here] `function` + if (this.test('async') && this.testAhead(Token.FUNCTION) + && !this.peekAhead().hadLineTerminatorBefore) { + return this.parseFunctionExpression(FunctionKind.ASYNC); + } + return this.parseIdentifierReference(); + case Token.THIS: { + const node = this.startNode(); + this.next(); + return this.finishNode(node, 'ThisExpression'); + } + case Token.NUMBER: + case Token.BIGINT: + return this.parseNumericLiteral(); + case Token.STRING: + return this.parseStringLiteral(); + case Token.NULL: { + const node = this.startNode(); + this.next(); + return this.finishNode(node, 'NullLiteral'); + } + case Token.TRUE: + case Token.FALSE: + return this.parseBooleanLiteral(); + case Token.LBRACK: + return this.parseArrayLiteral(); + case Token.LBRACE: + return this.parseObjectLiteral(); + case Token.FUNCTION: + return this.parseFunctionExpression(FunctionKind.NORMAL); + case Token.CLASS: + return this.parseClassExpression(); + case Token.TEMPLATE: + return this.parseTemplateLiteral(); + case Token.DIV: + case Token.ASSIGN_DIV: + return this.parseRegularExpressionLiteral(); + case Token.LPAREN: + return this.parseCoverParenthesizedExpressionAndArrowParameterList(); + default: + return this.unexpected(); + } + } + + // NumericLiteral + parseNumericLiteral() { + const node = this.startNode(); + if (!this.test(Token.NUMBER) && !this.test(Token.BIGINT)) { + this.unexpected(); + } + node.value = this.next().value; + return this.finishNode(node, 'NumericLiteral'); + } + + // StringLiteral + parseStringLiteral() { + const node = this.startNode(); + if (!this.test(Token.STRING)) { + this.unexpected(); + } + node.value = this.next().value; + return this.finishNode(node, 'StringLiteral'); + } + + // BooleanLiteral : + // `true` + // `false` + parseBooleanLiteral() { + const node = this.startNode(); + switch (this.peek().type) { + case Token.TRUE: + this.next(); + node.value = true; + break; + case Token.FALSE: + this.next(); + node.value = false; + break; + default: + this.unexpected(); + } + return this.finishNode(node, 'BooleanLiteral'); + } + + // ArrayLiteral : + // `[` `]` + // `[` Elision `]` + // `[` ElementList `]` + // `[` ElementList `,` `]` + // `[` ElementList `,` Elision `]` + parseArrayLiteral() { + const node = this.startNode(); + this.expect(Token.LBRACK); + node.ElementList = []; + node.hasTrailingComma = false; + while (true) { + while (this.test(Token.COMMA)) { + const elision = this.startNode(); + this.next(); + node.ElementList.push(this.finishNode(elision, 'Elision')); + } + if (this.eat(Token.RBRACK)) { + break; + } + if (this.test(Token.ELLIPSIS)) { + const spread = this.startNode(); + this.next(); + spread.AssignmentExpression = this.parseAssignmentExpression(); + node.ElementList.push(this.finishNode(spread, 'SpreadElement')); + } else { + node.ElementList.push(this.parseAssignmentExpression()); + } + if (this.eat(Token.RBRACK)) { + node.hasTrailingComma = false; + break; + } + node.hasTrailingComma = true; + this.expect(Token.COMMA); + } + return this.finishNode(node, 'ArrayLiteral'); + } + + // ObjectLiteral : + // `{` `}` + // `{` PropertyDefinitionList `}` + // `{` PropertyDefinitionList `,` `}` + parseObjectLiteral() { + const node = this.startNode(); + this.expect(Token.LBRACE); + node.PropertyDefinitionList = []; + while (true) { + if (this.eat(Token.RBRACE)) { + break; + } + node.PropertyDefinitionList.push(this.parsePropertyDefinition()); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'ObjectLiteral'); + } + + parsePropertyDefinition() { + return this.parseBracketedDefinition('property'); + } + + parseFunctionExpression(kind) { + return this.parseFunction(true, kind); + } + + parseArguments() { + this.expect(Token.LPAREN); + if (this.eat(Token.RPAREN)) { + return { Arguments: [], trailingComma: false }; + } + const Arguments = []; + let trailingComma = false; + while (true) { + const node = this.startNode(); + if (this.eat(Token.ELLIPSIS)) { + node.AssignmentExpression = this.parseAssignmentExpression(); + Arguments.push(this.finishNode(node, 'AssignmentRestElement')); + } else { + Arguments.push(this.parseAssignmentExpression()); + } + if (this.eat(Token.RPAREN)) { + break; + } + this.expect(Token.COMMA); + if (this.eat(Token.RPAREN)) { + trailingComma = true; + break; + } + } + return { Arguments, trailingComma }; + } + + // #sec-class-definitions + // ClassDeclaration : + // `class` BindingIdentifier ClassTail + // [+Default] `class` ClassTail + // + // ClassExpression : + // `class` BindingIdentifier? ClassTail + parseClass(isExpression) { + const node = this.startNode(); + + this.expect(Token.CLASS); + + this.scope.with({ strict: true }, () => { + if (!this.test(Token.LBRACE) && !this.test(Token.EXTENDS)) { + node.BindingIdentifier = this.parseBindingIdentifier(); + if (!isExpression) { + this.scope.declare(node.BindingIdentifier, 'lexical'); + } + } else if (isExpression === false && !this.scope.isDefault()) { + this.raise('ClassMissingBindingIdentifier'); + } else { + node.BindingIdentifier = null; + } + node.ClassTail = this.scope.with({ default: false }, () => this.parseClassTail()); + }); + + return this.finishNode(node, isExpression ? 'ClassExpression' : 'ClassDeclaration'); + } + + // ClassTail : ClassHeritage? `{` ClassBody? `}` + // ClassHeritage : `extends` LeftHandSideExpression + // ClassBody : ClassElementList + parseClassTail() { + const node = this.startNode(); + + if (this.eat(Token.EXTENDS)) { + node.ClassHeritage = this.parseLeftHandSideExpression(); + } else { + node.ClassHeritage = null; + } + + this.expect(Token.LBRACE); + if (this.eat(Token.RBRACE)) { + node.ClassBody = null; + } else { + this.scope.with({ superCall: !!node.ClassHeritage }, () => { + node.ClassBody = []; + let hasConstructor = false; + while (!this.eat(Token.RBRACE)) { + const m = this.parseClassElement(); + node.ClassBody.push(m); + + const name = PropName(m.MethodDefinition); + const isActualConstructor = !m.static + && !!m.MethodDefinition.UniqueFormalParameters + && m.MethodDefinition.type === 'MethodDefinition' + && name === 'constructor'; + if (isActualConstructor) { + if (hasConstructor) { + this.raiseEarly('DuplicateConstructor', m); + } else { + hasConstructor = true; + } + } + if ((m.static && name === 'prototype') + || (!m.static && !isActualConstructor && name === 'constructor')) { + this.raiseEarly('InvalidMethodName', m, name); + } + } + }); + } + + return this.finishNode(node, 'ClassTail'); + } + + // ClassElement : + // `static` MethodDefinition + // MethodDefinition + parseClassElement() { + const node = this.startNode(); + node.static = this.eat('static'); + node.MethodDefinition = this.parseMethodDefinition(node.static); + while (this.eat(Token.SEMICOLON)) { + // nothing + } + return this.finishNode(node, 'ClassElement'); + } + + parseMethodDefinition(isStatic) { + return this.parseBracketedDefinition('method', isStatic); + } + + parseClassExpression() { + return this.parseClass(true); + } + + parseTemplateLiteral(tagged = false) { + const node = this.startNode(); + node.TemplateSpanList = []; + node.ExpressionList = []; + let buffer = ''; + while (true) { + if (this.position >= this.source.length) { + this.raise('UnterminatedTemplate', this.position); + } + const c = this.source[this.position]; + switch (c) { + case '`': + this.position += 1; + node.TemplateSpanList.push(buffer); + this.next(); + if (!tagged) { + node.TemplateSpanList.forEach((s) => { + if (TV(s) === undefined) { + this.raise('InvalidTemplateEscape'); + } + }); + } + return this.finishNode(node, 'TemplateLiteral'); + case '$': + this.position += 1; + if (this.source[this.position] === '{') { + this.position += 1; + node.TemplateSpanList.push(buffer); + buffer = ''; + this.next(); + node.ExpressionList.push(this.parseExpression()); + break; + } + buffer += c; + break; + default: { + if (c === '\\') { + buffer += c; + this.position += 1; + } + const l = this.source[this.position]; + this.position += 1; + if (isLineTerminator(l)) { + if (l === '\r' && this.source[this.position] === '\n') { + this.position += 1; + } + if (l === '\u{2028}' || l === '\u{2029}') { + buffer += l; + } else { + buffer += '\n'; + } + this.line += 1; + this.columnOffset = this.position; + } else { + buffer += l; + } + break; + } + } + } + } + + // RegularExpressionLiteral : + // `/` RegularExpressionBody `/` RegularExpressionFlags + parseRegularExpressionLiteral() { + const node = this.startNode(); + this.scanRegularExpressionBody(); + node.RegularExpressionBody = this.scannedValue; + this.scanRegularExpressionFlags(); + node.RegularExpressionFlags = this.scannedValue; + try { + const parse = (flags) => { + const p = new RegExpParser(node.RegularExpressionBody); + return p.scope(flags, () => p.parsePattern()); + }; + if (node.RegularExpressionFlags.includes('u')) { + parse({ U: true, N: true }); + } else { + const pattern = parse({ U: false, N: false }); + if (pattern.groupSpecifiers.size > 0) { + parse({ U: false, N: true }); + } + } + } catch (e) { + if (e instanceof SyntaxError) { + this.raise('Raw', node.location.startIndex + e.position + 1, e.message); + } else { + throw e; + } + } + const fakeToken = { + endIndex: this.position - 1, + line: this.line - 1, + column: this.position - this.columnOffset, + }; + this.next(); + this.currentToken = fakeToken; + return this.finishNode(node, 'RegularExpressionLiteral'); + } + + // CoverParenthesizedExpressionAndArrowParameterList : + // `(` Expression `)` + // `(` Expression `,` `)` + // `(` `)` + // `(` `...` BindingIdentifier `)` + // `(` `...` BindingPattern `)` + // `(` Expression `,` `...` BindingIdentifier `)` + // `(` Expression `.` `...` BindingPattern `)` + parseCoverParenthesizedExpressionAndArrowParameterList() { + const node = this.startNode(); + const commaOp = this.startNode(); + this.expect(Token.LPAREN); + if (this.test(Token.RPAREN)) { + if (!this.testAhead(Token.ARROW) || this.peekAhead().hadLineTerminatorBefore) { + this.unexpected(); + } + this.next(); + node.Arguments = []; + return this.finishNode(node, 'CoverParenthesizedExpressionAndArrowParameterList'); + } + + this.scope.pushArrowInfo(); + this.scope.pushAssignmentInfo('arrow'); + + const expressions = []; + let rparenAfterComma; + while (true) { + if (this.test(Token.ELLIPSIS)) { + const inner = this.startNode(); + this.next(); + switch (this.peek().type) { + case Token.LBRACE: + case Token.LBRACK: + inner.BindingPattern = this.parseBindingPattern(); + break; + default: + inner.BindingIdentifier = this.parseBindingIdentifier(); + break; + } + expressions.push(this.finishNode(inner, 'BindingRestElement')); + this.expect(Token.RPAREN); + break; + } + expressions.push(this.parseAssignmentExpression()); + if (this.eat(Token.COMMA)) { + if (this.eat(Token.RPAREN)) { + rparenAfterComma = this.currentToken; + break; + } + } else { + this.expect(Token.RPAREN); + break; + } + } + + const arrowInfo = this.scope.popArrowInfo(); + const assignmentInfo = this.scope.popAssignmentInfo(); + + // ArrowParameters : + // CoverParenthesizedExpressionAndArrowParameterList + if (this.test(Token.ARROW) && !this.peek().hadLineTerminatorBefore) { + node.Arguments = expressions; + node.arrowInfo = arrowInfo; + assignmentInfo.clear(); + return this.finishNode(node, 'CoverParenthesizedExpressionAndArrowParameterList'); + } + + // ParenthesizedExpression : + // `(` Expression `)` + if (expressions[expressions.length - 1].type === 'BindingRestElement') { + this.unexpected(expressions[expressions.length - 1]); + } + if (rparenAfterComma) { + this.unexpected(rparenAfterComma); + } + if (expressions.length === 1) { + node.Expression = expressions[0]; + } else { + commaOp.ExpressionList = expressions; + node.Expression = this.finishNode(commaOp, 'CommaOperator'); + } + return this.finishNode(node, 'ParenthesizedExpression'); + } + + // PropertyName : + // LiteralPropertyName + // ComputedPropertyName + // LiteralPropertyName : + // IdentifierName + // StringLiteral + // NumericLiteral + // ComputedPropertyName : + // `[` AssignmentExpression `]` + parsePropertyName() { + if (this.test(Token.LBRACK)) { + const node = this.startNode(); + this.next(); + node.ComputedPropertyName = this.parseAssignmentExpression(); + this.expect(Token.RBRACK); + return this.finishNode(node, 'PropertyName'); + } + if (this.test(Token.STRING)) { + return this.parseStringLiteral(); + } + if (this.test(Token.NUMBER) || this.test(Token.BIGINT)) { + return this.parseNumericLiteral(); + } + return this.parseIdentifierName(); + } + + // PropertyDefinition : + // IdentifierReference + // CoverInitializedName + // PropertyName `:` AssignmentExpression + // MethodDefinition + // `...` AssignmentExpression + // MethodDefinition : + // PropertyName `(` UniqueFormalParameters `)` `{` FunctionBody `}` + // GeneratorMethod + // AsyncMethod + // AsyncGeneratorMethod + // `get` PropertyName `(` `)` `{` FunctionBody `}` + // `set` PropertyName `(` PropertySetParameterList `)` `{` FunctionBody `}` + // GeneratorMethod : + // `*` PropertyName `(` UniqueFormalParameters `)` `{` GeneratorBody `}` + // AsyncMethod : + // `async` [no LineTerminator here] PropertyName `(` UniqueFormalParameters `)` `{` AsyncFunctionBody `}` + // AsyncGeneratorMethod : + // `async` [no LineTerminator here] `*` Propertyname `(` UniqueFormalParameters `)` `{` AsyncGeneratorBody `}` + parseBracketedDefinition(type, isStatic = false) { + const node = this.startNode(); + + if (type === 'property' && this.eat(Token.ELLIPSIS)) { + node.PropertyName = null; + node.AssignmentExpression = this.parseAssignmentExpression(); + return this.finishNode(node, 'PropertyDefinition'); + } + + let isGenerator = this.eat(Token.MUL); + let isGetter = false; + let isSetter = false; + let isAsync = false; + if (!isGenerator) { + if (this.test('get')) { + isGetter = true; + } else if (this.test('set')) { + isSetter = true; + } else if (this.test('async') && !this.peekAhead().hadLineTerminatorBefore) { + isAsync = true; + } + } + const firstName = this.parsePropertyName(); + if (!isGenerator && !isGetter && !isSetter) { + isGenerator = this.eat(Token.MUL); + } + const isSpecialMethod = isGenerator || ((isSetter || isGetter || isAsync) && !this.test(Token.LPAREN)); + + if (!isGenerator && type === 'property') { + if (this.eat(Token.COLON)) { + node.PropertyName = firstName; + node.AssignmentExpression = this.parseAssignmentExpression(); + return this.finishNode(node, 'PropertyDefinition'); + } + if (this.scope.assignmentInfoStack.length > 0 && this.test(Token.ASSIGN)) { + node.IdentifierReference = firstName; + node.IdentifierReference.type = 'IdentifierReference'; + node.Initializer = this.parseInitializerOpt(); + this.finishNode(node, 'CoverInitializedName'); + this.scope.registerCoverInitializedName(node); + return node; + } + + if (!isSpecialMethod + && firstName.type === 'IdentifierName' + && !this.test(Token.LPAREN) + && !isKeyword(firstName.name)) { + firstName.type = 'IdentifierReference'; + if (firstName.name === 'await' && (this.isStrictMode() || this.scope.hasAwait())) { + this.raiseEarly('UnexpectedReservedWordStrict', firstName); + } + if (firstName.name === 'yield' && (this.isStrictMode() || this.scope.hasYield())) { + this.raiseEarly('UnexpectedReservedWordStrict', firstName); + } + if (firstName.name !== 'yield' + && firstName.name !== 'await' + && isKeywordRaw(firstName.name)) { + this.raiseEarly('UnexpectedToken', firstName); + } + if (this.isStrictMode() && isReservedWordStrict(firstName.name)) { + this.raiseEarly('UnexpectedReservedWordStrict', firstName); + } + return firstName; + } + } + + node.PropertyName = (isSpecialMethod && (!isGenerator || isAsync)) ? this.parsePropertyName() : firstName; + + this.scope.with({ + lexical: true, + variable: true, + superProperty: true, + await: isAsync, + yield: isGenerator, + }, () => { + if (isSpecialMethod && isGetter) { + this.expect(Token.LPAREN); + this.expect(Token.RPAREN); + node.PropertySetParameterList = null; + node.UniqueFormalParameters = null; + } else if (isSpecialMethod && isSetter) { + this.expect(Token.LPAREN); + node.PropertySetParameterList = [this.parseFormalParameter()]; + this.expect(Token.RPAREN); + node.UniqueFormalParameters = null; + } else { + node.PropertySetParameterList = null; + node.UniqueFormalParameters = this.parseUniqueFormalParameters(); + } + + this.scope.with({ + superCall: !isSpecialMethod + && !isStatic + && (node.PropertyName.name === 'constructor' || node.PropertyName.value === 'constructor') + && this.scope.hasSuperCall(), + }, () => { + const body = this.parseFunctionBody(isAsync, isGenerator, false); + node[`${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}Body`] = body; + if (node.UniqueFormalParameters || node.PropertySetParameterList) { + this.validateFormalParameters(node.UniqueFormalParameters || node.PropertySetParameterList, body, true); + } + }); + }); + + const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : ''}Method${isAsync || isGenerator ? '' : 'Definition'}`; + return this.finishNode(node, name); + } +} diff --git a/engine262/src/parser/FunctionParser.mjs b/engine262/src/parser/FunctionParser.mjs new file mode 100644 index 0000000..233509c --- /dev/null +++ b/engine262/src/parser/FunctionParser.mjs @@ -0,0 +1,322 @@ +import { IsSimpleParameterList } from '../static-semantics/all.mjs'; +import { getDeclarations } from './Scope.mjs'; +import { Token } from './tokens.mjs'; +import { IdentifierParser } from './IdentifierParser.mjs'; + +export const FunctionKind = { + NORMAL: 0, + ASYNC: 1, +}; + +export class FunctionParser extends IdentifierParser { + // FunctionDeclaration : + // `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` + // [+Default] `function` `(` FormalParameters `)` `{` FunctionBody `}` + // FunctionExpression : + // `function` BindingIdentifier? `(` FormalParameters `)` `{` FunctionBody `}` + // GeneratorDeclaration : + // `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` + // [+Default] `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` + // GeneratorExpression : + // `function` BindingIdentifier? `(` FormalParameters `)` `{` GeneratorBody `}` + // AsyncGeneratorDeclaration : + // `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + // [+Default] `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + // AsyncGeneratorExpression : + // `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + // AsyncFunctionDeclaration : + // `async` `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` + // [+Default] `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}` + // Async`FunctionExpression : + // `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncFunctionBody `}` + parseFunction(isExpression, kind) { + const isAsync = kind === FunctionKind.ASYNC; + const node = this.startNode(); + if (isAsync) { + this.expect('async'); + } + this.expect(Token.FUNCTION); + const isGenerator = this.eat(Token.MUL); + if (!this.test(Token.LPAREN)) { + this.scope.with({ + await: isExpression ? false : undefined, + yield: isExpression ? false : undefined, + }, () => { + node.BindingIdentifier = this.parseBindingIdentifier(); + }); + if (!isExpression) { + this.scope.declare(node.BindingIdentifier, 'function'); + } + } else if (isExpression === false && !this.scope.isDefault()) { + this.unexpected(); + } else { + node.BindingIdentifier = null; + } + + this.scope.with({ + default: false, + await: isAsync, + yield: isGenerator, + lexical: true, + variable: true, + variableFunctions: true, + }, () => { + node.FormalParameters = this.parseFormalParameters(); + + const body = this.parseFunctionBody(isAsync, isGenerator, false); + node[body.type] = body; + + if (node.BindingIdentifier) { + if (body.strict && (node.BindingIdentifier.name === 'eval' || node.BindingIdentifier.name === 'arguments')) { + this.raiseEarly('UnexpectedToken', node.BindingIdentifier); + } + if (isExpression) { + if (this.scope.hasYield() && node.BindingIdentifier.name === 'yield') { + this.raiseEarly('UnexpectedToken', node.BindingIdentifier); + } + if (this.scope.hasAwait() && node.BindingIdentifier.name === 'await') { + this.raiseEarly('UnexpectedToken', node.BindingIdentifier); + } + } + } + + this.validateFormalParameters(node.FormalParameters, body); + }); + + const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}${isExpression ? 'Expression' : 'Declaration'}`; + return this.finishNode(node, name); + } + + validateFormalParameters(parameters, body, wantsUnique = false) { + const isStrict = body.strict; + const hasStrictDirective = body.directives && body.directives.includes('use strict'); + if (wantsUnique === false && !IsSimpleParameterList(parameters)) { + wantsUnique = true; + } + + if (hasStrictDirective) { + parameters.forEach((p) => { + if (p.type !== 'SingleNameBinding' || p.Initializer) { + this.raiseEarly('UseStrictNonSimpleParameter', p); + } + }); + } + + const names = new Set(); + getDeclarations(parameters) + .forEach((d) => { + if (isStrict) { + if (d.name === 'arguments' || d.name === 'eval') { + this.raiseEarly('UnexpectedToken', d.node); + } + } + if (isStrict || wantsUnique) { + if (names.has(d.name)) { + this.raiseEarly('AlreadyDeclared', d.node, d.name); + } else { + names.add(d.name); + } + } + }); + } + + convertArrowParameter(node) { + switch (node.type) { + case 'IdentifierReference': { + node.type = 'BindingIdentifier'; + const container = this.startNode(); + container.BindingIdentifier = node; + container.Initializer = null; + this.scope.declare(node, 'parameter'); + return this.finishNode(container, 'SingleNameBinding'); + } + case 'BindingRestElement': + this.scope.declare(node, 'parameter'); + return node; + case 'Elision': + return node; + case 'ArrayLiteral': { + const wrap = this.startNode(); + node.BindingElementList = []; + node.ElementList.forEach((p, i) => { + const c = this.convertArrowParameter(p); + if (c.type === 'BindingRestElement') { + if (i !== node.ElementList.length - 1) { + this.raiseEarly('UnexpectedToken', c); + } + node.BindingRestElement = c; + } else { + node.BindingElementList.push(c); + } + }); + delete node.ElementList; + node.type = 'ArrayBindingPattern'; + wrap.BindingPattern = node; + wrap.Initializer = null; + return this.finishNode(wrap, 'BindingElement'); + } + case 'ObjectLiteral': { + const wrap = this.startNode(); + node.BindingPropertyList = []; + node.PropertyDefinitionList.forEach((p) => { + const c = this.convertArrowParameter(p); + if (c.type === 'BindingRestProperty') { + node.BindingRestProperty = c; + } else { + node.BindingPropertyList.push(c); + } + }); + delete node.PropertyDefinitionList; + node.type = 'ObjectBindingPattern'; + wrap.BindingPattern = node; + wrap.Initializer = null; + return this.finishNode(wrap, 'BindingElement'); + } + case 'AssignmentExpression': { + const result = this.convertArrowParameter(node.LeftHandSideExpression); + result.Initializer = node.AssignmentExpression; + return result; + } + case 'CoverInitializedName': + node.type = 'SingleNameBinding'; + node.BindingIdentifier = node.IdentifierReference; + node.BindingIdentifier.type = 'BindingIdentifier'; + delete node.IdentifierReference; + this.scope.declare(node, 'parameter'); + return node; + case 'PropertyDefinition': + if (node.PropertyName === null) { + node.type = 'BindingRestProperty'; + node.BindingIdentifier = node.AssignmentExpression; + node.BindingIdentifier.type = 'BindingIdentifier'; + } else { + node.type = 'BindingProperty'; + node.BindingElement = this.convertArrowParameter(node.AssignmentExpression); + } + this.scope.declare(node, 'parameter'); + delete node.AssignmentExpression; + return node; + case 'SpreadElement': + case 'AssignmentRestElement': + node.type = 'BindingRestElement'; + if (node.AssignmentExpression.type === 'AssignmentExpression') { + this.raiseEarly('UnexpectedToken', node); + } else if (node.AssignmentExpression.type === 'IdentifierReference') { + node.BindingIdentifier = node.AssignmentExpression; + node.BindingIdentifier.type = 'BindingIdentifier'; + } else { + node.BindingPattern = this.convertArrowParameter(node.AssignmentExpression).BindingPattern; + } + this.scope.declare(node, 'parameter'); + delete node.AssignmentExpression; + return node; + default: + this.raiseEarly('UnexpectedToken', node); + return node; + } + } + + parseArrowFunction(node, { arrowInfo, Arguments }, kind) { + const isAsync = kind === FunctionKind.ASYNC; + this.expect(Token.ARROW); + if (arrowInfo) { + arrowInfo.awaitExpressions.forEach((e) => { + this.raiseEarly('AwaitInFormalParameters', e); + }); + arrowInfo.yieldExpressions.forEach((e) => { + this.raiseEarly('YieldInFormalParameters', e); + }); + if (isAsync) { + arrowInfo.awaitIdentifiers.forEach((e) => { + this.raiseEarly('AwaitInFormalParameters', e); + }); + } + } + this.scope.with({ + default: false, + lexical: true, + variable: true, + }, () => { + this.scope.with({ + parameters: true, + }, () => { + node.ArrowParameters = Arguments.map((p) => this.convertArrowParameter(p)); + }); + const body = this.parseConciseBody(isAsync); + this.validateFormalParameters(node.ArrowParameters, body, true); + node[`${isAsync ? 'Async' : ''}ConciseBody`] = body; + }); + return this.finishNode(node, `${isAsync ? 'Async' : ''}ArrowFunction`); + } + + parseConciseBody(isAsync) { + if (this.test(Token.LBRACE)) { + return this.parseFunctionBody(isAsync, false, true); + } + const asyncBody = this.startNode(); + const exprBody = this.startNode(); + this.scope.with({ await: isAsync }, () => { + exprBody.AssignmentExpression = this.parseAssignmentExpression(); + }); + asyncBody.ExpressionBody = this.finishNode(exprBody, 'ExpressionBody'); + return this.finishNode(asyncBody, `${isAsync ? 'Async' : ''}ConciseBody`); + } + + // FormalParameter : BindingElement + parseFormalParameter() { + return this.parseBindingElement(); + } + + parseFormalParameters() { + this.expect(Token.LPAREN); + if (this.eat(Token.RPAREN)) { + return []; + } + const params = []; + this.scope.with({ parameters: true }, () => { + while (true) { + if (this.test(Token.ELLIPSIS)) { + const element = this.parseBindingRestElement(); + this.scope.declare(element, 'parameter'); + params.push(element); + this.expect(Token.RPAREN); + break; + } else { + const formal = this.parseFormalParameter(); + this.scope.declare(formal, 'parameter'); + params.push(formal); + } + if (this.eat(Token.RPAREN)) { + break; + } + this.expect(Token.COMMA); + if (this.eat(Token.RPAREN)) { + break; + } + } + }); + return params; + } + + parseUniqueFormalParameters() { + return this.parseFormalParameters(); + } + + parseFunctionBody(isAsync, isGenerator, isArrow) { + const node = this.startNode(); + this.expect(Token.LBRACE); + this.scope.with({ + newTarget: isArrow ? undefined : true, + return: true, + await: isAsync, + yield: isGenerator, + label: 'boundary', + }, () => { + node.directives = []; + node.FunctionStatementList = this.parseStatementList(Token.RBRACE, node.directives); + node.strict = node.strict || node.directives.includes('use strict'); + }); + const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}Body`; + return this.finishNode(node, name); + } +} diff --git a/engine262/src/parser/IdentifierParser.mjs b/engine262/src/parser/IdentifierParser.mjs new file mode 100644 index 0000000..ab5d8e5 --- /dev/null +++ b/engine262/src/parser/IdentifierParser.mjs @@ -0,0 +1,138 @@ +import { + Token, + isKeyword, + isReservedWordStrict, + isKeywordRaw, +} from './tokens.mjs'; +import { BaseParser } from './BaseParser.mjs'; + +export class IdentifierParser extends BaseParser { + // IdentifierName + parseIdentifierName() { + const node = this.startNode(); + const p = this.peek(); + if (p.type === Token.IDENTIFIER + || p.type === Token.ESCAPED_KEYWORD + || isKeyword(p.type)) { + node.name = this.next().value; + } else { + this.unexpected(); + } + return this.finishNode(node, 'IdentifierName'); + } + + // BindingIdentifier : + // Identifier + // `yield` + // `await` + parseBindingIdentifier() { + const node = this.startNode(); + const token = this.next(); + switch (token.type) { + case Token.IDENTIFIER: + node.name = token.value; + break; + case Token.ESCAPED_KEYWORD: + node.name = token.value; + break; + case Token.YIELD: + node.name = 'yield'; + break; + case Token.AWAIT: + node.name = 'await'; + for (let i = 0; i < this.scope.arrowInfoStack.length; i += 1) { + const arrowInfo = this.scope.arrowInfoStack[i]; + if (arrowInfo.isAsync) { + arrowInfo.awaitIdentifiers.push(node); + break; + } + } + break; + default: + this.unexpected(token); + } + if (node.name === 'yield' && (this.scope.hasYield() || this.scope.isModule())) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (node.name === 'await' && (this.scope.hasAwait() || this.scope.isModule())) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (this.isStrictMode()) { + if (isReservedWordStrict(node.name)) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (node.name === 'eval' || node.name === 'arguments') { + this.raiseEarly('UnexpectedEvalOrArguments', token); + } + } + if (node.name !== 'yield' + && node.name !== 'await' + && isKeywordRaw(node.name)) { + this.raiseEarly('UnexpectedToken', token); + } + return this.finishNode(node, 'BindingIdentifier'); + } + + // IdentifierReference : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + parseIdentifierReference() { + const node = this.startNode(); + const token = this.next(); + switch (token.type) { + case Token.IDENTIFIER: + node.name = token.value; + break; + case Token.ESCAPED_KEYWORD: + node.name = token.value; + break; + case Token.YIELD: + if (this.scope.hasYield()) { + this.unexpected(token); + } + node.name = 'yield'; + break; + case Token.AWAIT: + if (this.scope.hasAwait()) { + this.unexpected(token); + } + for (let i = 0; i < this.scope.arrowInfoStack.length; i += 1) { + const arrowInfo = this.scope.arrowInfoStack[i]; + if (arrowInfo.isAsync) { + arrowInfo.awaitIdentifiers.push(node); + break; + } + } + node.name = 'await'; + break; + default: + this.unexpected(token); + } + if (node.name === 'yield' && (this.scope.hasYield() || this.scope.isModule())) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (node.name === 'await' && (this.scope.hasAwait() || this.scope.isModule())) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (this.isStrictMode() && isReservedWordStrict(node.name)) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (node.name !== 'yield' + && node.name !== 'await' + && isKeywordRaw(node.name)) { + this.raiseEarly('UnexpectedToken', token); + } + return this.finishNode(node, 'IdentifierReference'); + } + + // LabelIdentifier : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + parseLabelIdentifier() { + const node = this.parseIdentifierReference(); + node.type = 'LabelIdentifier'; + return node; + } +} diff --git a/engine262/src/parser/LanguageParser.mjs b/engine262/src/parser/LanguageParser.mjs new file mode 100644 index 0000000..323a7fc --- /dev/null +++ b/engine262/src/parser/LanguageParser.mjs @@ -0,0 +1,96 @@ +import { StatementParser } from './StatementParser.mjs'; +import { Token } from './tokens.mjs'; + +export class LanguageParser extends StatementParser { + // Script : ScriptBody? + parseScript() { + if (this.feature('hashbang')) { + this.skipHashbangComment(); + } + const node = this.startNode(); + if (this.eat(Token.EOS)) { + node.ScriptBody = null; + } else { + node.ScriptBody = this.parseScriptBody(); + } + return this.finishNode(node, 'Script'); + } + + // ScriptBody : StatementList + parseScriptBody() { + const node = this.startNode(); + this.scope.with({ + in: true, + lexical: true, + variable: true, + variableFunctions: true, + }, () => { + const directives = []; + node.StatementList = this.parseStatementList(Token.EOS, directives); + node.strict = directives.includes('use strict'); + }); + return this.finishNode(node, 'ScriptBody'); + } + + // Module : ModuleBody? + parseModule() { + if (this.feature('hashbang')) { + this.skipHashbangComment(); + } + return this.scope.with({ + module: true, + strict: true, + in: true, + importMeta: true, + await: this.feature('top-level-await'), + lexical: true, + variable: true, + }, () => { + const node = this.startNode(); + if (this.eat(Token.EOS)) { + node.ModuleBody = null; + } else { + node.ModuleBody = this.parseModuleBody(); + } + this.scope.undefinedExports.forEach((importNode, name) => { + this.raiseEarly('ModuleUndefinedExport', importNode, name); + }); + node.hasTopLevelAwait = this.state.hasTopLevelAwait; + return this.finishNode(node, 'Module'); + }); + } + + // ModuleBody : + // ModuleItemList + parseModuleBody() { + const node = this.startNode(); + node.ModuleItemList = this.parseModuleItemList(); + return this.finishNode(node, 'ModuleBody'); + } + + // ModuleItemList : + // ModuleItem + // ModuleItemList ModuleItem + // + // ModuleItem : + // ImportDeclaration + // ExportDeclaration + // StatementListItem + parseModuleItemList() { + const moduleItemList = []; + while (!this.eat(Token.EOS)) { + switch (this.peek().type) { + case Token.IMPORT: + moduleItemList.push(this.parseImportDeclaration()); + break; + case Token.EXPORT: + moduleItemList.push(this.parseExportDeclaration()); + break; + default: + moduleItemList.push(this.parseStatementListItem()); + break; + } + } + return moduleItemList; + } +} diff --git a/engine262/src/parser/Lexer.mjs b/engine262/src/parser/Lexer.mjs new file mode 100644 index 0000000..1999f13 --- /dev/null +++ b/engine262/src/parser/Lexer.mjs @@ -0,0 +1,896 @@ +import isUnicodeIDStartRegex from 'unicode-13.0.0/Binary_Property/ID_Start/regex'; +import isUnicodeIDContinueRegex from 'unicode-13.0.0/Binary_Property/ID_Continue/regex'; +import isSpaceSeparatorRegex from 'unicode-13.0.0/General_Category/Space_Separator/regex'; +import { surroundingAgent } from '../engine.mjs'; +import { UTF16SurrogatePairToCodePoint } from '../static-semantics/all.mjs'; +import { + RawTokens, + Token, + TokenNames, + KeywordLookup, + isKeywordRaw, +} from './tokens.mjs'; + +const isUnicodeIDStart = (c) => c && isUnicodeIDStartRegex.test(c); +const isUnicodeIDContinue = (c) => c && isUnicodeIDContinueRegex.test(c); +export const isDecimalDigit = (c) => c && /\d/u.test(c); +export const isHexDigit = (c) => c && /[\da-f]/ui.test(c); +const isOctalDigit = (c) => c && /[0-7]/u.test(c); +const isBinaryDigit = (c) => (c === '0' || c === '1'); +export const isWhitespace = (c) => c && (/[\u0009\u000B\u000C\u0020\u00A0\uFEFF]/u.test(c) || isSpaceSeparatorRegex.test(c)); // eslint-disable-line no-control-regex +export const isLineTerminator = (c) => c && /[\r\n\u2028\u2029]/u.test(c); +const isRegularExpressionFlagPart = (c) => c && (isUnicodeIDContinue(c) || c === '$'); +export const isIdentifierStart = (c) => SingleCharTokens[c] === Token.IDENTIFIER || isUnicodeIDStart(c); +export const isIdentifierPart = (c) => SingleCharTokens[c] === Token.IDENTIFIER || c === '\u{200C}' || c === '\u{200D}' || isUnicodeIDContinue(c); +export const isLeadingSurrogate = (cp) => cp >= 0xD800 && cp <= 0xDBFF; +export const isTrailingSurrogate = (cp) => cp >= 0xDC00 && cp <= 0xDFFF; + +const SingleCharTokens = { + '__proto__': null, + '0': Token.NUMBER, + '1': Token.NUMBER, + '2': Token.NUMBER, + '3': Token.NUMBER, + '4': Token.NUMBER, + '5': Token.NUMBER, + '6': Token.NUMBER, + '7': Token.NUMBER, + '8': Token.NUMBER, + '9': Token.NUMBER, + 'a': Token.IDENTIFIER, + 'b': Token.IDENTIFIER, + 'c': Token.IDENTIFIER, + 'd': Token.IDENTIFIER, + 'e': Token.IDENTIFIER, + 'f': Token.IDENTIFIER, + 'g': Token.IDENTIFIER, + 'h': Token.IDENTIFIER, + 'i': Token.IDENTIFIER, + 'j': Token.IDENTIFIER, + 'k': Token.IDENTIFIER, + 'l': Token.IDENTIFIER, + 'm': Token.IDENTIFIER, + 'n': Token.IDENTIFIER, + 'o': Token.IDENTIFIER, + 'p': Token.IDENTIFIER, + 'q': Token.IDENTIFIER, + 'r': Token.IDENTIFIER, + 's': Token.IDENTIFIER, + 't': Token.IDENTIFIER, + 'u': Token.IDENTIFIER, + 'v': Token.IDENTIFIER, + 'w': Token.IDENTIFIER, + 'x': Token.IDENTIFIER, + 'y': Token.IDENTIFIER, + 'z': Token.IDENTIFIER, + 'A': Token.IDENTIFIER, + 'B': Token.IDENTIFIER, + 'C': Token.IDENTIFIER, + 'D': Token.IDENTIFIER, + 'E': Token.IDENTIFIER, + 'F': Token.IDENTIFIER, + 'G': Token.IDENTIFIER, + 'H': Token.IDENTIFIER, + 'I': Token.IDENTIFIER, + 'J': Token.IDENTIFIER, + 'K': Token.IDENTIFIER, + 'L': Token.IDENTIFIER, + 'M': Token.IDENTIFIER, + 'N': Token.IDENTIFIER, + 'O': Token.IDENTIFIER, + 'P': Token.IDENTIFIER, + 'Q': Token.IDENTIFIER, + 'R': Token.IDENTIFIER, + 'S': Token.IDENTIFIER, + 'T': Token.IDENTIFIER, + 'U': Token.IDENTIFIER, + 'V': Token.IDENTIFIER, + 'W': Token.IDENTIFIER, + 'X': Token.IDENTIFIER, + 'Y': Token.IDENTIFIER, + 'Z': Token.IDENTIFIER, + '$': Token.IDENTIFIER, + '_': Token.IDENTIFIER, + '\\': Token.IDENTIFIER, + '.': Token.PERIOD, + ',': Token.COMMA, + ':': Token.COLON, + ';': Token.SEMICOLON, + '%': Token.MOD, + '~': Token.BIT_NOT, + '!': Token.NOT, + '+': Token.ADD, + '-': Token.SUB, + '*': Token.MUL, + '<': Token.LT, + '>': Token.GT, + '=': Token.ASSIGN, + '?': Token.CONDITIONAL, + '[': Token.LBRACK, + ']': Token.RBRACK, + '(': Token.LPAREN, + ')': Token.RPAREN, + '/': Token.DIV, + '^': Token.BIT_XOR, + '`': Token.TEMPLATE, + '{': Token.LBRACE, + '}': Token.RBRACE, + '&': Token.BIT_AND, + '|': Token.BIT_OR, + '"': Token.STRING, + '\'': Token.STRING, +}; + +export class Lexer { + constructor() { + this.currentToken = undefined; + this.peekToken = undefined; + this.peekAheadToken = undefined; + + this.position = 0; + this.line = 1; + this.columnOffset = 0; + this.scannedValue = undefined; + this.lineTerminatorBeforeNextToken = false; + this.positionForNextToken = 0; + this.lineForNextToken = 0; + this.columnForNextToken = 0; + } + + advance() { + this.lineTerminatorBeforeNextToken = false; + const type = this.nextToken(); + return { + type, + startIndex: this.positionForNextToken, + endIndex: this.position, + line: this.lineForNextToken, + column: this.columnForNextToken, + hadLineTerminatorBefore: this.lineTerminatorBeforeNextToken, + name: TokenNames[type], + value: ( + type === Token.IDENTIFIER + || type === Token.NUMBER + || type === Token.BIGINT + || type === Token.STRING + || type === Token.ESCAPED_KEYWORD + ) ? this.scannedValue : RawTokens[type][1], + }; + } + + next() { + this.currentToken = this.peekToken; + if (this.peekAheadToken !== undefined) { + this.peekToken = this.peekAheadToken; + this.peekAheadToken = undefined; + } else { + this.peekToken = this.advance(); + } + return this.currentToken; + } + + peek() { + if (this.peekToken === undefined) { + this.next(); + } + return this.peekToken; + } + + peekAhead() { + if (this.peekAheadToken === undefined) { + this.peek(); + this.peekAheadToken = this.advance(); + } + return this.peekAheadToken; + } + + matches(token, peek) { + if (typeof token === 'string') { + if (peek.type === Token.IDENTIFIER && peek.value === token) { + const escapeIndex = this.source.slice(peek.startIndex, peek.endIndex).indexOf('\\'); + if (escapeIndex !== -1) { + return false; + } + return true; + } else { + return false; + } + } + return peek.type === token; + } + + test(token) { + return this.matches(token, this.peek()); + } + + testAhead(token) { + return this.matches(token, this.peekAhead()); + } + + eat(token) { + if (this.test(token)) { + this.next(); + return true; + } + return false; + } + + expect(token) { + if (this.test(token)) { + return this.next(); + } + return this.unexpected(); + } + + skipSpace() { + loop: // eslint-disable-line no-labels + while (this.position < this.source.length) { + const c = this.source[this.position]; + switch (c) { + case ' ': + case '\t': + this.position += 1; + break; + case '/': + switch (this.source[this.position + 1]) { + case '/': + this.skipLineComment(); + break; + case '*': + this.skipBlockComment(); + break; + default: + break loop; // eslint-disable-line no-labels + } + break; + default: + if (isWhitespace(c)) { + this.position += 1; + } else if (isLineTerminator(c)) { + this.position += 1; + if (c === '\r' && this.source[this.position] === '\n') { + this.position += 1; + } + this.line += 1; + this.columnOffset = this.position; + this.lineTerminatorBeforeNextToken = true; + break; + } else { + break loop; // eslint-disable-line no-labels + } + break; + } + } + } + + skipHashbangComment() { + if (this.position === 0 + && this.source[0] === '#' + && this.source[1] === '!') { + this.skipLineComment(); + } + } + + skipLineComment() { + while (this.position < this.source.length) { + const c = this.source[this.position]; + this.position += 1; + if (isLineTerminator(c)) { + if (c === '\r' && this.source[this.position] === '\n') { + this.position += 1; + } + this.line += 1; + this.columnOffset = this.position; + this.lineTerminatorBeforeNextToken = true; + break; + } + } + } + + skipBlockComment() { + const end = this.source.indexOf('*/', this.position + 2); + if (end === -1) { + this.raise('UnterminatedComment', this.position); + } + this.position += 2; + for (const match of this.source.slice(this.position, end).matchAll(/\r\n?|[\n\u2028\u2029]/ug)) { + this.position = match.index; + this.line += 1; + this.columnOffset = this.position; + this.lineTerminatorBeforeNextToken = true; + } + this.position = end + 2; + } + + nextToken() { + this.skipSpace(); + + // set token location info after skipping space + this.positionForNextToken = this.position; + this.lineForNextToken = this.line; + this.columnForNextToken = this.position - this.columnOffset + 1; + + if (this.position >= this.source.length) { + return Token.EOS; + } + const c = this.source[this.position]; + this.position += 1; + const c1 = this.source[this.position]; + if (c.charCodeAt(0) <= 127) { + const single = SingleCharTokens[c]; + switch (single) { + case Token.LPAREN: + case Token.RPAREN: + case Token.LBRACE: + case Token.RBRACE: + case Token.LBRACK: + case Token.RBRACK: + case Token.COLON: + case Token.SEMICOLON: + case Token.COMMA: + case Token.BIT_NOT: + case Token.TEMPLATE: + return single; + + case Token.CONDITIONAL: + // ? ?. ?? ??= + if (c1 === '.' && !isDecimalDigit(this.source[this.position + 1])) { + this.position += 1; + return Token.OPTIONAL; + } + if (c1 === '?') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_NULLISH; + } + return Token.NULLISH; + } + return Token.CONDITIONAL; + + case Token.LT: + // < <= << <<= + if (c1 === '=') { + this.position += 1; + return Token.LTE; + } + if (c1 === '<') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_SHL; + } + return Token.SHL; + } + return Token.LT; + + case Token.GT: + // > >= >> >>= >>> >>>= + if (c1 === '=') { + this.position += 1; + return Token.GTE; + } + if (c1 === '>') { + this.position += 1; + if (this.source[this.position] === '>') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_SHR; + } + return Token.SHR; + } + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_SAR; + } + return Token.SAR; + } + return Token.GT; + + case Token.ASSIGN: + // = == === => + if (c1 === '=') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.EQ_STRICT; + } + return Token.EQ; + } + if (c1 === '>') { + this.position += 1; + return Token.ARROW; + } + return Token.ASSIGN; + + case Token.NOT: + // ! != !== + if (c1 === '=') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.NE_STRICT; + } + return Token.NE; + } + return Token.NOT; + + case Token.ADD: + // + ++ += + if (c1 === '+') { + this.position += 1; + return Token.INC; + } + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_ADD; + } + return Token.ADD; + + case Token.SUB: + // - -- -= + if (c1 === '-') { + this.position += 1; + return Token.DEC; + } + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_SUB; + } + return Token.SUB; + + case Token.MUL: + // * *= ** **= + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_MUL; + } + if (c1 === '*') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_EXP; + } + return Token.EXP; + } + return Token.MUL; + + case Token.MOD: + // % %= + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_MOD; + } + return Token.MOD; + + case Token.DIV: + // / /= + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_DIV; + } + return Token.DIV; + + case Token.BIT_AND: + // & && &= &&= + if (c1 === '&') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_AND; + } + return Token.AND; + } + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_BIT_AND; + } + return Token.BIT_AND; + + case Token.BIT_OR: + // | || |= + if (c1 === '|') { + this.position += 1; + if (this.source[this.position] === '=') { + this.position += 1; + return Token.ASSIGN_OR; + } + return Token.OR; + } + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_BIT_OR; + } + return Token.BIT_OR; + + case Token.BIT_XOR: + // ^ ^= + if (c1 === '=') { + this.position += 1; + return Token.ASSIGN_BIT_XOR; + } + return Token.BIT_XOR; + + case Token.PERIOD: + // . ... NUMBER + if (isDecimalDigit(c1)) { + this.position -= 1; + return this.scanNumber(); + } + if (c1 === '.') { + if (this.source[this.position + 1] === '.') { + this.position += 2; + return Token.ELLIPSIS; + } + } + return Token.PERIOD; + + case Token.STRING: + return this.scanString(c); + + case Token.NUMBER: + this.position -= 1; + return this.scanNumber(); + + case Token.IDENTIFIER: + this.position -= 1; + return this.scanIdentifierOrKeyword(); + + default: + this.unexpected(single); + break; + } + } + + this.position -= 1; + + if (isLeadingSurrogate(c.charCodeAt(0)) || isIdentifierStart(c)) { + return this.scanIdentifierOrKeyword(); + } + + return this.unexpected(this.position); + } + + scanNumber() { + const separators = surroundingAgent.feature('numeric-separators'); + const start = this.position; + let base = 10; + let check = isDecimalDigit; + if (this.source[this.position] === '0') { + this.scannedValue = 0; + this.position += 1; + switch (this.source[this.position]) { + case 'x': + case 'X': + base = 16; + break; + case 'o': + case 'O': + base = 8; + break; + case 'b': + case 'B': + base = 2; + break; + case '.': + case 'e': + case 'E': + break; + case 'n': + this.position += 1; + this.scannedValue = 0n; + return Token.BIGINT; + default: + return Token.NUMBER; + } + check = { + 16: isHexDigit, + 10: isDecimalDigit, + 8: isOctalDigit, + 2: isBinaryDigit, + }[base]; + if (base !== 10) { + if (!check(this.source[this.position + 1])) { + return Token.NUMBER; + } + this.position += 1; + } + } + while (this.position < this.source.length) { + const c = this.source[this.position]; + if (check(c) || (separators && c === '_')) { + this.position += 1; + if (separators && c === '_') { + if (!check(this.source[this.position])) { + this.unexpected(this.position); + } + } + } else { + break; + } + } + if (this.source[this.position] === 'n') { + const buffer = this.source.slice(start, this.position).replace(/_/g, ''); + this.position += 1; + this.scannedValue = BigInt(buffer); + return Token.BIGINT; + } + if (base === 10 && this.source[this.position] === '.') { + this.position += 1; + if (separators && this.source[this.position] === '_') { + this.unexpected(this.position); + } + while (this.position < this.source.length) { + const c = this.source[this.position]; + if (isDecimalDigit(c) || (separators && c === '_')) { + this.position += 1; + if (separators && c === '_') { + if (!isDecimalDigit(this.source[this.position])) { + this.unexpected(this.position); + } + } + } else { + break; + } + } + } + if (base === 10 && (this.source[this.position] === 'E' || this.source[this.position] === 'e')) { + this.position += 1; + if (this.source[this.position] === '-' || this.source[this.position] === '+') { + this.position += 1; + } + while (this.position < this.source.length) { + const c = this.source[this.position]; + if (isDecimalDigit(c) || (separators && c === '_')) { + this.position += 1; + if (separators && c === '_') { + if (!isDecimalDigit(this.source[this.position])) { + this.unexpected(this.position); + } + } + } else { + break; + } + } + } + if (isIdentifierStart(this.source[this.position])) { + this.unexpected(this.position); + } + const buffer = this.source + .slice(base === 10 ? start : start + 2, this.position) + .replace(/_/g, ''); + this.scannedValue = base === 10 + ? Number.parseFloat(buffer, base) + : Number.parseInt(buffer, base); + return Token.NUMBER; + } + + scanString(char) { + let buffer = ''; + while (true) { + if (this.position >= this.source.length) { + this.raise('UnterminatedString', this.position); + } + const c = this.source[this.position]; + if (c === char) { + this.position += 1; + break; + } + if (c === '\r' || c === '\n') { + this.raise('UnterminatedString', this.position); + } + this.position += 1; + if (c === '\\') { + const l = this.source[this.position]; + if (isLineTerminator(l)) { + this.position += 1; + if (l === '\r' && this.source[this.position] === '\n') { + this.position += 1; + } + this.line += 1; + this.columnOffset = this.position; + this.lineTerminatorBeforeNextToken = true; + } else { + buffer += this.scanEscapeSequence(); + } + } else { + buffer += c; + } + } + this.scannedValue = buffer; + return Token.STRING; + } + + scanEscapeSequence() { + const c = this.source[this.position]; + switch (c) { + case 'b': + this.position += 1; + return '\b'; + case 't': + this.position += 1; + return '\t'; + case 'n': + this.position += 1; + return '\n'; + case 'v': + this.position += 1; + return '\v'; + case 'f': + this.position += 1; + return '\f'; + case 'r': + this.position += 1; + return '\r'; + case 'x': + this.position += 1; + return String.fromCodePoint(this.scanHex(2)); + case 'u': + this.position += 1; + return String.fromCodePoint(this.scanCodePoint()); + default: + if (c === '0' && !isDecimalDigit(this.source[this.position + 1])) { + this.position += 1; + return '\u{0000}'; + } else if (this.isStrictMode() && isDecimalDigit(c)) { + this.raise('IllegalOctalEscape', this.position); + } + this.position += 1; + return c; + } + } + + scanCodePoint() { + if (this.source[this.position] === '{') { + const end = this.source.indexOf('}', this.position); + this.position += 1; + const code = this.scanHex(end - this.position); + this.position += 1; + if (code > 0x10FFFF) { + this.raise('InvalidCodePoint', this.position); + } + return code; + } + return this.scanHex(4); + } + + scanHex(length) { + if (length === 0) { + this.raise('InvalidCodePoint', this.position); + } + let n = 0; + for (let i = 0; i < length; i += 1) { + const c = this.source[this.position]; + if (isHexDigit(c)) { + this.position += 1; + n = (n << 4) | Number.parseInt(c, 16); + } else { + this.unexpected(this.position); + } + } + return n; + } + + scanIdentifierOrKeyword() { + let buffer = ''; + let escapeIndex = -1; + let check = isIdentifierStart; + while (this.position < this.source.length) { + const c = this.source[this.position]; + const code = c.charCodeAt(0); + if (c === '\\') { + if (escapeIndex === -1) { + escapeIndex = this.position; + } + this.position += 1; + if (this.source[this.position] !== 'u') { + this.raise('InvalidUnicodeEscape', this.position); + } + this.position += 1; + const raw = String.fromCodePoint(this.scanCodePoint()); + if (!check(raw)) { + this.raise('InvalidUnicodeEscape', this.position); + } + buffer += raw; + } else if (isLeadingSurrogate(code)) { + const lowSurrogate = this.source.charCodeAt(this.position + 1); + if (!isTrailingSurrogate(lowSurrogate)) { + this.raise('InvalidUnicodeEscape', this.position); + } + const codePoint = UTF16SurrogatePairToCodePoint(code, lowSurrogate); + const raw = String.fromCodePoint(codePoint); + if (!check(raw)) { + this.raise('InvalidUnicodeEscape', this.position); + } + this.position += 2; + buffer += raw; + } else if (check(c)) { + buffer += c; + this.position += 1; + } else { + break; + } + check = isIdentifierPart; + } + if (isKeywordRaw(buffer)) { + if (escapeIndex !== -1) { + this.scannedValue = buffer; + return Token.ESCAPED_KEYWORD; + } + return KeywordLookup[buffer]; + } else { + this.scannedValue = buffer; + return Token.IDENTIFIER; + } + } + + scanRegularExpressionBody() { + let inClass = false; + let buffer = this.peek().type === Token.ASSIGN_DIV ? '=' : ''; + while (true) { + if (this.position >= this.source.length) { + this.raise('UnterminatedRegExp', this.position); + } + const c = this.source[this.position]; + switch (c) { + case '[': + inClass = true; + this.position += 1; + buffer += c; + break; + case ']': + if (inClass) { + inClass = false; + } + buffer += c; + this.position += 1; + break; + case '/': + this.position += 1; + if (!inClass) { + this.scannedValue = buffer; + return; + } + buffer += c; + break; + case '\\': + buffer += c; + this.position += 1; + if (isLineTerminator(this.source[this.position])) { + this.raise('UnterminatedRegExp', this.position); + } + buffer += this.source[this.position]; + this.position += 1; + break; + default: + if (isLineTerminator(c)) { + this.raise('UnterminatedRegExp', this.position); + } + this.position += 1; + buffer += c; + break; + } + } + } + + scanRegularExpressionFlags() { + let buffer = ''; + while (true) { + if (this.position >= this.source.length) { + this.scannedValue = buffer; + return; + } + const c = this.source[this.position]; + if (isRegularExpressionFlagPart(c) + && 'gimsuy'.includes(c) + && !buffer.includes(c)) { + this.position += 1; + buffer += c; + } else { + this.scannedValue = buffer; + return; + } + } + } +} diff --git a/engine262/src/parser/Parser.mjs b/engine262/src/parser/Parser.mjs new file mode 100644 index 0000000..9932146 --- /dev/null +++ b/engine262/src/parser/Parser.mjs @@ -0,0 +1,153 @@ +import { surroundingAgent } from '../engine.mjs'; +import * as messages from '../messages.mjs'; +import { LanguageParser } from './LanguageParser.mjs'; +import { Token } from './tokens.mjs'; +import { Scope } from './Scope.mjs'; +import { isLineTerminator } from './Lexer.mjs'; + +export class Parser extends LanguageParser { + constructor({ source, specifier }) { + super(); + this.source = source; + this.specifier = specifier; + this.earlyErrors = new Set(); + this.state = { + hasTopLevelAwait: false, + strict: false, + }; + this.scope = new Scope(this); + } + + isStrictMode() { + return this.state.strict; + } + + feature(name) { + // eslint-disable-next-line engine262/valid-feature + return surroundingAgent.feature(name); + } + + startNode(inheritStart = undefined) { + this.peek(); + const node = { + type: undefined, + location: { + startIndex: inheritStart ? inheritStart.location.startIndex : this.peekToken.startIndex, + endIndex: -1, + start: inheritStart ? { ...inheritStart.location.start } : { + line: this.peekToken.line, + column: this.peekToken.column, + }, + end: { + line: -1, + column: -1, + }, + }, + strict: this.state.strict, + sourceText: () => this.source.slice(node.location.startIndex, node.location.endIndex), + }; + return node; + } + + finishNode(node, type) { + node.type = type; + node.location.endIndex = this.currentToken.endIndex; + node.location.end.line = this.currentToken.line; + node.location.end.column = this.currentToken.column; + return node; + } + + createSyntaxError(context = this.peek(), template, templateArgs) { + if (template === 'UnexpectedToken' && context.type === Token.EOS) { + template = 'UnexpectedEOS'; + } + + let startIndex; + let endIndex; + let line; + let column; + if (typeof context === 'number') { + line = this.line; + if (context === this.source.length) { + while (isLineTerminator(this.source[context - 1])) { + line -= 1; + context -= 1; + } + } + startIndex = context; + endIndex = context + 1; + } else if (context.type === Token.EOS) { + line = this.line; + startIndex = context.startIndex; + while (isLineTerminator(this.source[startIndex - 1])) { + line -= 1; + startIndex -= 1; + } + endIndex = startIndex + 1; + } else { + if (context.location) { + context = context.location; + } + ({ + startIndex, + endIndex, + start: { + line, + column, + } = context, + } = context); + } + + /* + * Source looks like: + * + * const a = 1; + * const b 'string string string'; // a string + * const c = 3; | | + * | | | | + * | | startIndex | endIndex | + * | lineStart | lineEnd + * + * Exception looks like: + * + * const b 'string string string'; // a string + * ^^^^^^^^^^^^^^^^^^^^^^ + * SyntaxError: unexpected token + */ + + let lineStart = startIndex; + while (!isLineTerminator(this.source[lineStart - 1]) && this.source[lineStart - 1] !== undefined) { + lineStart -= 1; + } + + let lineEnd = startIndex; + while (!isLineTerminator(this.source[lineEnd]) && this.source[lineEnd] !== undefined) { + lineEnd += 1; + } + + if (column === undefined) { + column = startIndex - lineStart + 1; + } + + const e = new SyntaxError(messages[template](...templateArgs)); + e.decoration = `\ +${this.specifier ? `${this.specifier}:${line}:${column}\n` : ''}${this.source.slice(lineStart, lineEnd)} +${' '.repeat(startIndex - lineStart)}${'^'.repeat(Math.max(endIndex - startIndex, 1))}`; + return e; + } + + raiseEarly(template, context, ...templateArgs) { + const e = this.createSyntaxError(context, template, templateArgs); + this.earlyErrors.add(e); + return e; + } + + raise(template, context, ...templateArgs) { + const e = this.createSyntaxError(context, template, templateArgs); + throw e; + } + + unexpected(...args) { + return this.raise('UnexpectedToken', ...args); + } +} diff --git a/engine262/src/parser/RegExpParser.mjs b/engine262/src/parser/RegExpParser.mjs new file mode 100644 index 0000000..5160fde --- /dev/null +++ b/engine262/src/parser/RegExpParser.mjs @@ -0,0 +1,911 @@ +import { + BinaryUnicodeProperties, + NonbinaryUnicodeProperties, + UnicodeGeneralCategoryValues, + UnicodeScriptValues, +} from '../runtime-semantics/all.mjs'; +import { + CharacterValue, + UTF16SurrogatePairToCodePoint, +} from '../static-semantics/all.mjs'; +import { + isIdentifierStart, + isIdentifierPart, + isLeadingSurrogate, + isTrailingSurrogate, + isHexDigit, +} from './Lexer.mjs'; + +const isSyntaxCharacter = (c) => '^$\\.*+?()[]{}|'.includes(c); +const isClosingSyntaxCharacter = (c) => ')]}|'.includes(c); +const isDecimalDigit = (c) => /[0123456789]/u.test(c); +const isControlLetter = (c) => /[a-zA-Z]/u.test(c); + +const PLUS_U = 1 << 0; +const PLUS_N = 1 << 1; + +export class RegExpParser { + constructor(source) { + this.source = source; + this.position = 0; + this.capturingGroups = []; + this.groupSpecifiers = new Map(); + this.decimalEscapes = []; + this.groupNameRefs = []; + this.state = 0; + } + + scope(flags, f) { + const oldState = this.state; + + if (flags.U === true) { + this.state |= PLUS_U; + } else if (flags.U === false) { + this.state &= ~PLUS_U; + } + + if (flags.N === true) { + this.state |= PLUS_N; + } else if (flags.N === false) { + this.state &= ~PLUS_N; + } + + const r = f(); + + this.state = oldState; + + return r; + } + + get plusU() { + return (this.state & PLUS_U) > 0; + } + + get plusN() { + return (this.state & PLUS_N) > 0; + } + + raise(message, position = this.position) { + const e = new SyntaxError(message); + e.position = position; + throw e; + } + + peek() { + return this.source[this.position]; + } + + test(c) { + return this.source[this.position] === c; + } + + eat(c) { + if (this.test(c)) { + this.next(); + return true; + } + return false; + } + + next() { + const c = this.source[this.position]; + this.position += 1; + return c; + } + + expect(c) { + if (!this.eat(c)) { + this.raise(`Expected ${c} but got ${this.peek()}`); + } + } + + // Pattern :: + // Disjunction + parsePattern() { + const node = { + type: 'Pattern', + groupSpecifiers: this.groupSpecifiers, + capturingGroups: this.capturingGroups, + Disjunction: undefined, + }; + node.Disjunction = this.parseDisjunction(); + if (this.position < this.source.length) { + this.raise('Unexpected token'); + } + this.decimalEscapes.forEach((d) => { + if (d.value > node.capturingGroups.length) { + this.raise('Invalid decimal escape', d.position); + } + }); + this.groupNameRefs.forEach((g) => { + if (!node.groupSpecifiers.has(g.GroupName)) { + this.raise('Invalid group name', g.position); + } + }); + return node; + } + + // Disjunction :: + // Alternative + // Alternative `|` Disjunction + parseDisjunction() { + const node = { + type: 'Disjunction', + Alternative: undefined, + Disjunction: undefined, + }; + node.Alternative = this.parseAlternative(); + if (this.eat('|')) { + node.Disjunction = this.parseDisjunction(); + } + return node; + } + + + // Alternative :: + // [empty] + // Term Alternative + parseAlternative() { + let node = { + type: 'Alternative', + Term: undefined, + Alternative: undefined, + }; + while (this.position < this.source.length + && !isClosingSyntaxCharacter(this.peek())) { + node = { + type: 'Alternative', + Term: this.parseTerm(), + Alternative: node, + }; + } + return node; + } + + // Term :: + // Assertion + // Atom + // Atom Quantifier + parseTerm() { + const assertion = this.maybeParseAssertion(); + if (assertion) { + return assertion; + } + return { + type: 'Term', + capturingParenthesesBefore: this.capturingGroups.length, + Atom: this.parseAtom(), + Quantifier: this.maybeParseQuantifier(), + }; + } + + // Assertion :: + // `^` + // `$` + // `\` `b` + // `\` `B` + // `(` `?` `=` Disjunction `)` + // `(` `?` `!` Disjunction `)` + // `(` `?` `<=` Disjunction `)` + // `(` `?` ` QuantifierPrefix.DecimalDigits_b) { + this.raise('Numbers out of order in quantifier'); + } + } + this.expect('}'); + } + + if (QuantifierPrefix) { + return { + type: 'Quantifier', + QuantifierPrefix, + greedy: !this.eat('?'), + }; + } + + return undefined; + } + + // Atom :: + // PatternCharacter + // `.` + // `\` AtomEscape + // CharacterClass + // `(` GroupSpecifier Disjunction `)` + // `(` `?` `:` Disjunction `)` + parseAtom() { + if (this.eat('.')) { + return { type: 'Atom', subtype: '.', enclosedCapturingParentheses: 0 }; + } + if (this.eat('\\')) { + return this.parseAtomEscape(); + } + if (this.eat('(')) { + const node = { + type: 'Atom', + capturingParenthesesBefore: this.capturingGroups.length, + enclosedCapturingParentheses: 0, + capturing: true, + GroupSpecifier: undefined, + Disjunction: undefined, + }; + if (this.eat('?')) { + if (this.eat(':')) { + node.capturing = false; + } else { + node.GroupSpecifier = this.parseGroupName(); + } + } + if (node.capturing) { + this.capturingGroups.push(node); + } + if (node.GroupSpecifier) { + if (this.groupSpecifiers.has(node.GroupSpecifier)) { + this.raise(`Duplicate group specifier '${node.GroupSpecifier}'`); + } + this.groupSpecifiers.set(node.GroupSpecifier, node.capturingParenthesesBefore); + } + node.Disjunction = this.parseDisjunction(); + this.expect(')'); + node.enclosedCapturingParentheses = this.capturingGroups.length - node.capturingParenthesesBefore - 1; + return node; + } + if (this.test('[')) { + return { + type: 'Atom', + CharacterClass: this.parseCharacterClass(), + }; + } + if (isSyntaxCharacter(this.peek())) { + this.raise(`Expected a PatternCharacter but got ${this.peek()}`); + } + return { + type: 'Atom', + PatternCharacter: this.parseSourceCharacter(), + }; + } + + // AtomEscape :: + // DecimalEscape + // CharacterClassEscape + // CharacterEscape + // [+N] `k` GroupName + parseAtomEscape() { + if (this.plusN && this.eat('k')) { + const node = { + type: 'AtomEscape', + position: this.position, + GroupName: this.parseGroupName(), + }; + this.groupNameRefs.push(node); + return node; + } + const CharacterClassEscape = this.maybeParseCharacterClassEscape(); + if (CharacterClassEscape) { + return { + type: 'AtomEscape', + CharacterClassEscape, + }; + } + const DecimalEscape = this.maybeParseDecimalEscape(); + if (DecimalEscape) { + return { + type: 'AtomEscape', + DecimalEscape, + }; + } + return { + type: 'AtomEscape', + CharacterEscape: this.parseCharacterEscape(), + }; + } + + // CharacterEscape :: + // ControlEscape + // `c` ControlLetter + // `0` [lookahead ∉ DecimalDigit] + // HexEscapeSequence + // RegExpUnicodeEscapeSequence + // IdentityEscape + // + // IdentityEscape :: + // [+U] SyntaxCharacter + // [+U] `/` + // [~U] SourceCharacter but not UnicodeIDContinue + parseCharacterEscape() { + switch (this.peek()) { + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + return { + type: 'CharacterEscape', + ControlEscape: this.next(), + }; + case 'c': { + this.next(); + const c = this.next(); + if (c === undefined) { + if (this.plusU) { + this.raise('Invalid identity escape'); + } + return { + type: 'CharacterEscape', + IdentityEscape: 'c', + }; + } + const p = c.codePointAt(0); + if ((p >= 65 && p <= 90) || (p >= 97 && p <= 122)) { + return { + type: 'CharacterEscape', + ControlLetter: c, + }; + } + if (this.plusU) { + this.raise('Invalid identity escape'); + } + return { + type: 'CharacterEscape', + IdentityEscape: c, + }; + } + case 'x': + if (isHexDigit(this.source[this.position + 1]) && isHexDigit(this.source[this.position + 2])) { + return { + type: 'CharacterEscape', + HexEscapeSequence: this.parseHexEscapeSequence(), + }; + } + if (this.plusU) { + this.raise('Invalid identity escape'); + } + this.next(); + return { + type: 'CharacterEscape', + IdentityEscape: 'x', + }; + case 'u': { + const RegExpUnicodeEscapeSequence = this.maybeParseRegExpUnicodeEscapeSequence(); + if (RegExpUnicodeEscapeSequence) { + return { + type: 'CharacterEscape', + RegExpUnicodeEscapeSequence, + }; + } + if (this.plusU) { + this.raise('Invalid identity escape'); + } + this.next(); + return { + type: 'CharacterEscape', + IdentityEscape: 'u', + }; + } + default: { + const c = this.next(); + if (c === undefined) { + this.raise('Unexpected escape'); + } + if (c === '0' && !isDecimalDigit(this.peek())) { + return { + type: 'CharacterEscape', + subtype: '0', + }; + } + if (this.plusU && !isSyntaxCharacter(c) && c !== '/') { + this.raise('Invalid identity escape'); + } + return { + type: 'CharacterEscape', + IdentityEscape: c, + }; + } + } + } + + // DecimalEscape :: + // NonZeroDigit DecimalDigits? [lookahead != DecimalDigit] + maybeParseDecimalEscape() { + if (isDecimalDigit(this.source[this.position]) && this.source[this.position] !== '0') { + const start = this.position; + let buffer = this.source[this.position]; + this.position += 1; + while (isDecimalDigit(this.source[this.position])) { + buffer += this.source[this.position]; + this.position += 1; + } + const node = { + type: 'DecimalEscape', + position: start, + value: Number.parseInt(buffer, 10), + }; + this.decimalEscapes.push(node); + return node; + } + return undefined; + } + + // CharacterClassEscape :: + // `d` + // `D` + // `s` + // `S` + // `w` + // `W` + // [+U] `p{` UnicodePropertyValueExpression `}` + // [+U] `P{` UnicodePropertyValueExpression `}` + maybeParseCharacterClassEscape() { + switch (this.peek()) { + case 'd': + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + return { + type: 'CharacterClassEscape', + value: this.next(), + }; + case 'p': + case 'P': { + if (!this.plusU) { + return undefined; + } + const value = this.next(); + this.expect('{'); + let sawDigit; + let LoneUnicodePropertyNameOrValue = ''; + while (true) { + if (this.position >= this.source.length) { + this.raise('Invalid unicode property name or value'); + } + const c = this.source[this.position]; + if (isDecimalDigit(c)) { + sawDigit = true; + this.position += 1; + LoneUnicodePropertyNameOrValue += c; + continue; + } + if (c === '_') { + this.position += 1; + LoneUnicodePropertyNameOrValue += c; + continue; + } + if (!isControlLetter(c)) { + break; + } + this.position += 1; + LoneUnicodePropertyNameOrValue += c; + } + if (LoneUnicodePropertyNameOrValue.length === 0) { + this.raise('Invalid unicode property name or value'); + } + if (sawDigit && this.eat('}')) { + if (!(LoneUnicodePropertyNameOrValue in UnicodeGeneralCategoryValues + || LoneUnicodePropertyNameOrValue in BinaryUnicodeProperties)) { + this.raise('Invalid unicode property name or value'); + } + return { + type: 'CharacterClassEscape', + value, + UnicodePropertyValueExpression: { + type: 'UnicodePropertyValueExpression', + LoneUnicodePropertyNameOrValue, + }, + }; + } + let UnicodePropertyValue; + if (this.source[this.position] === '=') { + this.position += 1; + UnicodePropertyValue = ''; + while (true) { + if (this.position >= this.source.length) { + this.raise('Invalid unicode property value'); + } + const c = this.source[this.position]; + if (!isControlLetter(c) && !isDecimalDigit(c) && c !== '_') { + break; + } + this.position += 1; + UnicodePropertyValue += c; + } + if (UnicodePropertyValue.length === 0) { + this.raise('Invalid unicode property value'); + } + } + this.expect('}'); + if (UnicodePropertyValue) { + if (!(LoneUnicodePropertyNameOrValue in NonbinaryUnicodeProperties)) { + this.raise('Invalid unicode property name'); + } + if (!(UnicodePropertyValue in UnicodeGeneralCategoryValues || UnicodePropertyValue in UnicodeScriptValues)) { + this.raise('Invalid unicode property value'); + } + return { + type: 'CharacterClassEscape', + value, + UnicodePropertyValueExpression: { + type: 'UnicodePropertyValueExpression', + UnicodePropertyName: LoneUnicodePropertyNameOrValue, + UnicodePropertyValue, + }, + }; + } + if (!(LoneUnicodePropertyNameOrValue in UnicodeGeneralCategoryValues + || LoneUnicodePropertyNameOrValue in BinaryUnicodeProperties)) { + this.raise('Invalid unicode property name or value'); + } + return { + type: 'CharacterClassEscape', + value, + UnicodePropertyValueExpression: { + type: 'UnicodePropertyValueExpression', + LoneUnicodePropertyNameOrValue, + }, + }; + } + default: + return undefined; + } + } + + // CharacterClass :: + // `[` ClassRanges `]` + // `[` `^` ClassRanges `]` + parseCharacterClass() { + this.expect('['); + const node = { + type: 'CharacterClass', + invert: false, + ClassRanges: undefined, + }; + node.invert = this.eat('^'); + node.ClassRanges = this.parseClassRanges(); + this.expect(']'); + return node; + } + + // ClassRanges :: + // [empty] + // NonemptyClassRanges + parseClassRanges() { + const ranges = []; + while (!this.test(']')) { + if (this.position >= this.source.length) { + this.raise('Unexpected end of CharacterClass'); + } + const atom = this.parseClassAtom(); + if (this.eat('-')) { + if (atom.type === 'CharacterClassEscape') { + this.raise('Invalid class range'); + } + if (this.test(']')) { + ranges.push(atom); + ranges.push({ type: 'ClassAtom', value: '-' }); + } else { + const atom2 = this.parseClassAtom(); + if (atom2.type === 'CharacterClassEscape') { + this.raise('Invalid class range'); + } + if (CharacterValue(atom) > CharacterValue(atom2)) { + this.raise('Invalid class range'); + } + ranges.push([atom, atom2]); + } + } else { + ranges.push(atom); + } + } + return ranges; + } + + // ClassAtom :: + // `-` + // ClassAtomNoDash + // ClassAtomNoDash :: + // SourceCharacter but not one of `\` or `]` or `-` + // `\` ClassEscape + // ClassEscape : + // `b` + // [+U] `-` + // CharacterClassEscape + // CharacterEscape + parseClassAtom() { + if (this.eat('\\')) { + if (this.eat('b')) { + return { + type: 'ClassEscape', + value: 'b', + }; + } + if (this.plusU && this.eat('-')) { + return { + type: 'ClassEscape', + value: '-', + }; + } + const CharacterClassEscape = this.maybeParseCharacterClassEscape(); + if (CharacterClassEscape) { + return CharacterClassEscape; + } + return { + type: 'ClassEscape', + CharacterEscape: this.parseCharacterEscape(), + }; + } + return { + type: 'ClassAtom', + SourceCharacter: this.parseSourceCharacter(), + }; + } + + parseSourceCharacter() { + const lead = this.source.charCodeAt(this.position); + const trail = this.source.charCodeAt(this.position + 1); + if (trail && isLeadingSurrogate(lead) && isTrailingSurrogate(trail)) { + return this.next() + this.next(); + } + return this.next(); + } + + parseGroupName() { + this.expect('<'); + const RegExpIdentifierName = this.parseRegExpIdentifierName(); + this.expect('>'); + return RegExpIdentifierName; + } + + // RegExpIdentifierName :: + // RegExpIdentifierStart + // RegExpIdentifierName RegExpIdentifierPart + parseRegExpIdentifierName() { + let buffer = ''; + let check = isIdentifierStart; + while (this.position < this.source.length) { + const c = this.source[this.position]; + const code = c.charCodeAt(0); + if (c === '\\') { + this.position += 1; + const RegExpUnicodeEscapeSequence = this.scope({ U: true }, () => this.maybeParseRegExpUnicodeEscapeSequence()); + if (!RegExpUnicodeEscapeSequence) { + this.raise('Invalid unicode escape'); + } + const raw = String.fromCodePoint(CharacterValue(RegExpUnicodeEscapeSequence)); + if (!check(raw)) { + this.raise('Invalid identifier escape'); + } + buffer += raw; + } else if (isLeadingSurrogate(code)) { + const lowSurrogate = this.source.charCodeAt(this.position + 1); + if (!isTrailingSurrogate(lowSurrogate)) { + this.raise('Invalid trailing surrogate'); + } + const codePoint = UTF16SurrogatePairToCodePoint(code, lowSurrogate); + const raw = String.fromCodePoint(codePoint); + if (!check(raw)) { + this.raise('Invalid surrogate pair'); + } + this.position += 2; + buffer += raw; + } else if (check(c)) { + buffer += c; + this.position += 1; + } else { + break; + } + check = isIdentifierPart; + } + if (buffer.length === 0) { + this.raise('Invalid empty identifier'); + } + return buffer; + } + + // DecimalDigits :: + // DecimalDigit + // DecimalDigits DecimalDigit + parseDecimalDigits() { + let n = ''; + if (!isDecimalDigit(this.peek())) { + this.raise('Invalid decimal digits'); + } + while (isDecimalDigit(this.peek())) { + n += this.next(); + } + return n; + } + + // HexEscapeSequence :: + // `x` HexDigit HexDigit + parseHexEscapeSequence() { + this.expect('x'); + const HexDigit_a = this.next(); + if (!isHexDigit(HexDigit_a)) { + this.raise('Not a hex digit'); + } + const HexDigit_b = this.next(); + if (!isHexDigit(HexDigit_b)) { + this.raise('Not a hex digit'); + } + return { + type: 'HexEscapeSequence', + HexDigit_a, + HexDigit_b, + }; + } + + scanHex(length) { + if (length === 0) { + this.raise('Invalid code point'); + } + let n = 0; + for (let i = 0; i < length; i += 1) { + const c = this.source[this.position]; + if (isHexDigit(c)) { + this.position += 1; + n = (n << 4) | Number.parseInt(c, 16); + } else { + this.raise('Invalid hex digit'); + } + } + return n; + } + + // RegExpUnicodeEscapeSequence :: + // [+U] `u` HexLeadSurrogate `\u` HexTrailSurrogate + // [+U] `u` HexLeadSurrogate + // [+U] `u` HexTrailSurrogate + // [+U] `u` HexNonSurrogate + // [~U] `u` Hex4Digits + // [+U] `u{` CodePoint `}` + maybeParseRegExpUnicodeEscapeSequence() { + const start = this.position; + if (!this.eat('u')) { + this.position = start; + return undefined; + } + if (this.plusU && this.eat('{')) { + const end = this.source.indexOf('}', this.position); + if (end === -1) { + this.raise('Invalid code point'); + } + const code = this.scanHex(end - this.position); + if (code > 0x10FFFF) { + this.raise('Invalid code point'); + } + this.position += 1; + return { + type: 'RegExpUnicodeEscapeSequence', + CodePoint: code, + }; + } + let lead; + try { + lead = this.scanHex(4); + } catch { + this.position = start; + return undefined; + } + if (this.plusU && isLeadingSurrogate(lead)) { + const back = this.position; + if (this.eat('\\') && this.eat('u')) { + let trail; + try { + trail = this.scanHex(4); + } catch { + this.position = back; + } + return { + type: 'RegExpUnicodeEscapeSequence', + HexLeadSurrogate: lead, + HexTrailSurrogate: trail, + }; + } + return { + type: 'RegExpUnicodeEscapeSequence', + HexLeadSurrogate: lead, + }; + } + return { + type: 'RegExpUnicodeEscapeSequence', + Hex4Digits: lead, + }; + } +} diff --git a/engine262/src/parser/Scope.mjs b/engine262/src/parser/Scope.mjs new file mode 100644 index 0000000..8061de7 --- /dev/null +++ b/engine262/src/parser/Scope.mjs @@ -0,0 +1,371 @@ +import { OutOfRange } from '../helpers.mjs'; + +export const Flag = { + __proto__: null, +}; +[ + 'return', + 'await', + 'yield', + 'parameters', + 'newTarget', + 'importMeta', + 'superCall', + 'superProperty', + 'in', + 'default', + 'module', +].forEach((name, i) => { + /* istanbul ignore next */ + if (i > 31) { + throw new RangeError(name); + } + Flag[name] = 1 << i; +}); + +export function getDeclarations(node) { + if (Array.isArray(node)) { + return node.flatMap((n) => getDeclarations(n)); + } + switch (node.type) { + case 'LexicalBinding': + case 'VariableDeclaration': + case 'BindingRestElement': + case 'BindingRestProperty': + case 'ForBinding': + if (node.BindingIdentifier) { + return getDeclarations(node.BindingIdentifier); + } + if (node.BindingPattern) { + return getDeclarations(node.BindingPattern); + } + return []; + case 'SingleNameBinding': + return getDeclarations(node.BindingIdentifier); + case 'ImportClause': { + const d = []; + if (node.ImportedDefaultBinding) { + d.push(...getDeclarations(node.ImportedDefaultBinding)); + } + if (node.NameSpaceImport) { + d.push(...getDeclarations(node.NameSpaceImport)); + } + if (node.NamedImports) { + d.push(...getDeclarations(node.NamedImports)); + } + return d; + } + case 'ImportSpecifier': + return getDeclarations(node.ImportedBinding); + case 'ImportedDefaultBinding': + case 'NameSpaceImport': + return getDeclarations(node.ImportedBinding); + case 'NamedImports': + return getDeclarations(node.ImportsList); + case 'ObjectBindingPattern': { + const declarations = getDeclarations(node.BindingPropertyList); + if (node.BindingRestProperty) { + declarations.push(...getDeclarations(node.BindingRestProperty)); + } + return declarations; + } + case 'ArrayBindingPattern': { + const declarations = getDeclarations(node.BindingElementList); + if (node.BindingRestElement) { + declarations.push(...getDeclarations(node.BindingRestElement)); + } + return declarations; + } + case 'BindingElement': + return getDeclarations(node.BindingPattern); + case 'BindingProperty': + return getDeclarations(node.BindingElement); + case 'BindingIdentifier': + case 'IdentifierName': + case 'LabelIdentifier': + return [{ name: node.name, node }]; + case 'StringLiteral': + return [{ name: node.value, node }]; + case 'Elision': + return []; + case 'ForDeclaration': + return getDeclarations(node.ForBinding); + case 'ExportSpecifier': + if (node.IdentifierName) { + return getDeclarations(node.IdentifierName); + } + return getDeclarations(node.IdentifierName_b); + case 'FunctionDeclaration': + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + return getDeclarations(node.BindingIdentifier); + case 'LexicalDeclaration': + return getDeclarations(node.BindingList); + case 'VariableStatement': + return getDeclarations(node.VariableDeclarationList); + case 'ClassDeclaration': + return getDeclarations(node.BindingIdentifier); + default: + throw new OutOfRange('getDeclarations', node); + } +} + +export class Scope { + constructor(parser) { + this.parser = parser; + this.scopeStack = []; + this.labels = []; + this.arrowInfoStack = []; + this.assignmentInfoStack = []; + this.exports = new Set(); + this.undefinedExports = new Map(); + this.flags = 0; + } + + hasReturn() { + return (this.flags & Flag.return) !== 0; + } + + hasAwait() { + return (this.flags & Flag.await) !== 0; + } + + hasYield() { + return (this.flags & Flag.yield) !== 0; + } + + hasNewTarget() { + return (this.flags & Flag.newTarget) !== 0; + } + + hasSuperCall() { + return (this.flags & Flag.superCall) !== 0; + } + + hasSuperProperty() { + return (this.flags & Flag.superProperty) !== 0; + } + + hasImportMeta() { + return (this.flags & Flag.importMeta) !== 0; + } + + hasIn() { + return (this.flags & Flag.in) !== 0; + } + + inParameters() { + return (this.flags & Flag.parameters) !== 0; + } + + isDefault() { + return (this.flags & Flag.default) !== 0; + } + + isModule() { + return (this.flags & Flag.module) !== 0; + } + + with(flags, f) { + const oldFlags = this.flags; + + Object.entries(flags) + .forEach(([k, v]) => { + if (k in Flag) { + if (v === true) { + this.flags |= Flag[k]; + } else if (v === false) { + this.flags &= ~Flag[k]; + } + } + }); + + if (flags.lexical || flags.variable) { + this.scopeStack.push({ + flags, + lexicals: new Set(), + variables: new Set(), + functions: new Set(), + parameters: new Set(), + }); + } + + const oldLabels = this.labels; + if (flags.label === 'boundary') { + this.labels = []; + } else if (flags.label) { + this.labels.push({ type: flags.label }); + } + + const oldStrict = this.parser.state.strict; + if (flags.strict === true) { + this.parser.state.strict = true; + } else if (flags.strict === false) { + this.parser.state.strict = false; + } + + const r = f(); + + if (flags.label === 'boundary') { + this.labels = oldLabels; + } else if (flags.label) { + this.labels.pop(); + } + + if (flags.lexical || flags.variable) { + this.scopeStack.pop(); + } + + this.parser.state.strict = oldStrict; + this.flags = oldFlags; + + return r; + } + + pushArrowInfo(isAsync = false) { + this.arrowInfoStack.push({ + isAsync, + hasTrailingComma: false, + yieldExpressions: [], + awaitExpressions: [], + awaitIdentifiers: [], + }); + } + + popArrowInfo() { + return this.arrowInfoStack.pop(); + } + + pushAssignmentInfo(type) { + const parser = this.parser; + this.assignmentInfoStack.push({ + type, + coverInitializedNameErrors: [], + clear() { + this.coverInitializedNameErrors.forEach((e) => { + parser.earlyErrors.delete(e); + }); + }, + }); + } + + popAssignmentInfo() { + return this.assignmentInfoStack.pop(); + } + + registerCoverInitializedName(CoverInitializedName) { + const error = this.parser.raiseEarly('UnexpectedToken', CoverInitializedName); + for (let i = this.assignmentInfoStack.length - 1; i >= 0; i -= 1) { + const info = this.assignmentInfoStack[i]; + info.coverInitializedNameErrors.push(error); + if (info.type !== 'assign') { + break; + } + } + } + + lexicalScope() { + for (let i = this.scopeStack.length - 1; i >= 0; i -= 1) { + const scope = this.scopeStack[i]; + if (scope.flags.lexical) { + return scope; + } + } + /* istanbul ignore next */ + throw new RangeError(); + } + + variableScope() { + for (let i = this.scopeStack.length - 1; i >= 0; i -= 1) { + const scope = this.scopeStack[i]; + if (scope.flags.variable) { + return scope; + } + } + /* istanbul ignore next */ + throw new RangeError(); + } + + declare(node, type) { + const declarations = getDeclarations(node); + declarations.forEach((d) => { + switch (type) { + case 'lexical': + case 'import': { + if (type === 'lexical' && d.name === 'let') { + this.parser.raiseEarly('LetInLexicalBinding', d.node); + } + const scope = this.lexicalScope(); + if (scope.lexicals.has(d.name) + || scope.variables.has(d.name) + || scope.functions.has(d.name) + || scope.parameters.has(d.name)) { + this.parser.raiseEarly('AlreadyDeclared', d.node, d.name); + } + scope.lexicals.add(d.name); + if (scope === this.scopeStack[0] && this.undefinedExports.has(d.name)) { + this.undefinedExports.delete(d.name); + } + break; + } + case 'function': { + const scope = this.lexicalScope(); + if (scope.lexicals.has(d.name)) { + this.parser.raiseEarly('AlreadyDeclared', d.node, d.name); + } + if (scope.flags.variableFunctions) { + scope.functions.add(d.name); + } else { + if (scope.variables.has(d.name)) { + this.parser.raiseEarly('AlreadyDeclared', d.node, d.name); + } + scope.lexicals.add(d.name); + } + if (scope === this.scopeStack[0] && this.undefinedExports.has(d.name)) { + this.undefinedExports.delete(d.name); + } + break; + } + case 'parameter': + this.variableScope().parameters.add(d.name); + break; + case 'variable': + for (let i = this.scopeStack.length - 1; i >= 0; i -= 1) { + const scope = this.scopeStack[i]; + scope.variables.add(d.name); + if (scope.lexicals.has(d.name) || (!scope.flags.variableFunctions && scope.functions.has(d.name))) { + this.parser.raiseEarly('AlreadyDeclared', d.node, d.name); + } + if (i === 0 && this.undefinedExports.has(d.name)) { + this.undefinedExports.delete(d.name); + } + if (scope.flags.variable) { + break; + } + } + break; + case 'export': + if (this.exports.has(d.name)) { + this.parser.raiseEarly('AlreadyDeclared', d.node, d.name); + } else { + this.exports.add(d.name); + } + break; + default: + /* istanbul ignore next */ + throw new RangeError(type); + } + }); + } + + checkUndefinedExports(NamedExports) { + const scope = this.variableScope(); + NamedExports.ExportsList.forEach((n) => { + const targetNode = n.IdentifierName || n.IdentifierName_a; + if (!scope.lexicals.has(targetNode.name) && !scope.variables.has(targetNode.name)) { + this.undefinedExports.set(targetNode.name, targetNode); + } + }); + } +} diff --git a/engine262/src/parser/StatementParser.mjs b/engine262/src/parser/StatementParser.mjs new file mode 100644 index 0000000..681b6f3 --- /dev/null +++ b/engine262/src/parser/StatementParser.mjs @@ -0,0 +1,1209 @@ +import { IsStringValidUnicode, StringValue } from '../static-semantics/all.mjs'; +import { Token, isAutomaticSemicolon, isKeywordRaw } from './tokens.mjs'; +import { ExpressionParser } from './ExpressionParser.mjs'; +import { FunctionKind } from './FunctionParser.mjs'; +import { getDeclarations } from './Scope.mjs'; + +export class StatementParser extends ExpressionParser { + semicolon() { + if (this.eat(Token.SEMICOLON)) { + return; + } + if (this.peek().hadLineTerminatorBefore || isAutomaticSemicolon(this.peek().type)) { + return; + } + this.unexpected(); + } + + // StatementList : + // StatementListItem + // StatementList StatementListItem + parseStatementList(endToken, directives) { + const statementList = []; + const oldStrict = this.state.strict; + const directiveData = []; + while (!this.eat(endToken)) { + if (directives !== undefined && this.test(Token.STRING)) { + const token = this.peek(); + const directive = this.source.slice(token.startIndex + 1, token.endIndex - 1); + if (directive === 'use strict') { + this.state.strict = true; + directiveData.forEach((d) => { + if (/\\([1-9]|0\d)/.test(d.directive)) { + this.raiseEarly('IllegalOctalEscape', d.token); + } + }); + } + directives.push(directive); + directiveData.push({ directive, token }); + } else { + directives = undefined; + } + + const stmt = this.parseStatementListItem(); + statementList.push(stmt); + } + + this.state.strict = oldStrict; + + return statementList; + } + + // StatementListItem : + // Statement + // Declaration + // + // Declaration : + // HoistableDeclaration + // ClassDeclaration + // LexicalDeclaration + parseStatementListItem() { + switch (this.peek().type) { + case Token.FUNCTION: + return this.parseHoistableDeclaration(); + case Token.CLASS: + return this.parseClassDeclaration(); + case Token.CONST: + return this.parseLexicalDeclaration(); + default: + if (this.test('let')) { + switch (this.peekAhead().type) { + case Token.LBRACE: + case Token.LBRACK: + case Token.IDENTIFIER: + case Token.YIELD: + case Token.AWAIT: + return this.parseLexicalDeclaration(); + default: + break; + } + } + if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) { + return this.parseHoistableDeclaration(); + } + return this.parseStatement(); + } + } + + // HoistableDeclaration : + // FunctionDeclaration + // GeneratorDeclaration + // AsyncFunctionDeclaration + // AsyncGeneratorDeclaration + parseHoistableDeclaration() { + switch (this.peek().type) { + case Token.FUNCTION: + return this.parseFunctionDeclaration(FunctionKind.NORMAL); + default: + if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) { + return this.parseFunctionDeclaration(FunctionKind.ASYNC); + } + throw new Error('unreachable'); + } + } + + // ClassDeclaration : + // `class` BindingIdentifier ClassTail + // [+Default] `class` ClassTail + parseClassDeclaration() { + return this.parseClass(false); + } + + // LexicalDeclaration : LetOrConst BindingList `;` + parseLexicalDeclaration() { + const node = this.startNode(); + const letOrConst = this.eat('let') || this.expect(Token.CONST); + node.LetOrConst = letOrConst.type === Token.CONST ? 'const' : 'let'; + node.BindingList = this.parseBindingList(); + this.semicolon(); + + this.scope.declare(node.BindingList, 'lexical'); + node.BindingList.forEach((b) => { + if (node.LetOrConst === 'const' && !b.Initializer) { + this.raiseEarly('ConstDeclarationMissingInitializer', b); + } + }); + + return this.finishNode(node, 'LexicalDeclaration'); + } + + // BindingList : + // LexicalBinding + // BindingList `,` LexicalBinding + // + // LexicalBinding : + // BindingIdentifier Initializer? + // BindingPattern Initializer + parseBindingList() { + const bindingList = []; + do { + const node = this.parseBindingElement(); + node.type = 'LexicalBinding'; + bindingList.push(node); + } while (this.eat(Token.COMMA)); + return bindingList; + } + + // BindingElement : + // SingleNameBinding + // BindingPattern Initializer? + // SingleNameBinding : + // BindingIdentifier Initializer? + parseBindingElement() { + const node = this.startNode(); + if (this.test(Token.LBRACE) || this.test(Token.LBRACK)) { + node.BindingPattern = this.parseBindingPattern(); + } else { + node.BindingIdentifier = this.parseBindingIdentifier(); + } + node.Initializer = this.parseInitializerOpt(); + return this.finishNode(node, node.BindingPattern ? 'BindingElement' : 'SingleNameBinding'); + } + + // BindingPattern: + // ObjectBindingPattern + // ArrayBindingPattern + parseBindingPattern() { + switch (this.peek().type) { + case Token.LBRACE: + return this.parseObjectBindingPattern(); + case Token.LBRACK: + return this.parseArrayBindingPattern(); + default: + return this.unexpected(); + } + } + + // ObjectBindingPattern : + // `{` `}` + // `{` BindingRestProperty `}` + // `{` BindingPropertyList `}` + // `{` BindingPropertyList `,` BindingRestProperty? `}` + parseObjectBindingPattern() { + const node = this.startNode(); + this.expect(Token.LBRACE); + node.BindingPropertyList = []; + while (!this.eat(Token.RBRACE)) { + if (this.test(Token.ELLIPSIS)) { + node.BindingRestProperty = this.parseBindingRestProperty(); + this.expect(Token.RBRACE); + break; + } else { + node.BindingPropertyList.push(this.parseBindingProperty()); + if (!this.eat(Token.COMMA)) { + this.expect(Token.RBRACE); + break; + } + } + } + return this.finishNode(node, 'ObjectBindingPattern'); + } + + // BindingProperty : + // SingleNameBinding + // PropertyName : BindingElement + parseBindingProperty() { + const node = this.startNode(); + const name = this.parsePropertyName(); + if (this.eat(Token.COLON)) { + node.PropertyName = name; + node.BindingElement = this.parseBindingElement(); + return this.finishNode(node, 'BindingProperty'); + } + node.BindingIdentifier = name; + if (name.type === 'IdentifierName') { + name.type = 'BindingIdentifier'; + } else { + this.unexpected(name); + } + node.Initializer = this.parseInitializerOpt(); + return this.finishNode(node, 'SingleNameBinding'); + } + + // BindingRestProperty : + // `...` BindingIdentifier + parseBindingRestProperty() { + const node = this.startNode(); + this.expect(Token.ELLIPSIS); + node.BindingIdentifier = this.parseBindingIdentifier(); + return this.finishNode(node, 'BindingRestProperty'); + } + + // ArrayBindingPattern : + // `[` Elision? BindingRestElement `]` + // `[` BindingElementList `]` + // `[` BindingElementList `,` Elision? BindingRestElement `]` + parseArrayBindingPattern() { + const node = this.startNode(); + this.expect(Token.LBRACK); + node.BindingElementList = []; + while (true) { + while (this.test(Token.COMMA)) { + const elision = this.startNode(); + this.next(); + node.BindingElementList.push(this.finishNode(elision, 'Elision')); + } + if (this.eat(Token.RBRACK)) { + break; + } + if (this.test(Token.ELLIPSIS)) { + node.BindingRestElement = this.parseBindingRestElement(); + this.expect(Token.RBRACK); + break; + } else { + node.BindingElementList.push(this.parseBindingElement()); + } + if (this.eat(Token.RBRACK)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'ArrayBindingPattern'); + } + + // BindingRestElement : + // `...` BindingIdentifier + // `...` BindingPattern + parseBindingRestElement() { + const node = this.startNode(); + this.expect(Token.ELLIPSIS); + switch (this.peek().type) { + case Token.LBRACE: + case Token.LBRACK: + node.BindingPattern = this.parseBindingPattern(); + break; + default: + node.BindingIdentifier = this.parseBindingIdentifier(); + break; + } + return this.finishNode(node, 'BindingRestElement'); + } + + // Initializer : `=` AssignmentExpression + parseInitializerOpt() { + if (this.eat(Token.ASSIGN)) { + return this.parseAssignmentExpression(); + } + return null; + } + + // FunctionDeclaration + parseFunctionDeclaration(kind) { + return this.parseFunction(false, kind); + } + + // Statement : + // ... + parseStatement() { + switch (this.peek().type) { + case Token.LBRACE: + return this.parseBlockStatement(); + case Token.VAR: + return this.parseVariableStatement(); + case Token.SEMICOLON: { + const node = this.startNode(); + this.next(); + return this.finishNode(node, 'EmptyStatement'); + } + case Token.IF: + return this.parseIfStatement(); + case Token.DO: + return this.parseDoWhileStatement(); + case Token.WHILE: + return this.parseWhileStatement(); + case Token.FOR: + return this.parseForStatement(); + case Token.SWITCH: + return this.parseSwitchStatement(); + case Token.CONTINUE: + case Token.BREAK: + return this.parseBreakContinueStatement(); + case Token.RETURN: + return this.parseReturnStatement(); + case Token.WITH: + return this.parseWithStatement(); + case Token.THROW: + return this.parseThrowStatement(); + case Token.TRY: + return this.parseTryStatement(); + case Token.DEBUGGER: + return this.parseDebuggerStatement(); + default: + return this.parseExpressionStatement(); + } + } + + // BlockStatement : Block + parseBlockStatement() { + return this.parseBlock(); + } + + // Block : `{` StatementList `}` + parseBlock(lexical = true) { + const node = this.startNode(); + this.expect(Token.LBRACE); + this.scope.with({ lexical }, () => { + node.StatementList = this.parseStatementList(Token.RBRACE); + }); + return this.finishNode(node, 'Block'); + } + + // VariableStatement : `var` VariableDeclarationList `;` + parseVariableStatement() { + const node = this.startNode(); + this.expect(Token.VAR); + node.VariableDeclarationList = this.parseVariableDeclarationList(); + this.semicolon(); + this.scope.declare(node.VariableDeclarationList, 'variable'); + return this.finishNode(node, 'VariableStatement'); + } + + // VariableDeclarationList : + // VariableDeclaration + // VariableDeclarationList `,` VariableDeclaration + parseVariableDeclarationList(firstDeclarationRequiresInit = true) { + const declarationList = []; + do { + const node = this.parseVariableDeclaration(firstDeclarationRequiresInit); + declarationList.push(node); + } while (this.eat(Token.COMMA)); + return declarationList; + } + + // VariableDeclaration : + // BindingIdentifier Initializer? + // BindingPattern Initializer + parseVariableDeclaration(firstDeclarationRequiresInit) { + const node = this.startNode(); + switch (this.peek().type) { + case Token.LBRACE: + case Token.LBRACK: + node.BindingPattern = this.parseBindingPattern(); + if (firstDeclarationRequiresInit) { + this.expect(Token.ASSIGN); + node.Initializer = this.parseAssignmentExpression(); + } else { + node.Initializer = this.parseInitializerOpt(); + } + break; + default: + node.BindingIdentifier = this.parseBindingIdentifier(); + node.Initializer = this.parseInitializerOpt(); + break; + } + return this.finishNode(node, 'VariableDeclaration'); + } + + // IfStatement : + // `if` `(` Expression `)` Statement `else` Statement + // `if` `(` Expression `)` Statement + parseIfStatement() { + const node = this.startNode(); + this.expect(Token.IF); + this.expect(Token.LPAREN); + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + node.Statement_a = this.parseStatement(); + if (this.eat(Token.ELSE)) { + node.Statement_b = this.parseStatement(); + } + return this.finishNode(node, 'IfStatement'); + } + + // `while` `(` Expression `)` Statement + parseWhileStatement() { + const node = this.startNode(); + this.expect(Token.WHILE); + this.expect(Token.LPAREN); + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + this.scope.with({ label: 'loop' }, () => { + node.Statement = this.parseStatement(); + }); + return this.finishNode(node, 'WhileStatement'); + } + + // `do` Statement `while` `(` Expression `)` `;` + parseDoWhileStatement() { + const node = this.startNode(); + this.expect(Token.DO); + this.scope.with({ label: 'loop' }, () => { + node.Statement = this.parseStatement(); + }); + this.expect(Token.WHILE); + this.expect(Token.LPAREN); + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + // Semicolons are completely optional after a do-while, even without a newline + this.eat(Token.SEMICOLON); + return this.finishNode(node, 'DoWhileStatement'); + } + + // `for` `(` [lookahead != `let` `[`] Expression? `;` Expression? `;` Expression? `)` Statement + // `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement + // `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement + // `for` `(` [lookahead != `let` `[`] LeftHandSideExpression `in` Expression `)` Statement + // `for` `(` `var` ForBinding `in` Expression `)` Statement + // `for` `(` ForDeclaration `in` Expression `)` Statement + // `for` `(` [lookahead != `let`] LeftHandSideExpression `of` AssignmentExpression `)` Statement + // `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + // `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement + // `for` `await` `(` [lookahead != `let`] LeftHandSideExpression `of` AssignmentExpression `)` Statement + // `for` `await` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + // `for` `await` `(` ForDeclaration `of` AssignmentExpression `)` Statement + // + // ForDeclaration : LetOrConst ForBinding + parseForStatement() { + return this.scope.with({ + lexical: true, + label: 'loop', + }, () => { + const node = this.startNode(); + this.expect(Token.FOR); + const isAwait = this.scope.hasAwait() && this.eat(Token.AWAIT); + if (isAwait && !this.scope.hasReturn()) { + this.state.hasTopLevelAwait = true; + } + this.expect(Token.LPAREN); + if (isAwait && this.test(Token.SEMICOLON)) { + this.unexpected(); + } + if (this.eat(Token.SEMICOLON)) { + if (!this.test(Token.SEMICOLON)) { + node.Expression_b = this.parseExpression(); + } + this.expect(Token.SEMICOLON); + if (!this.test(Token.RPAREN)) { + node.Expression_c = this.parseExpression(); + } + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForStatement'); + } + const isLexicalStart = () => { + switch (this.peekAhead().type) { + case Token.LBRACE: + case Token.LBRACK: + case Token.IDENTIFIER: + case Token.YIELD: + case Token.AWAIT: + return true; + default: + return false; + } + }; + if ((this.test('let') || this.test(Token.CONST)) && isLexicalStart()) { + const inner = this.startNode(); + if (this.eat('let')) { + inner.LetOrConst = 'let'; + } else { + this.expect(Token.CONST); + inner.LetOrConst = 'const'; + } + const list = this.parseBindingList(); + this.scope.declare(list, 'lexical'); + if (list.length > 1 || this.test(Token.SEMICOLON)) { + inner.BindingList = list; + node.LexicalDeclaration = this.finishNode(inner, 'LexicalDeclaration'); + this.expect(Token.SEMICOLON); + if (!this.test(Token.SEMICOLON)) { + node.Expression_a = this.parseExpression(); + } + this.expect(Token.SEMICOLON); + if (!this.test(Token.RPAREN)) { + node.Expression_b = this.parseExpression(); + } + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForStatement'); + } + inner.ForBinding = list[0]; + inner.ForBinding.type = 'ForBinding'; + if (inner.ForBinding.Initializer) { + this.unexpected(inner.ForBinding.Initializer); + } + node.ForDeclaration = this.finishNode(inner, 'ForDeclaration'); + getDeclarations(node.ForDeclaration) + .forEach((d) => { + if (d.name === 'let') { + this.raiseEarly('UnexpectedToken', d.node); + } + }); + if (!isAwait && this.eat(Token.IN)) { + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForInStatement'); + } + this.expect('of'); + node.AssignmentExpression = this.parseAssignmentExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, isAwait ? 'ForAwaitStatement' : 'ForOfStatement'); + } + if (this.eat(Token.VAR)) { + if (isAwait) { + node.ForBinding = this.parseForBinding(); + this.expect('of'); + node.AssignmentExpression = this.parseAssignmentExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForAwaitStatement'); + } + const list = this.parseVariableDeclarationList(false); + if (list.length > 1 || this.test(Token.SEMICOLON)) { + node.VariableDeclarationList = list; + this.expect(Token.SEMICOLON); + if (!this.test(Token.SEMICOLON)) { + node.Expression_a = this.parseExpression(); + } + this.expect(Token.SEMICOLON); + if (!this.test(Token.RPAREN)) { + node.Expression_b = this.parseExpression(); + } + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForStatement'); + } + node.ForBinding = list[0]; + node.ForBinding.type = 'ForBinding'; + if (node.ForBinding.Initializer) { + this.unexpected(node.ForBinding.Initializer); + } + if (this.eat('of')) { + node.AssignmentExpression = this.parseAssignmentExpression(); + } else { + this.expect(Token.IN); + node.Expression = this.parseExpression(); + } + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, node.AssignmentExpression ? 'ForOfStatement' : 'ForInStatement'); + } + + this.scope.pushAssignmentInfo('for'); + const expression = this.scope.with({ in: false }, () => this.parseExpression()); + const validateLHS = (n) => { + if (n.type === 'AssignmentExpression') { + this.raiseEarly('UnexpectedToken', n); + } else { + this.validateAssignmentTarget(n); + } + }; + const assignmentInfo = this.scope.popAssignmentInfo(); + if (!isAwait && this.eat(Token.IN)) { + assignmentInfo.clear(); + validateLHS(expression); + node.LeftHandSideExpression = expression; + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForInStatement'); + } + if (this.eat('of')) { + assignmentInfo.clear(); + validateLHS(expression); + node.LeftHandSideExpression = expression; + node.AssignmentExpression = this.parseAssignmentExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, isAwait ? 'ForAwaitStatement' : 'ForOfStatement'); + } + + node.Expression_a = expression; + this.expect(Token.SEMICOLON); + + if (!this.test(Token.SEMICOLON)) { + node.Expression_b = this.parseExpression(); + } + this.expect(Token.SEMICOLON); + + if (!this.test(Token.RPAREN)) { + node.Expression_c = this.parseExpression(); + } + this.expect(Token.RPAREN); + + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForStatement'); + }); + } + + // ForBinding : + // BindingIdentifier + // BindingPattern + parseForBinding() { + const node = this.startNode(); + switch (this.peek().type) { + case Token.LBRACE: + case Token.LBRACK: + node.BindingPattern = this.parseBindingPattern(); + break; + default: + node.BindingIdentifier = this.parseBindingIdentifier(); + break; + } + return this.finishNode(node, 'ForBinding'); + } + + + // SwitchStatement : + // `switch` `(` Expression `)` CaseBlock + parseSwitchStatement() { + const node = this.startNode(); + this.expect(Token.SWITCH); + this.expect(Token.LPAREN); + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + this.scope.with({ + lexical: true, + label: 'switch', + }, () => { + node.CaseBlock = this.parseCaseBlock(); + }); + return this.finishNode(node, 'SwitchStatement'); + } + + // CaseBlock : + // `{` CaseClauses? `}` + // `{` CaseClauses? DefaultClause CaseClauses? `}` + // CaseClauses : + // CaseClause + // CaseClauses CauseClause + // CaseClause : + // `case` Expression `:` StatementList? + // DefaultClause : + // `default` `:` StatementList? + parseCaseBlock() { + const node = this.startNode(); + this.expect(Token.LBRACE); + while (!this.eat(Token.RBRACE)) { + switch (this.peek().type) { + case Token.CASE: + case Token.DEFAULT: { + const inner = this.startNode(); + const t = this.next().type; + if (t === Token.DEFAULT && node.DefaultClause) { + this.unexpected(); + } + if (t === Token.CASE) { + inner.Expression = this.parseExpression(); + } + this.expect(Token.COLON); + while (!(this.test(Token.CASE) || this.test(Token.DEFAULT) || this.test(Token.RBRACE))) { + if (!inner.StatementList) { + inner.StatementList = []; + } + inner.StatementList.push(this.parseStatementListItem()); + } + if (t === Token.DEFAULT) { + node.DefaultClause = this.finishNode(inner, 'DefaultClause'); + } else { + if (node.DefaultClause) { + if (!node.CaseClauses_b) { + node.CaseClauses_b = []; + } + node.CaseClauses_b.push(this.finishNode(inner, 'CaseClause')); + } else { + if (!node.CaseClauses_a) { + node.CaseClauses_a = []; + } + node.CaseClauses_a.push(this.finishNode(inner, 'CaseClause')); + } + } + break; + } + default: + this.unexpected(); + } + } + return this.finishNode(node, 'CaseBlock'); + } + + // BreakStatement : + // `break` `;` + // `break` [no LineTerminator here] LabelIdentifier `;` + // + // ContinueStatement : + // `continue` `;` + // `continue` [no LineTerminator here] LabelIdentifier `;` + parseBreakContinueStatement() { + const node = this.startNode(); + const isBreak = this.eat(Token.BREAK); + if (!isBreak) { + this.expect(Token.CONTINUE); + } + if (this.eat(Token.SEMICOLON)) { + node.LabelIdentifier = null; + } else if (this.peek().hadLineTerminatorBefore) { + node.LabelIdentifier = null; + this.semicolon(); + } else { + if (this.test(Token.IDENTIFIER)) { + node.LabelIdentifier = this.parseLabelIdentifier(); + } else { + node.LabelIdentifier = null; + } + this.semicolon(); + } + this.verifyBreakContinue(node, isBreak); + return this.finishNode(node, isBreak ? 'BreakStatement' : 'ContinueStatement'); + } + + verifyBreakContinue(node, isBreak) { + let i = 0; + for (; i < this.scope.labels.length; i += 1) { + const label = this.scope.labels[i]; + if (!node.LabelIdentifier || node.LabelIdentifier.name === label.name) { + if (label.type && (isBreak || label.type === 'loop')) { + break; + } + if (node.LabelIdentifier && isBreak) { + break; + } + } + } + if (i === this.scope.labels.length) { + this.raiseEarly('IllegalBreakContinue', node, isBreak); + } + } + + // ReturnStatement : + // `return` `;` + // `return` [no LineTerminator here] Expression `;` + parseReturnStatement() { + if (!this.scope.hasReturn()) { + this.unexpected(); + } + const node = this.startNode(); + this.expect(Token.RETURN); + if (this.eat(Token.SEMICOLON)) { + node.Expression = null; + } else if (this.peek().hadLineTerminatorBefore) { + node.Expression = null; + this.semicolon(); + } else { + node.Expression = this.parseExpression(); + this.semicolon(); + } + return this.finishNode(node, 'ReturnStatement'); + } + + // WithStatement : + // `with` `(` Expression `)` Statement + parseWithStatement() { + if (this.isStrictMode()) { + this.raiseEarly('UnexpectedToken'); + } + const node = this.startNode(); + this.expect(Token.WITH); + this.expect(Token.LPAREN); + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'WithStatement'); + } + + // ThrowStatement : + // `throw` [no LineTerminator here] Expression `;` + parseThrowStatement() { + const node = this.startNode(); + this.expect(Token.THROW); + if (this.peek().hadLineTerminatorBefore) { + this.raise('NewlineAfterThrow', node); + } + node.Expression = this.parseExpression(); + this.semicolon(); + return this.finishNode(node, 'ThrowStatement'); + } + + // TryStatement : + // `try` Block Catch + // `try` Block Finally + // `try` Block Catch Finally + // + // Catch : + // `catch` `(` CatchParameter `)` Block + // `catch` Block + // + // Finally : + // `finally` Block + // + // CatchParameter : + // BindingIdentifier + // BindingPattern + parseTryStatement() { + const node = this.startNode(); + this.expect(Token.TRY); + node.Block = this.parseBlock(); + if (this.eat(Token.CATCH)) { + this.scope.with({ lexical: true }, () => { + const clause = this.startNode(); + if (this.eat(Token.LPAREN)) { + switch (this.peek().type) { + case Token.LBRACE: + case Token.LBRACK: + clause.CatchParameter = this.parseBindingPattern(); + break; + default: + clause.CatchParameter = this.parseBindingIdentifier(); + break; + } + this.scope.declare(clause.CatchParameter, 'lexical'); + this.expect(Token.RPAREN); + } else { + clause.CatchParameter = null; + } + clause.Block = this.parseBlock(false); + node.Catch = this.finishNode(clause, 'Catch'); + }); + } else { + node.Catch = null; + } + if (this.eat(Token.FINALLY)) { + node.Finally = this.parseBlock(); + } else { + node.Finally = null; + } + if (!node.Catch && !node.Finally) { + this.raise('TryMissingCatchOrFinally'); + } + return this.finishNode(node, 'TryStatement'); + } + + // DebuggerStatement : `debugger` `;` + parseDebuggerStatement() { + const node = this.startNode(); + this.expect(Token.DEBUGGER); + this.semicolon(); + return this.finishNode(node, 'DebuggerStatement'); + } + + // ExpressionStatement : + // [lookahead != `{`, `function`, `async` [no LineTerminator here] `function`, `class`, `let` `[` ] Expression `;` + parseExpressionStatement() { + switch (this.peek().type) { + case Token.LBRACE: + case Token.FUNCTION: + case Token.CLASS: + this.unexpected(); + break; + default: + if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) { + this.unexpected(); + } + if (this.test('let') && this.testAhead(Token.LBRACK)) { + this.unexpected(); + } + break; + } + const node = this.startNode(); + const expression = this.parseExpression(); + if (expression.type === 'IdentifierReference' && this.eat(Token.COLON)) { + expression.type = 'LabelIdentifier'; + node.LabelIdentifier = expression; + + if (this.scope.labels.find((l) => l.name === node.LabelIdentifier.name)) { + this.raiseEarly('AlreadyDeclared', node.LabelIdentifier, node.LabelIdentifier.name); + } + let type = null; + switch (this.peek().type) { + case Token.SWITCH: + type = 'switch'; + break; + case Token.DO: + case Token.WHILE: + case Token.FOR: + type = 'loop'; + break; + default: + break; + } + this.scope.labels.push({ + name: node.LabelIdentifier.name, + type, + }); + + node.LabelledItem = this.parseStatement(); + + this.scope.labels.pop(); + + return this.finishNode(node, 'LabelledStatement'); + } + node.Expression = expression; + this.semicolon(); + return this.finishNode(node, 'ExpressionStatement'); + } + + // ImportDeclaration : + // `import` ImportClause FromClause `;` + // `import` ModuleSpecifier `;` + parseImportDeclaration() { + if (this.testAhead(Token.PERIOD) || this.testAhead(Token.LPAREN)) { + // `import` `(` + // `import` `.` + return this.parseExpressionStatement(); + } + const node = this.startNode(); + this.next(); + if (this.test(Token.STRING)) { + node.ModuleSpecifier = this.parsePrimaryExpression(); + } else { + node.ImportClause = this.parseImportClause(); + this.scope.declare(node.ImportClause, 'import'); + node.FromClause = this.parseFromClause(); + } + this.semicolon(); + return this.finishNode(node, 'ImportDeclaration'); + } + + // ImportClause : + // ImportedDefaultBinding + // NameSpaceImport + // NamedImports + // ImportedDefaultBinding `,` NameSpaceImport + // ImportedDefaultBinding `,` NamedImports + // + // ImportedBinding : + // BindingIdentifier + parseImportClause() { + const node = this.startNode(); + if (this.test(Token.IDENTIFIER)) { + node.ImportedDefaultBinding = this.parseImportedDefaultBinding(); + if (!this.eat(Token.COMMA)) { + return this.finishNode(node, 'ImportClause'); + } + } + if (this.test(Token.MUL)) { + node.NameSpaceImport = this.parseNameSpaceImport(); + } else if (this.eat(Token.LBRACE)) { + node.NamedImports = this.parseNamedImports(); + } else { + this.unexpected(); + } + return this.finishNode(node, 'ImportClause'); + } + + // ImportedDefaultBinding : + // ImportedBinding + parseImportedDefaultBinding() { + const node = this.startNode(); + node.ImportedBinding = this.parseBindingIdentifier(); + return this.finishNode(node, 'ImportedDefaultBinding'); + } + + // NameSpaceImport : + // `*` `as` ImportedBinding + parseNameSpaceImport() { + const node = this.startNode(); + this.expect(Token.MUL); + this.expect('as'); + node.ImportedBinding = this.parseBindingIdentifier(); + return this.finishNode(node, 'NameSpaceImport'); + } + + // NamedImports : + // `{` `}` + // `{` ImportsList `}` + // `{` ImportsList `,` `}` + parseNamedImports() { + const node = this.startNode(); + node.ImportsList = []; + while (!this.eat(Token.RBRACE)) { + node.ImportsList.push(this.parseImportSpecifier()); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'NamedImports'); + } + + // ImportSpecifier : + // ImportedBinding + // IdentifierName `as` ImportedBinding + // ModuleExportName `as` ImportedBinding + parseImportSpecifier() { + const node = this.startNode(); + if (this.feature('arbitrary-module-namespace-names') && this.test(Token.STRING)) { + node.ModuleExportName = this.parseModuleExportName(); + this.expect('as'); + node.ImportedBinding = this.parseBindingIdentifier(); + } else { + const name = this.parseIdentifierName(); + if (this.eat('as')) { + node.IdentifierName = name; + node.ImportedBinding = this.parseBindingIdentifier(); + } else { + node.ImportedBinding = name; + node.ImportedBinding.type = 'BindingIdentifier'; + if (isKeywordRaw(node.ImportedBinding.name)) { + this.raiseEarly('UnexpectedToken', node.ImportedBinding); + } + if (node.ImportedBinding.name === 'eval' || node.ImportedBinding.name === 'arguments') { + this.raiseEarly('UnexpectedToken', node.ImportedBinding); + } + } + } + return this.finishNode(node, 'ImportSpecifier'); + } + + // ExportDeclaration : + // `export` ExportFromClause FromClause `;` + // `export` NamedExports `;` + // `export` VariableStatement + // `export` Declaration + // `export` `default` HoistableDeclaration + // `export` `default` ClassDeclaration + // `export` `default` AssignmentExpression `;` + // + // ExportFromClause : + // `*` + // `*` as IdentifierName + // `*` as ModuleExportName + // NamedExports + parseExportDeclaration() { + const node = this.startNode(); + this.expect(Token.EXPORT); + node.default = this.eat(Token.DEFAULT); + if (node.default) { + switch (this.peek().type) { + case Token.FUNCTION: + node.HoistableDeclaration = this.scope.with({ default: true }, () => this.parseFunctionDeclaration(FunctionKind.NORMAL)); + break; + case Token.CLASS: + node.ClassDeclaration = this.scope.with({ default: true }, () => this.parseClassDeclaration()); + break; + default: + if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) { + node.HoistableDeclaration = this.scope.with({ default: true }, () => this.parseFunctionDeclaration(FunctionKind.ASYNC)); + } else { + node.AssignmentExpression = this.parseAssignmentExpression(); + this.semicolon(); + } + break; + } + if (this.scope.exports.has('default')) { + this.raiseEarly('AlreadyDeclared', node); + } else { + this.scope.exports.add('default'); + } + } else { + switch (this.peek().type) { + case Token.CONST: + node.Declaration = this.parseLexicalDeclaration(); + this.scope.declare(node.Declaration, 'export'); + break; + case Token.CLASS: + node.Declaration = this.parseClassDeclaration(); + this.scope.declare(node.Declaration, 'export'); + break; + case Token.FUNCTION: + node.Declaration = this.parseHoistableDeclaration(); + this.scope.declare(node.Declaration, 'export'); + break; + case Token.VAR: + node.VariableStatement = this.parseVariableStatement(); + this.scope.declare(node.VariableStatement, 'export'); + break; + case Token.LBRACE: { + const NamedExports = this.parseNamedExports(); + if (this.test('from')) { + node.ExportFromClause = NamedExports; + node.FromClause = this.parseFromClause(); + } else { + node.NamedExports = NamedExports; + this.scope.checkUndefinedExports(node.NamedExports); + } + this.semicolon(); + break; + } + case Token.MUL: { + const inner = this.startNode(); + this.next(); + if (this.eat('as')) { + if (this.feature('arbitrary-module-namespace-names') && this.test(Token.STRING)) { + inner.ModuleExportName = this.parseModuleExportName(); + this.scope.declare(inner.ModuleExportName, 'export'); + } else { + inner.IdentifierName = this.parseIdentifierName(); + this.scope.declare(inner.IdentifierName, 'export'); + } + } + node.ExportFromClause = this.finishNode(inner, 'ExportFromClause'); + node.FromClause = this.parseFromClause(); + this.semicolon(); + break; + } + default: + if (this.test('let')) { + node.Declaration = this.parseLexicalDeclaration(); + this.scope.declare(node.Declaration, 'export'); + } else if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) { + node.Declaration = this.parseHoistableDeclaration(); + this.scope.declare(node.Declaration, 'export'); + } else { + this.unexpected(); + } + } + } + return this.finishNode(node, 'ExportDeclaration'); + } + + // NamedExports : + // `{` `}` + // `{` ExportsList `}` + // `{` ExportsList `,` `}` + parseNamedExports() { + const node = this.startNode(); + this.expect(Token.LBRACE); + node.ExportsList = []; + while (!this.eat(Token.RBRACE)) { + node.ExportsList.push(this.parseExportSpecifier()); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'NamedExports'); + } + + // ExportSpecifier : + // IdentifierName + // IdentifierName `as` IdentifierName + // IdentifierName `as` ModuleExportName + parseExportSpecifier() { + const node = this.startNode(); + const name = this.parseIdentifierName(); + if (this.eat('as')) { + if (this.feature('arbitrary-module-namespace-names') && this.test(Token.STRING)) { + node.IdentifierName = name; + node.ModuleExportName = this.parseModuleExportName(); + this.scope.declare(node.ModuleExportName, 'export'); + } else { + node.IdentifierName_a = name; + node.IdentifierName_b = this.parseIdentifierName(); + this.scope.declare(node.IdentifierName_b, 'export'); + } + } else { + node.IdentifierName = name; + this.scope.declare(name, 'export'); + } + return this.finishNode(node, 'ExportSpecifier'); + } + + // ModuleExportName : StringLiteral + parseModuleExportName() { + const literal = this.parseStringLiteral(); + if (!IsStringValidUnicode(StringValue(literal))) { + this.raiseEarly('ModuleExportNameInvalidUnicode', literal); + } + return literal; + } + + // FromClause : + // `from` ModuleSpecifier + parseFromClause() { + this.expect('from'); + return this.parseStringLiteral(); + } +} diff --git a/engine262/src/parser/tokens.mjs b/engine262/src/parser/tokens.mjs new file mode 100644 index 0000000..4b4eaa6 --- /dev/null +++ b/engine262/src/parser/tokens.mjs @@ -0,0 +1,175 @@ +const MaybeAssignTokens = [ + // Logical + ['NULLISH', '??', 3], + ['OR', '||', 4], + ['AND', '&&', 5], + + // Binop + ['BIT_OR', '|', 6], + ['BIT_XOR', '^', 7], + ['BIT_AND', '&', 8], + ['SHL', '<<', 11], + ['SAR', '>>', 11], + ['SHR', '>>>', 11], + ['MUL', '*', 13], + ['DIV', '/', 13], + ['MOD', '%', 13], + ['EXP', '**', 14], + + // Unop + ['ADD', '+', 12], + ['SUB', '-', 12], +]; + +export const RawTokens = [ + // BEGIN PropertyOrCall + // BEGIN Member + // BEGIN Template + ['TEMPLATE', '`'], + // END Template + + // BEGIN Property + ['PERIOD', '.'], + ['LBRACK', '['], + // END Property + // END Member + ['OPTIONAL', '?.'], + ['LPAREN', '('], + // END PropertyOrCall + ['RPAREN', ')'], + ['RBRACK', ']'], + ['LBRACE', '{'], + ['COLON', ':'], + ['ELLIPSIS', '...'], + ['CONDITIONAL', '?'], + // BEGIN AutoSemicolon + ['SEMICOLON', ';'], + ['RBRACE', '}'], + + ['EOS', 'EOS'], + // END AutoSemicolon + + // BEGIN ArrowOrAssign + ['ARROW', '=>'], + // BEGIN Assign + ['ASSIGN', '=', 2], + ...MaybeAssignTokens.map((t) => [`ASSIGN_${t[0]}`, `${t[1]}=`, 2]), + // END Assign + // END ArrowOrAssign + + // Binary operators by precidence + ['COMMA', ',', 1], + + ...MaybeAssignTokens, + + ['NOT', '!'], + ['BIT_NOT', '~'], + ['DELETE', 'delete'], + ['TYPEOF', 'typeof'], + ['VOID', 'void'], + + // BEGIN IsCountOp + ['INC', '++'], + ['DEC', '--'], + // END IsCountOp + // END IsUnaryOrCountOp + + ['EQ', '==', 9], + ['EQ_STRICT', '===', 9], + ['NE', '!=', 9], + ['NE_STRICT', '!==', 9], + ['LT', '<', 10], + ['GT', '>', 10], + ['LTE', '<=', 10], + ['GTE', '>=', 10], + ['INSTANCEOF', 'instanceof', 10], + ['IN', 'in', 10], + + ['BREAK', 'break'], + ['CASE', 'case'], + ['CATCH', 'catch'], + ['CONTINUE', 'continue'], + ['DEBUGGER', 'debugger'], + ['DEFAULT', 'default'], + // DELETE + ['DO', 'do'], + ['ELSE', 'else'], + ['FINALLY', 'finally'], + ['FOR', 'for'], + ['FUNCTION', 'function'], + ['IF', 'if'], + // IN + // INSTANCEOF + ['NEW', 'new'], + ['RETURN', 'return'], + ['SWITCH', 'switch'], + ['THROW', 'throw'], + ['TRY', 'try'], + // TYPEOF + ['VAR', 'var'], + // VOID + ['WHILE', 'while'], + ['WITH', 'with'], + ['THIS', 'this'], + + ['NULL', 'null'], + ['TRUE', 'true'], + ['FALSE', 'false'], + ['NUMBER', null], + ['STRING', null], + + // BEGIN Callable + ['SUPER', 'super'], + // BEGIN AnyIdentifier + ['IDENTIFIER', null], + ['AWAIT', 'await'], + ['YIELD', 'yield'], + // END AnyIdentifier + // END Callable + ['CLASS', 'class'], + ['CONST', 'const'], + ['EXPORT', 'export'], + ['EXTENDS', 'extends'], + ['IMPORT', 'import'], + + ['ENUM', 'enum'], + + ['ESCAPED_KEYWORD', null], +]; + +export const Token = RawTokens + .reduce((obj, [name], i) => { + obj[name] = i; + return obj; + }, {}); + +export const TokenNames = RawTokens.map((r) => r[0]); + +export const TokenPrecedence = RawTokens.map((r) => (r[2] || 0)); + +const Keywords = RawTokens + .filter(([name, raw]) => name.toLowerCase() === raw) + .map(([, raw]) => raw); + +export const KeywordLookup = Keywords + .reduce((obj, kw) => { + obj[kw] = Token[kw.toUpperCase()]; + return obj; + }, Object.create(null)); + +const KeywordTokens = new Set(Object.values(KeywordLookup)); + +const isInRange = (t, l, h) => t >= l && t <= h; +export const isAutomaticSemicolon = (t) => isInRange(t, Token.SEMICOLON, Token.EOS); +export const isMember = (t) => isInRange(t, Token.TEMPLATE, Token.LBRACK); +export const isPropertyOrCall = (t) => isInRange(t, Token.TEMPLATE, Token.LPAREN); +export const isKeyword = (t) => KeywordTokens.has(t); +export const isKeywordRaw = (s) => Keywords.includes(s); + +const ReservedWordsStrict = [ + 'implements', 'interface', 'let', + 'package', 'private', 'protected', + 'public', 'static', 'yield', +]; + +export const isReservedWordStrict = (s) => ReservedWordsStrict.includes(s); diff --git a/engine262/src/runtime-semantics/AdditiveExpression.mjs b/engine262/src/runtime-semantics/AdditiveExpression.mjs new file mode 100644 index 0000000..add9220 --- /dev/null +++ b/engine262/src/runtime-semantics/AdditiveExpression.mjs @@ -0,0 +1,27 @@ +import { Q } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mjs'; + +// #sec-addition-operator-plus-runtime-semantics-evaluation +// AdditiveExpression : AdditiveExpression + MultiplicativeExpression +function* Evaluate_AdditiveExpression_Plus({ AdditiveExpression, MultiplicativeExpression }) { + // 1. Return ? EvaluateStringOrNumericBinaryExpression(AdditiveExpression, +, MultiplicativeExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(AdditiveExpression, '+', MultiplicativeExpression)); +} + +// #sec-subtraction-operator-minus-runtime-semantics-evaluation +function* Evaluate_AdditiveExpression_Minus({ AdditiveExpression, MultiplicativeExpression }) { + // 1. Return ? EvaluateStringOrNumericBinaryExpression(AdditiveExpression, -, MultiplicativeExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(AdditiveExpression, '-', MultiplicativeExpression)); +} + +export function* Evaluate_AdditiveExpression(AdditiveExpression) { + switch (AdditiveExpression.operator) { + case '+': + return yield* Evaluate_AdditiveExpression_Plus(AdditiveExpression); + case '-': + return yield* Evaluate_AdditiveExpression_Minus(AdditiveExpression); + default: + throw new OutOfRange('Evaluate_AdditiveExpression', AdditiveExpression); + } +} diff --git a/engine262/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mjs b/engine262/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mjs new file mode 100644 index 0000000..1fb754a --- /dev/null +++ b/engine262/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mjs @@ -0,0 +1,56 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, TypeNumeric, Value } from '../value.mjs'; +import { ToNumeric, ToPrimitive, ToString } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-applystringornumericbinaryoperator +export function ApplyStringOrNumericBinaryOperator(lval, opText, rval) { + // 1. If opText is +, then + if (opText === '+') { + // a. Let lprim be ? ToPrimitive(lval). + const lprim = Q(ToPrimitive(lval)); + // b. Let rprim be ? ToPrimitive(rval). + const rprim = Q(ToPrimitive(rval)); + // c. If Type(lprim) is String or Type(rprim) is String, then + if (Type(lprim) === 'String' || Type(rprim) === 'String') { + // i. Let lstr be ? ToString(lprim). + const lstr = Q(ToString(lprim)); + // ii. Let rstr be ? ToString(rprim). + const rstr = Q(ToString(rprim)); + // iii. Return the string-concatenation of lstr and rstr. + return new Value(lstr.stringValue() + rstr.stringValue()); + } + // d. Set lval to lprim. + lval = lprim; + // e. Set rval to rprim. + rval = rprim; + } + // 2. NOTE: At this point, it must be a numeric operation. + // 3. Let lnum be ? ToNumeric(lval). + const lnum = Q(ToNumeric(lval)); + // 4. Let rnum be ? ToNumeric(rval). + const rnum = Q(ToNumeric(rval)); + // 5. If Type(lnum) is different from Type(rnum), throw a TypeError exception. + if (Type(lnum) !== Type(rnum)) { + return surroundingAgent.Throw('TypeError', 'CannotMixBigInts'); + } + // 6. Let T be Type(lnum). + const T = TypeNumeric(lnum); + // 7. Let operation be the abstract operation associated with opText in the following table: + const operation = { + '**': T.exponentiate, + '*': T.multiply, + '/': T.divide, + '%': T.remainder, + '+': T.add, + '-': T.subtract, + '<<': T.leftShift, + '>>': T.signedRightShift, + '>>>': T.unsignedRightShift, + '&': T.bitwiseAND, + '^': T.bitwiseXOR, + '|': T.bitwiseOR, + }[opText]; + // 8. Return ? operation(lnum, rnum). + return Q(operation(lnum, rnum)); +} diff --git a/engine262/src/runtime-semantics/ArgumentListEvaluation.mjs b/engine262/src/runtime-semantics/ArgumentListEvaluation.mjs new file mode 100644 index 0000000..14cbc37 --- /dev/null +++ b/engine262/src/runtime-semantics/ArgumentListEvaluation.mjs @@ -0,0 +1,177 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Descriptor } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q, X } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + Assert, + ArrayCreate, + SetIntegrityLevel, + ToString, + GetIterator, + GetValue, + IteratorStep, + IteratorValue, +} from '../abstract-ops/all.mjs'; +import { TemplateStrings } from '../static-semantics/all.mjs'; + +// #sec-gettemplateobjec +function GetTemplateObject(templateLiteral) { + // 1. Let realm be the current Realm Record. + const realm = surroundingAgent.currentRealmRecord; + // 2. Let templateRegistry be realm.[[TemplateMap]]. + const templateRegistry = realm.TemplateMap; + // 3. For each element e of templateRegistry, do + for (const e of templateRegistry) { + // a. If e.[[Site]] is the same Parse Node as templateLiteral, then + if (e.Site === templateLiteral) { + // b. Return e.[[Array]]. + return e.Array; + } + } + // 4. Let rawStrings be TemplateStrings of templateLiteral with argument true. + const rawStrings = TemplateStrings(templateLiteral, true); + // 5. Let cookedStrings be TemplateStrings of templateLiteral with argument false. + const cookedStrings = TemplateStrings(templateLiteral, false); + // 6. Let count be the number of elements in the List cookedStrings. + const count = cookedStrings.length; + // 7. Assert: count ≤ 232 - 1. + Assert(count < (2 ** 32) - 1); + // 8. Let template be ! ArrayCreate(count). + const template = X(ArrayCreate(new Value(count))); + // 9. Let template be ! ArrayCreate(count). + const rawObj = X(ArrayCreate(new Value(count))); + // 10. Let index be 0. + let index = 0; + // 11. Repeat, while index < count + while (index < count) { + // a. Let prop be ! ToString(index). + const prop = X(ToString(new Value(index))); + // b. Let cookedValue be the String value cookedStrings[index]. + const cookedValue = cookedStrings[index]; + // c. Call template.[[DefineOwnProperty]](prop, PropertyDescriptor { [[Value]]: cookedValue, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }). + X(template.DefineOwnProperty(prop, Descriptor({ + Value: cookedValue, + Writable: Value.false, + Enumerable: Value.true, + Configurable: Value.false, + }))); + // d. Let rawValue be the String value rawStrings[index]. + const rawValue = rawStrings[index]; + // e. Call rawObj.[[DefineOwnProperty]](prop, PropertyDescriptor { [[Value]]: rawValue, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }). + X(rawObj.DefineOwnProperty(prop, Descriptor({ + Value: rawValue, + Writable: Value.false, + Enumerable: Value.true, + Configurable: Value.false, + }))); + // f. Call rawObj.[[DefineOwnProperty]](prop, PropertyDescriptor { [[Value]]: rawValue, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }). + index += 1; + } + // 12. Perform SetIntegrityLevel(rawObj, frozen). + X(SetIntegrityLevel(rawObj, 'frozen')); + // 13. Perform SetIntegrityLevel(rawObj, frozen). + X(template.DefineOwnProperty(new Value('raw'), Descriptor({ + Value: rawObj, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 14. Perform SetIntegrityLevel(template, frozen). + X(SetIntegrityLevel(template, 'frozen')); + // 15. Append the Record { [[Site]]: templateLiteral, [[Array]]: template } to templateRegistry. + templateRegistry.push({ Site: templateLiteral, Array: template }); + // 16. Return template. + return template; +} + +// 12.2.9.3 #sec-template-literals-runtime-semantics-argumentlistevaluation +// TemplateLiteral : NoSubstitutionTemplate +// +// https://github.com/tc39/ecma262/pull/1402 +// TemplateLiteral : SubstitutionTemplate +function* ArgumentListEvaluation_TemplateLiteral(TemplateLiteral) { + switch (true) { + case TemplateLiteral.TemplateSpanList.length === 1: { + const templateLiteral = TemplateLiteral; + const siteObj = GetTemplateObject(templateLiteral); + return [siteObj]; + } + + case TemplateLiteral.TemplateSpanList.length > 1: { + const templateLiteral = TemplateLiteral; + const siteObj = GetTemplateObject(templateLiteral); + const restSub = []; + for (const Expression of TemplateLiteral.ExpressionList) { + const subRef = yield* Evaluate(Expression); + const subValue = Q(GetValue(subRef)); + restSub.push(subValue); + } + return [siteObj, ...restSub]; + } + + default: + throw new OutOfRange('ArgumentListEvaluation_TemplateLiteral', TemplateLiteral); + } +} + +// 12.3.6.1 #sec-argument-lists-runtime-semantics-argumentlistevaluation +// Arguments : `(` `)` +// ArgumentList : +// AssignmentExpression +// `...` AssignmentExpression +// ArgumentList `,` AssignmentExpression +// ArgumentList `,` `...` AssignmentExpression +// +// (implicit) +// Arguments : +// `(` ArgumentList `)` +// `(` ArgumentList `,` `)` +function* ArgumentListEvaluation_Arguments(Arguments) { + const precedingArgs = []; + for (const element of Arguments) { + if (element.type === 'AssignmentRestElement') { + const { AssignmentExpression } = element; + // 2. Let spreadRef be the result of evaluating AssignmentExpression. + const spreadRef = yield* Evaluate(AssignmentExpression); + // 3. Let spreadObj be ? GetValue(spreadRef). + const spreadObj = Q(GetValue(spreadRef)); + // 4. Let iteratorRecord be ? GetIterator(spreadObj). + const iteratorRecord = Q(GetIterator(spreadObj)); + // 5. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iteratorRecord). + const next = Q(IteratorStep(iteratorRecord)); + // b. If next is false, return list. + if (next === Value.false) { + break; + } + // c. Let nextArg be ? IteratorValue(next). + const nextArg = Q(IteratorValue(next)); + // d. Append nextArg as the last element of list. + precedingArgs.push(nextArg); + } + } else { + const AssignmentExpression = element; + // 2. Let ref be the result of evaluating AssignmentExpression. + const ref = yield* Evaluate(AssignmentExpression); + // 3. Let arg be ? GetValue(ref). + const arg = Q(GetValue(ref)); + // 4. Append arg to the end of precedingArgs. + precedingArgs.push(arg); + // 5. Return precedingArgs. + } + } + return precedingArgs; +} + +export function ArgumentListEvaluation(ArgumentsOrTemplateLiteral) { + switch (true) { + case Array.isArray(ArgumentsOrTemplateLiteral): + return ArgumentListEvaluation_Arguments(ArgumentsOrTemplateLiteral); + case ArgumentsOrTemplateLiteral.type === 'TemplateLiteral': + return ArgumentListEvaluation_TemplateLiteral(ArgumentsOrTemplateLiteral); + default: + throw new OutOfRange('ArgumentListEvaluation', ArgumentsOrTemplateLiteral); + } +} diff --git a/engine262/src/runtime-semantics/ArrayLiteral.mjs b/engine262/src/runtime-semantics/ArrayLiteral.mjs new file mode 100644 index 0000000..5cfa2ac --- /dev/null +++ b/engine262/src/runtime-semantics/ArrayLiteral.mjs @@ -0,0 +1,96 @@ +import { Value } from '../value.mjs'; +import { + Set, + ArrayCreate, + GetValue, + GetIterator, + IteratorStep, + IteratorValue, + ToString, + CreateDataPropertyOrThrow, +} from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { ReturnIfAbrupt, Q, X } from '../completion.mjs'; + +// #sec-runtime-semantics-arrayaccumulation +// Elision : +// `,` +// Elision `,` +// ElementList : +// Elision? AssignmentExpression +// Elision? SpreadElement +// ElementList `,` Elision? AssignmentExpression +// ElementList : ElementList `,` Elision SpreadElement +// SpreadElement : +// `...` AssignmentExpression +function* ArrayAccumulation(ElementList, array, nextIndex) { + let postIndex = nextIndex; + for (const element of ElementList) { + switch (element.type) { + case 'Elision': + postIndex += 1; + Q(Set(array, new Value('length'), new Value(postIndex), Value.true)); + break; + case 'SpreadElement': + postIndex = Q(yield* ArrayAccumulation_SpreadElement(element, array, postIndex)); + break; + default: + postIndex = Q(yield* ArrayAccumulation_AssignmentExpression(element, array, postIndex)); + break; + } + } + return postIndex; +} + +// SpreadElement : `...` AssignmentExpression +function* ArrayAccumulation_SpreadElement({ AssignmentExpression }, array, nextIndex) { + // 1. Let spreadRef be the result of evaluating AssignmentExpression. + const spreadRef = yield* Evaluate(AssignmentExpression); + // 2. Let spreadObj be ? GetValue(spreadRef). + const spreadObj = Q(GetValue(spreadRef)); + // 3. Let iteratorRecord be ? GetIterator(spreadObj). + const iteratorRecord = Q(GetIterator(spreadObj)); + // 4. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iteratorRecord). + const next = Q(IteratorStep(iteratorRecord)); + // b. If next is false, return nextIndex. + if (next === Value.false) { + return nextIndex; + } + // c. Let nextValue be ? IteratorValue(next). + const nextValue = Q(IteratorValue(next)); + // d. Perform ! CreateDataPropertyOrThrow(array, ! ToString(nextIndex), nextValue). + X(CreateDataPropertyOrThrow(array, X(ToString(new Value(nextIndex))), nextValue)); + // e. Set nextIndex to nextIndex + 1. + nextIndex += 1; + } +} + + +function* ArrayAccumulation_AssignmentExpression(AssignmentExpression, array, nextIndex) { + // 2. Let initResult be the result of evaluating AssignmentExpression. + const initResult = yield* Evaluate(AssignmentExpression); + // 3. Let initValue be ? GetValue(initResult). + const initValue = Q(GetValue(initResult)); + // 4. Let created be ! CreateDataPropertyOrThrow(array, ! ToString(nextIndex), initValue). + const _created = X(CreateDataPropertyOrThrow(array, X(ToString(new Value(nextIndex))), initValue)); + // 5. Return nextIndex + 1. + return nextIndex + 1; +} + +// #sec-array-initializer-runtime-semantics-evaluation +// ArrayLiteral : +// `[` Elision `]` +// `[` ElementList `]` +// `[` ElementList `,` Elision `]` +export function* Evaluate_ArrayLiteral({ ElementList }) { + // 1. Let array be ! ArrayCreate(0). + const array = X(ArrayCreate(new Value(0))); + // 2. Let len be the result of performing ArrayAccumulation for ElementList with arguments array and 0. + const len = yield* ArrayAccumulation(ElementList, array, 0); + // 3. ReturnIfAbrupt(len). + ReturnIfAbrupt(len); + // 4. Return array. + return array; +} diff --git a/engine262/src/runtime-semantics/ArrowFunction.mjs b/engine262/src/runtime-semantics/ArrowFunction.mjs new file mode 100644 index 0000000..9b148c6 --- /dev/null +++ b/engine262/src/runtime-semantics/ArrowFunction.mjs @@ -0,0 +1,7 @@ +import { Value } from '../value.mjs'; +import { NamedEvaluation } from './all.mjs'; + +export function Evaluate_ArrowFunction(ArrowFunction) { + // 1. Return the result of performing NamedEvaluation for this ArrowFunction with argument "". + return NamedEvaluation(ArrowFunction, new Value('')); +} diff --git a/engine262/src/runtime-semantics/AssignmentExpression.mjs b/engine262/src/runtime-semantics/AssignmentExpression.mjs new file mode 100644 index 0000000..008a698 --- /dev/null +++ b/engine262/src/runtime-semantics/AssignmentExpression.mjs @@ -0,0 +1,278 @@ +import { Value } from '../value.mjs'; +import { Q, X, ReturnIfAbrupt } from '../completion.mjs'; +import { + GetReferencedName, + GetValue, + PutValue, + ToBoolean, +} from '../abstract-ops/all.mjs'; +import { + IsAnonymousFunctionDefinition, + IsIdentifierRef, +} from '../static-semantics/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + NamedEvaluation, + ApplyStringOrNumericBinaryOperator, + DestructuringAssignmentEvaluation, +} from './all.mjs'; + +// #sec-destructuring-assignment +export function refineLeftHandSideExpression(node, type) { + switch (node.type) { + case 'ArrayLiteral': { + const refinement = { + type: 'ArrayAssignmentPattern', + AssignmentElementList: [], + AssignmentRestElement: undefined, + }; + node.ElementList.forEach((n) => { + switch (n.type) { + case 'SpreadElement': + refinement.AssignmentRestElement = { + type: 'AssignmentRestElement', + DestructuringAssignmentTarget: n.AssignmentExpression, + }; + break; + case 'ArrayLiteral': + case 'ObjectLiteral': + refinement.AssignmentElementList.push({ + type: 'AssignmentElement', + DestructuringAssignmentTarget: n, + Initializer: null, + }); + break; + default: + refinement.AssignmentElementList.push(refineLeftHandSideExpression(n, 'array')); + break; + } + }); + return refinement; + } + case 'ObjectLiteral': { + const refined = { + type: 'ObjectAssignmentPattern', + AssignmentPropertyList: [], + AssignmentRestProperty: undefined, + }; + node.PropertyDefinitionList.forEach((p) => { + if (p.PropertyName === null && p.AssignmentExpression) { + refined.AssignmentRestProperty = { + type: 'AssignmentRestProperty', + DestructuringAssignmentTarget: p.AssignmentExpression, + }; + } else { + refined.AssignmentPropertyList.push(refineLeftHandSideExpression(p, 'object')); + } + }); + return refined; + } + case 'PropertyDefinition': + return { + type: 'AssignmentProperty', + PropertyName: node.PropertyName, + AssignmentElement: node.AssignmentExpression.type === 'AssignmentExpression' + ? { + type: 'AssignmentElement', + DestructuringAssignmentTarget: node.AssignmentExpression.LeftHandSideExpression, + Initializer: node.AssignmentExpression.AssignmentExpression, + } + : { + type: 'AssignmentElement', + DestructuringAssignmentTarget: node.AssignmentExpression, + Initializer: undefined, + }, + }; + case 'IdentifierReference': + if (type === 'array') { + return { + type: 'AssignmentElement', + DestructuringAssignmentTarget: node, + Initializer: undefined, + }; + } else { + return { + type: 'AssignmentProperty', + IdentifierReference: node, + Initializer: undefined, + }; + } + case 'MemberExpression': + return { + type: 'AssignmentElement', + DestructuringAssignmentTarget: node, + Initializer: undefined, + }; + case 'CoverInitializedName': + return { + type: 'AssignmentProperty', + IdentifierReference: node.IdentifierReference, + Initializer: node.Initializer, + }; + case 'AssignmentExpression': + return { + type: 'AssignmentElement', + DestructuringAssignmentTarget: node.LeftHandSideExpression, + Initializer: node.AssignmentExpression, + }; + case 'Elision': + return { type: 'Elision' }; + default: + throw new OutOfRange('refineLeftHandSideExpression', node.type); + } +} + +// #sec-assignment-operators-runtime-semantics-evaluation +// AssignmentExpression : +// LeftHandSideExpression `=` AssignmentExpression +// LeftHandSideExpression AssignmentOperator AssignmentExpression +// LeftHandSideExpression `&&=` AssignmentExpression +// LeftHandSideExpression `||=` AssignmentExpression +// LeftHandSideExpression `??=` AssignmentExpression +export function* Evaluate_AssignmentExpression({ + LeftHandSideExpression, AssignmentOperator, AssignmentExpression, +}) { + if (AssignmentOperator === '=') { + // 1. If LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral, then + if (LeftHandSideExpression.type !== 'ObjectLiteral' && LeftHandSideExpression.type !== 'ArrayLiteral') { + // a. Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // b. ReturnIfAbrupt(lref). + ReturnIfAbrupt(lref); + // c. If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then + let rval; + if (IsAnonymousFunctionDefinition(AssignmentExpression) && IsIdentifierRef(LeftHandSideExpression)) { + // i. Let rval be NamedEvaluation of AssignmentExpression with argument GetReferencedName(lref). + rval = yield* NamedEvaluation(AssignmentExpression, GetReferencedName(lref)); + } else { // d. Else, + // i. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // ii. Let rval be ? GetValue(rref). + rval = Q(GetValue(rref)); + } + // e. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // f. Return rval. + return rval; + } + // 2. Let assignmentPattern be the AssignmentPattern that is covered by LeftHandSideExpression. + const assignmentPattern = refineLeftHandSideExpression(LeftHandSideExpression); + // 3. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // 3. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + // 4. Perform ? DestructuringAssignmentEvaluation of assignmentPattern using rval as the argument. + Q(yield* DestructuringAssignmentEvaluation(assignmentPattern, rval)); + // 5. Return rval. + return rval; + } else if (AssignmentOperator === '&&=') { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let lbool be ! ToBoolean(lval). + const lbool = X(ToBoolean(lval)); + // 4. If lbool is false, return lval. + if (lbool === Value.false) { + return lval; + } + let rval; + // 5. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and IsIdentifierRef of LeftHandSideExpression is true, then + if (IsAnonymousFunctionDefinition(AssignmentExpression) && IsIdentifierRef(LeftHandSideExpression)) { + // a. Let rval be NamedEvaluation of AssignmentExpression with argument GetReferencedName(lref). + rval = yield* NamedEvaluation(AssignmentExpression, GetReferencedName(lref)); + } else { // 6. Else, + // a. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // b. Let rval be ? GetValue(rref). + rval = Q(GetValue(rref)); + } + // 7. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // 8. Return rval. + return rval; + } else if (AssignmentOperator === '||=') { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let lbool be ! ToBoolean(lval). + const lbool = X(ToBoolean(lval)); + // 4. If lbool is true, return lval. + if (lbool === Value.true) { + return lval; + } + let rval; + // 5. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and IsIdentifierRef of LeftHandSideExpression is true, then + if (IsAnonymousFunctionDefinition(AssignmentExpression) && IsIdentifierRef(LeftHandSideExpression)) { + // a. Let rval be NamedEvaluation of AssignmentExpression with argument GetReferencedName(lref). + rval = yield* NamedEvaluation(AssignmentExpression, GetReferencedName(lref)); + } else { // 6. Else, + // a. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // b. Let rval be ? GetValue(rref). + rval = Q(GetValue(rref)); + } + // 7. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // 8. Return rval. + return rval; + } else if (AssignmentOperator === '??=') { + // 1.Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. If lval is not undefined nor null, return lval. + if (lval !== Value.undefined && lval !== Value.null) { + return lval; + } + let rval; + // 4. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and IsIdentifierRef of LeftHandSideExpression is true, then + if (IsAnonymousFunctionDefinition(AssignmentExpression) && IsIdentifierRef(LeftHandSideExpression)) { + // a. Let rval be NamedEvaluation of AssignmentExpression with argument GetReferencedName(lref). + rval = yield* NamedEvaluation(AssignmentExpression, GetReferencedName(lref)); + } else { // 5. Else, + // a. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // b. Let rval be ? GetValue(rref). + rval = Q(GetValue(rref)); + } + // 6. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // 7. Return rval. + return rval; + } else { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // 4. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + // 5. Let assignmentOpText be the source text matched by AssignmentOperator. + const assignmentOpText = AssignmentOperator; + // 6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table: + const opText = { + '**=': '**', + '*=': '*', + '/=': '/', + '%=': '%', + '+=': '+', + '-=': '-', + '<<=': '<<', + '>>=': '>>', + '>>>=': '>>>', + '&=': '&', + '^=': '^', + '|=': '|', + }[assignmentOpText]; + // 7. Let r be ApplyStringOrNumericBinaryOperator(lval, opText, rval). + const r = ApplyStringOrNumericBinaryOperator(lval, opText, rval); + // 8. Perform ? PutValue(lref, r). + Q(PutValue(lref, r)); + // 9. Return r. + return r; + } +} diff --git a/engine262/src/runtime-semantics/AsyncArrowFunction.mjs b/engine262/src/runtime-semantics/AsyncArrowFunction.mjs new file mode 100644 index 0000000..4ccf5a4 --- /dev/null +++ b/engine262/src/runtime-semantics/AsyncArrowFunction.mjs @@ -0,0 +1,7 @@ +import { Value } from '../value.mjs'; +import { NamedEvaluation } from './all.mjs'; + +export function Evaluate_AsyncArrowFunction(AsyncArrowFunction) { + // 1. Return the result of performing NamedEvaluation for this ArrowFunction with argument "". + return NamedEvaluation(AsyncArrowFunction, new Value('')); +} diff --git a/engine262/src/runtime-semantics/AsyncFunctionExpression.mjs b/engine262/src/runtime-semantics/AsyncFunctionExpression.mjs new file mode 100644 index 0000000..02082d7 --- /dev/null +++ b/engine262/src/runtime-semantics/AsyncFunctionExpression.mjs @@ -0,0 +1,41 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + OrdinaryFunctionCreate, + SetFunctionName, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { X } from '../completion.mjs'; +import { NamedEvaluation } from './all.mjs'; + +// #sec-async-function-definitions-runtime-semantics-evaluation +// AsyncFunctionExpression : +// `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}` +// `async` `function` BindingIdentifier `(` FormalParameters `)` `{` AsyncFunctionBody `}` +export function* Evaluate_AsyncFunctionExpression(AsyncFunctionExpression) { + const { BindingIdentifier, FormalParameters, AsyncFunctionBody } = AsyncFunctionExpression; + if (!BindingIdentifier) { + // 1. Return the result of performing NamedEvaluation for this AsyncFunctionExpression with argument "". + return yield* NamedEvaluation(AsyncFunctionExpression, new Value('')); + } + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let funcEnv be ! NewDeclarativeEnvironment(scope). + const funcEnv = X(NewDeclarativeEnvironment(scope)); + // 3. Let name be StringValue of BindingIdentifier. + const name = StringValue(BindingIdentifier); + // 4. Perform ! funcEnv.CreateImmutableBinding(name, false). + X(funcEnv.CreateImmutableBinding(name, Value.false)); + // 5. Let sourceText be the source text matched by AsyncFunctionExpression. + const sourceText = sourceTextMatchedBy(AsyncFunctionExpression); + // 6. Let closure be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, FormalParameters, AsyncFunctionBody, non-lexical-this, funcEnv). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, FormalParameters, AsyncFunctionBody, 'non-lexical-this', funcEnv)); + // 7. Perform ! SetFunctionName(closure, name). + X(SetFunctionName(closure, name)); + // 8. Perform ! funcEnv.InitializeBinding(name, closure). + X(funcEnv.InitializeBinding(name, closure)); + // 9. Return closure. + return closure; +} diff --git a/engine262/src/runtime-semantics/AsyncGeneratorExpression.mjs b/engine262/src/runtime-semantics/AsyncGeneratorExpression.mjs new file mode 100644 index 0000000..96431bf --- /dev/null +++ b/engine262/src/runtime-semantics/AsyncGeneratorExpression.mjs @@ -0,0 +1,56 @@ +import { + DefinePropertyOrThrow, + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { NamedEvaluation } from './all.mjs'; + +// #sec-asyncgenerator-definitions-evaluation +// AsyncGeneratorExpression : +// `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +// `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +export function* Evaluate_AsyncGeneratorExpression(AsyncGeneratorExpression) { + const { BindingIdentifier, FormalParameters, AsyncGeneratorBody } = AsyncGeneratorExpression; + if (!BindingIdentifier) { + // 1. Return the result of performing NamedEvaluation for this AsyncGeneratorExpression with argument "". + return yield* NamedEvaluation(AsyncGeneratorExpression, new Value('')); + } + // 1. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let funcEnv be NewDeclarativeEnvironment(scope). + const funcEnv = NewDeclarativeEnvironment(scope); + // 3. Let name be StringValue of BindingIdentifier. + const name = StringValue(BindingIdentifier); + // 4. Perform funcEnv.CreateImmutableBinding(name, false). + funcEnv.CreateImmutableBinding(name, Value.false); + // 5. Let source text be the source textmatched by AsyncGeneratorExpression. + const sourceText = sourceTextMatchedBy(AsyncGeneratorExpression); + // 6. Let closure be OrdinaryFunctionCreate(%AsyncGenerator%, sourceText, FormalParameters, AsyncGeneratorBody, non-lexical-this, funcEnv). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, FormalParameters, AsyncGeneratorBody, 'non-lexical-this', funcEnv)); + // 7. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 8. Let prototype be OrdinaryObjectCreate(%AsyncGenerator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGenerator.prototype%')); + // 9. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow( + closure, + new Value('prototype'), + Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }), + )); + // 10. Perform funcEnv.InitializeBinding(name, closure). + funcEnv.InitializeBinding(name, closure); + // 11. Return closure. + return closure; +} diff --git a/engine262/src/runtime-semantics/AwaitExpression.mjs b/engine262/src/runtime-semantics/AwaitExpression.mjs new file mode 100644 index 0000000..08b44e2 --- /dev/null +++ b/engine262/src/runtime-semantics/AwaitExpression.mjs @@ -0,0 +1,14 @@ +import { GetValue } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Await, Q } from '../completion.mjs'; + +// #sec-async-function-definitions-runtime-semantics-evaluation +// AwaitExpression : `await` UnaryExpression +export function* Evaluate_AwaitExpression({ UnaryExpression }) { + // 1. Let exprRef be the result of evaluating UnaryExpression. + const exprRef = yield* Evaluate(UnaryExpression); + // 2. Let value be ? GetValue(exprRef). + const value = Q(GetValue(exprRef)); + // 3. Return ? Await(value). + return Q(yield* Await(value)); +} diff --git a/engine262/src/runtime-semantics/BindingInitialization.mjs b/engine262/src/runtime-semantics/BindingInitialization.mjs new file mode 100644 index 0000000..5302257 --- /dev/null +++ b/engine262/src/runtime-semantics/BindingInitialization.mjs @@ -0,0 +1,88 @@ +import { Type, Value } from '../value.mjs'; +import { + Assert, + PutValue, + ResolveBinding, + RequireObjectCoercible, + GetIterator, + IteratorClose, +} from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { NormalCompletion, Q } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + IteratorBindingInitialization_ArrayBindingPattern, + PropertyBindingInitialization, + RestBindingInitialization, +} from './all.mjs'; + +// #sec-initializeboundname +export function InitializeBoundName(name, value, environment) { + // 1. Assert: Either Type(name) is String or name is ~default~. + Assert(name === 'default' || Type(name) === 'String'); + // 2. If environment is not undefined, then + if (environment !== Value.undefined) { + // a. Perform environment.InitializeBinding(name, value). + environment.InitializeBinding(name, value); + // b. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); + } else { + // a. Let lhs be ResolveBinding(name). + const lhs = ResolveBinding(name, undefined, false); + // b. Return ? PutValue(lhs, value). + return Q(PutValue(lhs, value)); + } +} + +// ObjectBindingPattern : +// `{` `}` +// `{` BindingPropertyList `}` +// `{` BindingRestProperty `}` +// `{` BindingPropertyList `,` BindingRestProperty `}` +function* BindingInitialization_ObjectBindingPattern({ BindingPropertyList, BindingRestProperty }, value, environment) { + // 1. Perform ? PropertyBindingInitialization for BindingPropertyList using value and environment as the arguments. + const excludedNames = Q(yield* PropertyBindingInitialization(BindingPropertyList, value, environment)); + if (BindingRestProperty) { + Q(RestBindingInitialization(BindingRestProperty, value, environment, excludedNames)); + } + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} + +export function* BindingInitialization(node, value, environment) { + switch (node.type) { + case 'ForBinding': + if (node.BindingIdentifier) { + return yield* BindingInitialization(node.BindingIdentifier, value, environment); + } + return yield* BindingInitialization(node.BindingPattern, value, environment); + case 'ForDeclaration': + return yield* BindingInitialization(node.ForBinding, value, environment); + case 'BindingIdentifier': { + // 1. Let name be StringValue of Identifier. + const name = StringValue(node); + // 2. Return ? InitializeBoundName(name, value, environment). + return Q(InitializeBoundName(name, value, environment)); + } + case 'ObjectBindingPattern': { + // 1. Perform ? RequireObjectCoercible(value). + Q(RequireObjectCoercible(value)); + // 2. Return the result of performing BindingInitialization for ObjectBindingPattern using value and environment as arguments. + return yield* BindingInitialization_ObjectBindingPattern(node, value, environment); + } + case 'ArrayBindingPattern': { + // 1. Let iteratorRecord be ? GetIterator(value). + const iteratorRecord = Q(GetIterator(value)); + // 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. + const result = yield* IteratorBindingInitialization_ArrayBindingPattern(node, iteratorRecord, environment); + // 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result). + if (iteratorRecord.Done === Value.false) { + return Q(IteratorClose(iteratorRecord, result)); + } + // 4. Return result. + return result; + } + default: + throw new OutOfRange('BindingInitialization', node); + } +} diff --git a/engine262/src/runtime-semantics/BitwiseOperators.mjs b/engine262/src/runtime-semantics/BitwiseOperators.mjs new file mode 100644 index 0000000..bb43fe1 --- /dev/null +++ b/engine262/src/runtime-semantics/BitwiseOperators.mjs @@ -0,0 +1,12 @@ +import { Q } from '../completion.mjs'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mjs'; + +// #sec-binary-bitwise-operators-runtime-semantics-evaluation +// BitwiseANDExpression : BitwiseANDExpression `&` EqualityExpression +// BitwiseXORExpression : BitwiseXORExpression `^` BitwiseANDExpression +// BitwiseORExpression : BitwiseORExpression `|` BitwiseXORExpression +// The production A : A @ B, where @ is one of the bitwise operators in the +// productions above, is evaluated as follows: +export function* Evaluate_BinaryBitwiseExpression({ A, operator, B }) { + return Q(yield* EvaluateStringOrNumericBinaryExpression(A, operator, B)); +} diff --git a/engine262/src/runtime-semantics/Block.mjs b/engine262/src/runtime-semantics/Block.mjs new file mode 100644 index 0000000..1aafd97 --- /dev/null +++ b/engine262/src/runtime-semantics/Block.mjs @@ -0,0 +1,70 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { NewDeclarativeEnvironment, DeclarativeEnvironmentRecord } from '../environment.mjs'; +import { Assert } from '../abstract-ops/all.mjs'; +import { + LexicallyScopedDeclarations, + IsConstantDeclaration, + BoundNames, +} from '../static-semantics/all.mjs'; +import { X, NormalCompletion } from '../completion.mjs'; +import { Evaluate_StatementList, InstantiateFunctionObject } from './all.mjs'; + +// #sec-blockdeclarationinstantiation +export function BlockDeclarationInstantiation(code, env) { + // 1. Assert: env is a declarative Environment Record. + Assert(env instanceof DeclarativeEnvironmentRecord); + // 2. Let declarations be the LexicallyScopedDeclarations of code. + const declarations = LexicallyScopedDeclarations(code); + // 3. For each element d in declarations, do + for (const d of declarations) { + // a. For each element dn of the BoundNames of d, do + for (const dn of BoundNames(d)) { + // i. If IsConstantDeclaration of d is true, then + if (IsConstantDeclaration(d)) { + // 1. Perform ! env.CreateImmutableBinding(dn, true). + X(env.CreateImmutableBinding(dn, Value.true)); + } else { // ii. Else, + // 1. Perform ! env.CreateMutableBinding(dn, false). + X(env.CreateMutableBinding(dn, false)); + } + // b. If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then + if (d.type === 'FunctionDeclaration' + || d.type === 'GeneratorDeclaration' + || d.type === 'AsyncFunctionDeclaration' + || d.type === 'AsyncGeneratorDeclaration') { + // i. Let fn be the sole element of the BoundNames of d. + const fn = BoundNames(d)[0]; + // ii. Let fo be InstantiateFunctionObject of d with argument env. + const fo = InstantiateFunctionObject(d, env); + // iii. Perform env.InitializeBinding(fn, fo). + env.InitializeBinding(fn, fo); + } + } + } +} + +// #sec-block-runtime-semantics-evaluation +// Block : +// `{` `}` +// `{` StatementList `}` +export function* Evaluate_Block({ StatementList }) { + if (StatementList.length === 0) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let blockEnv be NewDeclarativeEnvironment(oldEnv). + const blockEnv = NewDeclarativeEnvironment(oldEnv); + // 3. Perform BlockDeclarationInstantiation(StatementList, blockEnv). + BlockDeclarationInstantiation(StatementList, blockEnv); + // 4. Set the running execution context's LexicalEnvironment to blockEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = blockEnv; + // 5. Let blockValue be the result of evaluating StatementList. + const blockValue = yield* Evaluate_StatementList(StatementList); + // 6. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 7. Return blockValue. + return blockValue; +} diff --git a/engine262/src/runtime-semantics/BreakStatement.mjs b/engine262/src/runtime-semantics/BreakStatement.mjs new file mode 100644 index 0000000..6ee7cd5 --- /dev/null +++ b/engine262/src/runtime-semantics/BreakStatement.mjs @@ -0,0 +1,17 @@ +import { Completion } from '../completion.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; + +// #sec-break-statement-runtime-semantics-evaluation +// BreakStatement : +// `break` `;` +// `break` LabelIdentifier `;` +export function Evaluate_BreakStatement({ LabelIdentifier }) { + if (!LabelIdentifier) { + // 1. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }. + return new Completion({ Type: 'break', Value: undefined, Target: undefined }); + } + // 1. Let label be the StringValue of LabelIdentifier. + const label = StringValue(LabelIdentifier); + // 2. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: label }. + return new Completion({ Type: 'break', Value: undefined, Target: label }); +} diff --git a/engine262/src/runtime-semantics/BreakableStatement.mjs b/engine262/src/runtime-semantics/BreakableStatement.mjs new file mode 100644 index 0000000..c373f6c --- /dev/null +++ b/engine262/src/runtime-semantics/BreakableStatement.mjs @@ -0,0 +1,17 @@ +import { ValueSet } from '../helpers.mjs'; +import { LabelledEvaluation } from './all.mjs'; + +// #sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : +// IterationStatement +// SwitchStatement +// +// IterationStatement : +// (DoStatement) +// (WhileStatement) +export function Evaluate_BreakableStatement(BreakableStatement) { + // 1. Let newLabelSet be a new empty List. + const newLabelSet = new ValueSet(); + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return LabelledEvaluation(BreakableStatement, newLabelSet); +} diff --git a/engine262/src/runtime-semantics/CallExpression.mjs b/engine262/src/runtime-semantics/CallExpression.mjs new file mode 100644 index 0000000..c61ec9f --- /dev/null +++ b/engine262/src/runtime-semantics/CallExpression.mjs @@ -0,0 +1,59 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { + GetReferencedName, + GetValue, + IsPropertyReference, + PerformEval, + SameValue, +} from '../abstract-ops/all.mjs'; +import { IsInTailPosition } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { EvaluateCall, ArgumentListEvaluation } from './all.mjs'; + +// #sec-function-calls-runtime-semantics-evaluation +// CallExpression : +// CoverCallExpressionAndAsyncArrowHead +// CallExpression Arguments +export function* Evaluate_CallExpression(CallExpression) { + // 1. Let expr be CoveredCallExpression of CoverCallExpressionAndAsyncArrowHead. + const expr = CallExpression; + // 2. Let memberExpr be the MemberExpression of expr. + const memberExpr = expr.CallExpression; + // 3. Let arguments be the Arguments of expr. + const args = expr.Arguments; + // 4. Let ref be the result of evaluating memberExpr. + const ref = yield* Evaluate(memberExpr); + // 5. Let func be ? GetValue(ref). + const func = Q(GetValue(ref)); + // 6. If Type(ref) is Reference, IsPropertyReference(ref) is false, and GetReferencedName(ref) is "eval", then + if (Type(ref) === 'Reference' + && IsPropertyReference(ref) === Value.false + && (Type(GetReferencedName(ref)) === 'String' + && GetReferencedName(ref).stringValue() === 'eval')) { + // a. If SameValue(func, %eval%) is true, then + if (SameValue(func, surroundingAgent.intrinsic('%eval%')) === Value.true) { + // i. Let argList be ? ArgumentListEvaluation of arguments. + const argList = Q(yield* ArgumentListEvaluation(args)); + // ii. If argList has no elements, return undefined. + if (argList.length === 0) { + return Value.undefined; + } + // iii. Let evalText be the first element of argList. + const evalText = argList[0]; + // iv. If the source code matching this CallExpression is strict mode code, let strictCaller be true. Otherwise let strictCaller be false. + const strictCaller = CallExpression.strict; + // v. Let evalRealm be the current Realm Record. + const evalRealm = surroundingAgent.currentRealmRecord; + // vi. Return ? PerformEval(evalText, evalRealm, strictCaller, true). + return Q(PerformEval(evalText, evalRealm, strictCaller, true)); + } + } + // 7. Let thisCall be this CallExpression. + const thisCall = CallExpression; + // 8. Let tailCall be IsInTailPosition(thisCall). + const tailCall = IsInTailPosition(thisCall); + // 9. Return ? EvaluateCall(func, ref, arguments, tailCall). + return Q(yield* EvaluateCall(func, ref, args, tailCall)); +} diff --git a/engine262/src/runtime-semantics/ClassDeclaration.mjs b/engine262/src/runtime-semantics/ClassDeclaration.mjs new file mode 100644 index 0000000..167116e --- /dev/null +++ b/engine262/src/runtime-semantics/ClassDeclaration.mjs @@ -0,0 +1,43 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { sourceTextMatchedBy } from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { Q, NormalCompletion } from '../completion.mjs'; +import { InitializeBoundName, ClassDefinitionEvaluation } from './all.mjs'; + +// #sec-runtime-semantics-bindingclassdeclarationevaluation +// ClassDeclaration : +// `class` BindingIdentifier ClassTail +// `class` ClassTail +export function* BindingClassDeclarationEvaluation(ClassDeclaration) { + const { BindingIdentifier, ClassTail } = ClassDeclaration; + if (!BindingIdentifier) { + // 1. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments undefined and "default". + const value = Q(yield* ClassDefinitionEvaluation(ClassTail, Value.undefined, new Value('default'))); + // 2. Set value.[[SourceText]] to the source text matched by ClassDeclaration. + value.SourceText = sourceTextMatchedBy(ClassDeclaration); + // 3. Return value. + return value; + } + // 1. Let className be StringValue of BindingIdentifier. + const className = StringValue(BindingIdentifier); + // 2. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments className and className. + const value = Q(yield* ClassDefinitionEvaluation(ClassTail, className, className)); + // 3. Set value.[[SourceText]] to the source text matched by ClassDeclaration. + value.SourceText = sourceTextMatchedBy(ClassDeclaration); + // 4. Let env be the running execution context's LexicalEnvironment. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 5. Perform ? InitializeBoundName(className, value, env). + Q(InitializeBoundName(className, value, env)); + // 6. Return value. + return value; +} + +// #sec-class-definitions-runtime-semantics-evaluation +// ClassDeclaration : `class` BindingIdentifier ClassTAil +export function* Evaluate_ClassDeclaration(ClassDeclaration) { + // 1. Perform ? BindingClassDeclarationEvaluation of this ClassDeclaration. + Q(yield* BindingClassDeclarationEvaluation(ClassDeclaration)); + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/ClassDefinitionEvaluation.mjs b/engine262/src/runtime-semantics/ClassDefinitionEvaluation.mjs new file mode 100644 index 0000000..fb93bca --- /dev/null +++ b/engine262/src/runtime-semantics/ClassDefinitionEvaluation.mjs @@ -0,0 +1,161 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Type } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { + Get, + GetValue, + IsConstructor, + MakeConstructor, + MakeClassConstructor, + SetFunctionName, + CreateMethodProperty, + OrdinaryObjectCreate, +} from '../abstract-ops/all.mjs'; +import { + IsStatic, + ConstructorMethod, + NonConstructorMethodDefinitions, +} from '../static-semantics/all.mjs'; +import { Parser } from '../parser/Parser.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { + Q, X, + AbruptCompletion, + Completion, +} from '../completion.mjs'; +import { + DefineMethod, + PropertyDefinitionEvaluation, +} from './all.mjs'; + +function parseMethodDefinition(sourceText) { + const parser = new Parser({ source: sourceText }); + return parser.scope.with({ superCall: true }, () => parser.parseMethodDefinition()); +} + +// ClassTail : ClassHeritage? `{` ClassBody? `}` +export function* ClassDefinitionEvaluation(ClassTail, classBinding, className) { + const { ClassHeritage, ClassBody } = ClassTail; + // 1. Let env be the LexicalEnvironment of the running execution context. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let classScope be NewDeclarativeEnvironment(env). + const classScope = NewDeclarativeEnvironment(env); + // 3. If classBinding is not undefined, then + if (classBinding !== Value.undefined) { + // a. Perform classScopeEnv.CreateImmutableBinding(classBinding, true). + classScope.CreateImmutableBinding(classBinding, Value.true); + } + let protoParent; + let constructorParent; + // 4. If ClassHeritage is not present, then + if (!ClassHeritage) { + // a. Let protoParent be %Object.prototype%. + protoParent = surroundingAgent.intrinsic('%Object.prototype%'); + // b. Let constructorParent be %Function.prototype%. + constructorParent = surroundingAgent.intrinsic('%Function.prototype%'); + } else { // 5. Else, + // a. Set the running execution context's LexicalEnvironment to classScope. + surroundingAgent.runningExecutionContext.LexicalEnvironment = classScope; + // b. Let superclassRef be the result of evaluating ClassHeritage. + const superclassRef = yield* Evaluate(ClassHeritage); + // c. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + // d. Let superclass be ? GetValue(superclassRef). + const superclass = Q(GetValue(superclassRef)); + // e. If superclass is null, then + if (superclass === Value.null) { + // i. Let protoParent be null. + protoParent = Value.null; + // ii. Let constructorParent be %Function.prototype%. + constructorParent = surroundingAgent.intrinsic('%Function.prototype%'); + } else if (IsConstructor(superclass) === Value.false) { + // f. Else if IsConstructor(superclass) is false, throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAConstructor', superclass); + } else { // g. Else, + // i. Let protoParent be ? Get(superclass, "prototype"). + protoParent = Q(Get(superclass, new Value('prototype'))); + // ii. If Type(protoParent) is neither Object nor Null, throw a TypeError exception. + if (Type(protoParent) !== 'Object' && Type(protoParent) !== 'Null') { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // iii. Let constructorParent be superclass. + constructorParent = superclass; + } + } + // 6. Let proto be OrdinaryObjectCreate(protoParent). + const proto = OrdinaryObjectCreate(protoParent); + let constructor; + // 7. If ClassBody is not present, let constructor be empty. + if (!ClassBody) { + constructor = undefined; + } else { // 8. Else, let constructor be ConstructorMethod of ClassBody. + constructor = ConstructorMethod(ClassBody); + } + // 9. If constructor is empty, then + if (constructor === undefined) { + // a. If ClassHeritage is present, then + if (ClassHeritage) { + // i. Set constructor to the result of parsing the source text + // `constructor(...args) { super(...args); } using the syntactic grammar with the goal + // symbol MethodDefinition[~Yield, ~Await]. + constructor = parseMethodDefinition('constructor(...args) { super(...args); }'); + } else { // b. Else, + // i. Set constructor to the result of parsing the source text `constructor() {}` using the + // syntactic grammar with the goal symbol MethodDefinition[~Yield, ~Await]. + constructor = parseMethodDefinition('constructor() {}'); + } + } + // 10. Set the running execution context's LexicalEnvironment to classScope. + surroundingAgent.runningExecutionContext.LexicalEnvironment = classScope; + // 11. Let constructorInfo be ! DefineMethod of constructor with arguments proto and constructorParent. + const constructorInfo = X(yield* DefineMethod(constructor, proto, constructorParent)); + // 12. Let F be constructorInfo.[[Closure]]. + const F = constructorInfo.Closure; + // 13. Perform SetFunctionName(F, className). + SetFunctionName(F, className); + // 14. Perform MakeConstructor(F, false, proto). + MakeConstructor(F, Value.false, proto); + // 15. If ClassHeritage is present, set F.[[ConstructorKind]] to derived. + if (ClassHeritage) { + F.ConstructorKind = 'derived'; + } + // 16. Perform MakeClassConstructor(F). + MakeClassConstructor(F); + // 17. Perform CreateMethodProperty(proto, "constructor", F). + X(CreateMethodProperty(proto, new Value('constructor'), F)); + // 18. If ClassBody is not present, let methods be a new empty List. + let methods; + if (!ClassBody) { + methods = []; + } else { // 19. Else, let methods be NonConstructorMethodDefinitions of ClassBody. + methods = NonConstructorMethodDefinitions(ClassBody); + } + // 20. For each ClassElement m in order from methods, do + for (const m of methods) { + let status; + // a. If IsStatic of m is false, then + if (IsStatic(m) === false) { + // i. Let status be PropertyDefinitionEvaluation of m with arguments proto and false. + status = yield* PropertyDefinitionEvaluation(m, proto, Value.false); + } else { // b. Else, + // i. Let status be PropertyDefinitionEvaluation of m with arguments F and false. + status = yield* PropertyDefinitionEvaluation(m, F, Value.false); + } + // c. If status is an abrupt completion, then + if (status instanceof AbruptCompletion) { + // i. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + // ii. Return Completion(status). + return Completion(status); + } + } + // 21. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + // 22. If classBinding is not undefined, then + if (classBinding !== Value.undefined) { + // a. Perform classScope.InitializeBinding(classBinding, F). + classScope.InitializeBinding(classBinding, F); + } + // 23. Return F. + return F; +} diff --git a/engine262/src/runtime-semantics/ClassExpression.mjs b/engine262/src/runtime-semantics/ClassExpression.mjs new file mode 100644 index 0000000..d36cef8 --- /dev/null +++ b/engine262/src/runtime-semantics/ClassExpression.mjs @@ -0,0 +1,29 @@ +import { Value } from '../value.mjs'; +import { sourceTextMatchedBy } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { ClassDefinitionEvaluation } from './all.mjs'; + +// #sec-class-definitions-runtime-semantics-evaluation +// ClassExpression : +// `class` ClassTail +// `class` BindingIdentifier ClassTail +export function* Evaluate_ClassExpression(ClassExpression) { + const { BindingIdentifier, ClassTail } = ClassExpression; + if (!BindingIdentifier) { + // 1. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments undefined and '' + const value = Q(yield* ClassDefinitionEvaluation(ClassTail, Value.undefined, new Value(''))); + // 2. Set value.[[SourceText]] to the source text matched by ClassExpression. + value.SourceText = sourceTextMatchedBy(ClassExpression); + // 3. Return value. + return value; + } + // 1. Let className be StringValue of BindingIdentifier. + const className = StringValue(BindingIdentifier); + // 2. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments className and className. + const value = Q(yield* ClassDefinitionEvaluation(ClassTail, className, className)); + // Set value.[[SourceText]] to the source text matched by ClassExpression. + value.SourceText = sourceTextMatchedBy(ClassExpression); + // Return value. + return value; +} diff --git a/engine262/src/runtime-semantics/CoalesceExpression.mjs b/engine262/src/runtime-semantics/CoalesceExpression.mjs new file mode 100644 index 0000000..69ff1a3 --- /dev/null +++ b/engine262/src/runtime-semantics/CoalesceExpression.mjs @@ -0,0 +1,23 @@ +import { Q } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { GetValue } from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; + +// #sec-binary-logical-operators-runtime-semantics-evaluation +// CoalesceExpression : +// CoalesceExpressionHead `??` BitwiseORExpression +export function* Evaluate_CoalesceExpression({ CoalesceExpressionHead, BitwiseORExpression }) { + // 1. Let lref be the result of evaluating |CoalesceExpressionHead|. + const lref = yield* Evaluate(CoalesceExpressionHead); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. If lval is *undefined* or *null*, + if (lval === Value.undefined || lval === Value.null) { + // a. Let rref be the result of evaluating |BitwiseORExpression|. + const rref = yield* Evaluate(BitwiseORExpression); + // b. Return ? GetValue(rref). + return Q(GetValue(rref)); + } + // 4. Otherwise, return lval. + return lval; +} diff --git a/engine262/src/runtime-semantics/CommaOperator.mjs b/engine262/src/runtime-semantics/CommaOperator.mjs new file mode 100644 index 0000000..9614e12 --- /dev/null +++ b/engine262/src/runtime-semantics/CommaOperator.mjs @@ -0,0 +1,16 @@ +import { Evaluate } from '../evaluator.mjs'; +import { GetValue } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-comma-operator-runtime-semantics-evaluation +// Expression : +// AssignmentExpression +// Expression `,` AssignmentExpression +export function* Evaluate_CommaOperator({ ExpressionList }) { + let result; + for (const Expression of ExpressionList) { + const lref = yield* Evaluate(Expression); + result = Q(GetValue(lref)); + } + return result; +} diff --git a/engine262/src/runtime-semantics/ConditionalExpression.mjs b/engine262/src/runtime-semantics/ConditionalExpression.mjs new file mode 100644 index 0000000..6e7a89d --- /dev/null +++ b/engine262/src/runtime-semantics/ConditionalExpression.mjs @@ -0,0 +1,30 @@ +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { ToBoolean, GetValue } from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-conditional-operator-runtime-semantics-evaluation +// ConditionalExpression : +// ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression +export function* Evaluate_ConditionalExpression({ + ShortCircuitExpression, + AssignmentExpression_a, + AssignmentExpression_b, +}) { + // 1. Let lref be the result of evaluating ShortCircuitExpression. + const lref = yield* Evaluate(ShortCircuitExpression); + // 2. Let lval be ! ToBoolean(? GetValue(lref)). + const lval = X(ToBoolean(Q(GetValue(lref)))); + // 3. If lval is true, then + if (lval === Value.true) { + // a. Let trueRef be the result of evaluating the first AssignmentExpression. + const trueRef = yield* Evaluate(AssignmentExpression_a); + // b. Return ? GetValue(trueRef). + return Q(GetValue(trueRef)); + } else { // 4. Else, + // a. Let falseRef be the result of evaluating the second AssignmentExpression. + const falseRef = yield* Evaluate(AssignmentExpression_b); + // b. Return ? GetValue(falseRef). + return Q(GetValue(falseRef)); + } +} diff --git a/engine262/src/runtime-semantics/ContinueStatement.mjs b/engine262/src/runtime-semantics/ContinueStatement.mjs new file mode 100644 index 0000000..a43891c --- /dev/null +++ b/engine262/src/runtime-semantics/ContinueStatement.mjs @@ -0,0 +1,17 @@ +import { Completion } from '../completion.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; + +// #sec-continue-statement-runtime-semantics-evaluation +// ContinueStatement : +// `continue` `;` +// `continue` LabelIdentifier `;` +export function Evaluate_ContinueStatement({ LabelIdentifier }) { + if (!LabelIdentifier) { + // 1. Return Completion { [[Type]]: continue, [[Value]]: empty, [[Target]]: empty }. + return new Completion({ Type: 'continue', Value: undefined, Target: undefined }); + } + // 1. Let label be the StringValue of LabelIdentifier. + const label = StringValue(LabelIdentifier); + // 2. Return Completion { [[Type]]: continue, [[Value]]: empty, [[Target]]: label }. + return new Completion({ Type: 'continue', Value: undefined, Target: label }); +} diff --git a/engine262/src/runtime-semantics/CreateDynamicFunction.mjs b/engine262/src/runtime-semantics/CreateDynamicFunction.mjs new file mode 100644 index 0000000..c075a09 --- /dev/null +++ b/engine262/src/runtime-semantics/CreateDynamicFunction.mjs @@ -0,0 +1,193 @@ +import { + Assert, + DefinePropertyOrThrow, + GetPrototypeFromConstructor, + MakeConstructor, + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + ToString, +} from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; +import { + HostEnsureCanCompileStrings, + surroundingAgent, +} from '../engine.mjs'; +import { wrappedParse } from '../parse.mjs'; +import { Token } from '../parser/tokens.mjs'; +import { Descriptor, Type, Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; + +// #table-dynamic-function-sourcetext-prefixes +const DynamicFunctionSourceTextPrefixes = { + 'normal': 'function', + 'generator': 'function*', + 'async': 'async function', + 'asyncGenerator': 'async function*', +}; + +export function CreateDynamicFunction(constructor, newTarget, kind, args) { + // 1. Assert: The execution context stack has at least two elements. + Assert(surroundingAgent.executionContextStack.length >= 2); + // 2. Let callerContext be the second to top element of the execution context stack. + const callerContext = surroundingAgent.executionContextStack[surroundingAgent.executionContextStack.length - 2]; + // 3. Let callerRealm be callerContext's Realm. + const callerRealm = callerContext.Realm; + // 4. Let calleeRealm be the current Realm Record. + const calleeRealm = surroundingAgent.currentRealmRecord; + // 5. Perform ? HostEnsureCanCompileStrings(callerRealm, calleeRealm). + Q(HostEnsureCanCompileStrings(callerRealm, calleeRealm)); + // 6. If newTarget is undefined, set newTarget to constructor. + if (Type(newTarget) === 'Undefined') { + newTarget = constructor; + } + // 7. If kind is normal, then + let fallbackProto; + if (kind === 'normal') { + // a. Let goal be the grammar symbol FunctionBody[~Yield, ~Await]. + // b. Let parameterGoal be the grammar symbol FormalParameters[~Yield, ~Await]. + // c. Let fallbackProto be "%Function.prototype%". + fallbackProto = '%Function.prototype%'; + } else if (kind === 'generator') { // 8. Else if kind is generator, then + // a. Let goal be the grammar symbol GeneratorBody. + // b. Let parameterGoal be the grammar symbol FormalParameters[+Yield, ~Await]. + // c. Let fallbackProto be "%Generator%". + fallbackProto = '%Generator%'; + } else if (kind === 'async') { // 9. Else if kind is async, then + // a. Let goal be the grammar symbol AsyncFunctionBody. + // b. Let parameterGoal be the grammar symbol FormalParameters[~Yield, +Await]. + // c. Let fallbackProto be "%AsyncFunction.prototype%". + fallbackProto = '%AsyncFunction.prototype%'; + } else { // 10. Else, + // a. Assert: kind is asyncGenerator. + Assert(kind === 'asyncGenerator'); + // b. Let goal be the grammar symbol AsyncGeneratorBody. + // c. Let parameterGoal be the grammar symbol FormalParameters[+Yield, +Await]. + // d. Let fallbackProto be "%AsyncGenerator%". + fallbackProto = '%AsyncGeneratorFunction.prototype%'; + } + // 11. Let argCount be the number of elements in args. + const argCount = args.length; + // 12. Let P be the empty String. + let P = ''; + // 13. If argCount = 0, let bodyArg be the empty String. + let bodyArg; + if (argCount === 0) { + bodyArg = new Value(''); + } else if (argCount === 1) { // 14. Else if argCount = 1, let bodyArg be args[0]. + bodyArg = args[0]; + } else { // 15. Else, + // a. Assert: argCount > 1. + Assert(argCount > 1); + // b. Let firstArg be args[0]. + const firstArg = args[0]; + // c. Set P to ? ToString(firstArg). + P = Q(ToString(firstArg)).stringValue(); + // d. Let k be 1. + let k = 1; + // e. Repeat, while k < argCount - 1 + while (k < argCount - 1) { + // i. Let nextArg be args[k]. + const nextArg = args[k]; + // ii. Let nextArgString be ? ToString(nextArg). + const nextArgString = Q(ToString(nextArg)); + // iii. Set P to the string-concatenation of the previous value of P, "," (a comma), and nextArgString. + P = `${P},${nextArgString.stringValue()}`; + // iv. Set k to k + 1. + k += 1; + } + // f. Let bodyArg be args[k]. + bodyArg = args[k]; + } + // 16. Let bodyString be the string-concatenation of 0x000A (LINE FEED), ? ToString(bodyArg), and 0x000A (LINE FEED). + const bodyString = `\u{000A}${Q(ToString(bodyArg)).stringValue()}\u{000A}`; + // 17. Let prefix be the prefix associated with kind in Table 48. + const prefix = DynamicFunctionSourceTextPrefixes[kind]; + // 18. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyString, and "}". + const sourceString = `${prefix} anonymous(${P}\u{000A}) {${bodyString}}`; + // 19. Let sourceText be ! UTF16DecodeString(sourceString). + const sourceText = sourceString; + // 20. Perform the following substeps in an implementation-dependent order, possibly interleaving parsing and error detection: + // a. Let parameters be the result of parsing ! UTF16DecodeString(P), using parameterGoal as the goal symbol. Throw a SyntaxError exception if the parse fails. + // b. Let body be the result of parsing ! UTF16DecodeString(bodyString), using goal as the goal symbol. Throw a SyntaxError exception if the parse fails. + // c. Let strict be ContainsUseStrict of body. + // d. If any static semantics errors are detected for parameters or body, throw a SyntaxError exception. If strict is true, the Early Error rules for UniqueFormalParameters:FormalParameters are applied. + // e. If strict is true and IsSimpleParameterList of parameters is false, throw a SyntaxError exception. + // f. If any element of the BoundNames of parameters also occurs in the LexicallyDeclaredNames of body, throw a SyntaxError exception. + // g. If body Contains SuperCall is true, throw a SyntaxError exception. + // h. If parameters Contains SuperCall is true, throw a SyntaxError exception. + // i. If body Contains SuperProperty is true, throw a SyntaxError exception. + // j. If parameters Contains SuperProperty is true, throw a SyntaxError exception. + // k. If kind is generator or asyncGenerator, then + // i. If parameters Contains YieldExpression is true, throw a SyntaxError exception. + // l. If kind is async or asyncGenerator, then + // i. If parameters Contains AwaitExpression is true, throw a SyntaxError exception. + // m. If strict is true, then + // i. If BoundNames of parameters contains any duplicate elements, throw a SyntaxError exception. + let parameters; + let body; + { + const f = wrappedParse({ source: sourceString }, (p) => { + const r = p.parseExpression(); + p.expect(Token.EOS); + return r; + }); + if (Array.isArray(f)) { + return surroundingAgent.Throw(f[0]); + } + parameters = f.FormalParameters; + switch (kind) { + case 'normal': + body = f.FunctionBody; + break; + case 'generator': + body = f.GeneratorBody; + break; + case 'async': + body = f.AsyncFunctionBody; + break; + case 'asyncGenerator': + body = f.AsyncGeneratorBody; + break; + default: + throw new OutOfRange('kind', kind); + } + } + // 21. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto). + const proto = Q(GetPrototypeFromConstructor(newTarget, fallbackProto)); + // 22. Let realmF be the current Realm Record. + const realmF = surroundingAgent.currentRealmRecord; + // 23. Let scope be realmF.[[GlobalEnv]]. + const scope = realmF.GlobalEnv; + // 24. Let F be ! OrdinaryFunctionCreate(proto, sourceText, parameters, body, non-lexical-this, scope). + const F = X(OrdinaryFunctionCreate(proto, sourceText, parameters, body, 'non-lexical-this', scope)); + // 25. Perform SetFunctionName(F, "anonymous"). + SetFunctionName(F, new Value('anonymous')); + // 26. If kind is generator, then + if (kind === 'generator') { + // a. Let prototype be OrdinaryObjectCreate(%Generator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Generator.prototype%')); + // b. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + DefinePropertyOrThrow(F, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + })); + } else if (kind === 'asyncGenerator') { // 27. Else if kind is asyncGenerator, then + // a. Let prototype be OrdinaryObjectCreate(%AsyncGenerator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGenerator.prototype%')); + // b. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + DefinePropertyOrThrow(F, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + })); + } else if (kind === 'normal') { // 28. Else if kind is normal, then perform MakeConstructor(F). + MakeConstructor(F); + } + // 29. NOTE: Functions whose kind is async are not constructible and do not have a [[Construct]] internal method or a "prototype" property. + // 20. Return F. + return F; +} diff --git a/engine262/src/runtime-semantics/DebuggerStatement.mjs b/engine262/src/runtime-semantics/DebuggerStatement.mjs new file mode 100644 index 0000000..33914f1 --- /dev/null +++ b/engine262/src/runtime-semantics/DebuggerStatement.mjs @@ -0,0 +1,19 @@ +import { surroundingAgent } from '../engine.mjs'; +import { NormalCompletion, EnsureCompletion } from '../completion.mjs'; + +// #sec-debugger-statement-runtime-semantics-evaluation +// DebuggerStatement : `debugger` `;` +export function Evaluate_DebuggerStatement() { + let result; + // 1. If an implementation-defined debugging facility is available and enabled, then + if (surroundingAgent.hostDefinedOptions.onDebugger) { + // a. Perform an implementation-defined debugging action. + // b. Let result be an implementation-defined Completion value. + result = EnsureCompletion(surroundingAgent.hostDefinedOptions.onDebugger()); + } else { + // a. Let result be NormalCompletion(empty). + result = NormalCompletion(undefined); + } + // 2. Return result. + return result; +} diff --git a/engine262/src/runtime-semantics/DefineMethod.mjs b/engine262/src/runtime-semantics/DefineMethod.mjs new file mode 100644 index 0000000..25add16 --- /dev/null +++ b/engine262/src/runtime-semantics/DefineMethod.mjs @@ -0,0 +1,44 @@ +import { surroundingAgent } from '../engine.mjs'; +import { OrdinaryFunctionCreate, MakeMethod, sourceTextMatchedBy } from '../abstract-ops/all.mjs'; +import { ReturnIfAbrupt } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { Evaluate_PropertyName } from './all.mjs'; + +// #sec-runtime-semantics-definemethod +function* DefineMethod_MethodDefinition(MethodDefinition, object, functionPrototype) { + const { PropertyName, UniqueFormalParameters, FunctionBody } = MethodDefinition; + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + let prototype; + // 4. If functionPrototype is present as a parameter, then + if (functionPrototype !== undefined) { + // a. Let prototype be functionPrototype. + prototype = functionPrototype; + } else { // 5. Else, + // a. Let prototype be %Function.prototype%. + prototype = surroundingAgent.intrinsic('%Function.prototype%'); + } + // 6. Let sourceText be the source text matched by MethodDefinition. + const sourceText = sourceTextMatchedBy(MethodDefinition); + // 7. Let closure be OrdinaryFunctionCreate(prototype, sourceText, UniqueFormalParameters, FunctionBody, non-lexical-this, scope). + const closure = OrdinaryFunctionCreate(prototype, sourceText, UniqueFormalParameters, FunctionBody, 'non-lexical-this', scope); + // 8. Perform MakeMethod(closure, object). + MakeMethod(closure, object); + // 9. Return the Record { [[Key]]: propKey, [[Closure]]: closure }. + return { Key: propKey, Closure: closure }; +} + +export function DefineMethod(node, object, functionPrototype) { + switch (node.type) { + case 'MethodDefinition': + return DefineMethod_MethodDefinition(node, object, functionPrototype); + case 'ClassElement': + return DefineMethod(node.MethodDefinition, object, functionPrototype); + default: + throw new OutOfRange('DefineMethod', node); + } +} diff --git a/engine262/src/runtime-semantics/DestructuringAssignmentEvaluation.mjs b/engine262/src/runtime-semantics/DestructuringAssignmentEvaluation.mjs new file mode 100644 index 0000000..49f6084 --- /dev/null +++ b/engine262/src/runtime-semantics/DestructuringAssignmentEvaluation.mjs @@ -0,0 +1,346 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + ArrayCreate, + CopyDataProperties, + CreateDataPropertyOrThrow, + GetIterator, + GetReferencedName, + GetV, + GetValue, + IteratorClose, + IteratorStep, + IteratorValue, + OrdinaryObjectCreate, + PutValue, + ResolveBinding, + RequireObjectCoercible, + ToString, +} from '../abstract-ops/all.mjs'; +import { + IsAnonymousFunctionDefinition, + IsIdentifierRef, + StringValue, +} from '../static-semantics/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { + Q, X, + Completion, + AbruptCompletion, + NormalCompletion, + ReturnIfAbrupt, + EnsureCompletion, +} from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + Evaluate_PropertyName, + NamedEvaluation, + refineLeftHandSideExpression, +} from './all.mjs'; + +// ObjectAssignmentPattern : +// `{` `}` +// `{` AssignmentPropertyList `}` +// `{` AssignmentPropertyList `,` `}` +// `{` AssignmentPropertyList `,` AssignmentRestProperty? `}` +function* DestructuringAssignmentEvaluation_ObjectAssignmentPattern({ AssignmentPropertyList, AssignmentRestProperty }, value) { + // 1. Perform ? RequireObjectCoercible(value). + Q(RequireObjectCoercible(value)); + // 2. Perform ? PropertyDestructuringAssignmentEvaluation for AssignmentPropertyList using value as the argument. + const excludedNames = Q(yield* PropertyDestructuringAssignmentEvaluation(AssignmentPropertyList, value)); + if (AssignmentRestProperty) { + Q(yield* RestDestructuringAssignmentEvaluation(AssignmentRestProperty, value, excludedNames)); + } + // 3. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} + +// #sec-runtime-semantics-restdestructuringassignmentevaluation +// AssignmentRestProperty : `...` DestructuringAssignmentTarget +function* RestDestructuringAssignmentEvaluation({ DestructuringAssignmentTarget }, value, excludedNames) { + // 1. Let lref be the result of evaluating DestructuringAssignmentTarget. + const lref = yield* Evaluate(DestructuringAssignmentTarget); + // 2. ReturnIfAbrupt(lref). + ReturnIfAbrupt(lref); + // 3. Let restObj be OrdinaryObjectCreate(%Object.prototype%). + const restObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // 4. Perform ? CopyDataProperties(restObj, value, excludedNames). + Q(CopyDataProperties(restObj, value, excludedNames)); + // 5. Return PutValue(lref, restObj). + return PutValue(lref, restObj); +} + +function* PropertyDestructuringAssignmentEvaluation(AssignmentPropertyList, value) { + const propertyNames = []; + for (const AssignmentProperty of AssignmentPropertyList) { + if (AssignmentProperty.IdentifierReference) { + // 1. Let P be StringValue of IdentifierReference. + const P = StringValue(AssignmentProperty.IdentifierReference); + // 2. Let lref be ? ResolveBinding(P). + const lref = Q(ResolveBinding(P, undefined, AssignmentProperty.IdentifierReference.strict)); + // 3. Let v be ? GetV(value, P). + let v = Q(GetV(value, P)); + // 4. If Initializer? is present and v is undefined, then + if (AssignmentProperty.Initializer && v === Value.undefined) { + // a. If IsAnonymousFunctionDefinition(Initializer) is true, then + if (IsAnonymousFunctionDefinition(AssignmentProperty.Initializer)) { + // i. Set v to the result of performing NamedEvaluation for Initializer with argument P. + v = yield* NamedEvaluation(AssignmentProperty.Initializer, P); + } else { // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = yield* Evaluate(AssignmentProperty.Initializer); + // ii. Set v to ? GetValue(defaultValue) + v = Q(GetValue(defaultValue)); + } + } + // 5. Perform ? PutValue(lref, v). + Q(PutValue(lref, v)); + // 6. Return a new List containing P. + propertyNames.push(P); + } else { + // 1. Let name be the result of evaluating PropertyName. + const name = yield* Evaluate_PropertyName(AssignmentProperty.PropertyName); + // 2. ReturnIfAbrupt(name). + ReturnIfAbrupt(name); + // 3. Perform ? KeyedDestructuringAssignmentEvaluation of AssignmentElement with value and name as the arguments. + Q(yield* KeyedDestructuringAssignmentEvaluation(AssignmentProperty.AssignmentElement, value, name)); + // 4. Return a new List containing name. + propertyNames.push(name); + } + } + return propertyNames; +} + +// AssignmentElement : DestructuringAssignmentTarget Initializer? +function* KeyedDestructuringAssignmentEvaluation({ + DestructuringAssignmentTarget, + Initializer, +}, value, propertyName) { + // 1. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then + let lref; + if (DestructuringAssignmentTarget.type !== 'ObjectLiteral' + && DestructuringAssignmentTarget.type !== 'ArrayLiteral') { + // a. Let lref be the result of evaluating DestructuringAssignmentTarget. + lref = yield* Evaluate(DestructuringAssignmentTarget); + // b. ReturnIfAbrupt(lref). + ReturnIfAbrupt(lref); + } + // 2. Let v be ? GetV(value, propertyName). + const v = Q(GetV(value, propertyName)); + // 3. If Initializer is present and v is undefined, then + let rhsValue; + if (Initializer && v === Value.undefined) { + // a. If IsAnonymousFunctionDefinition(Initializer) and IsIdentifierRef of DestructuringAssignmentTarget are both true, then + if (IsAnonymousFunctionDefinition(Initializer) && IsIdentifierRef(DestructuringAssignmentTarget)) { + // i. Let rhsValue be NamedEvaluation of Initializer with argument GetReferencedName(lref). + rhsValue = yield* NamedEvaluation(Initializer, GetReferencedName(lref)); + } else { + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = yield* Evaluate(Initializer); + // ii. Let rhsValue be ? GetValue(defaultValue). + rhsValue = Q(GetValue(defaultValue)); + } + } else { // 4. Else, let rhsValue be v. + rhsValue = v; + } + // 5. If DestructuringAssignmentTarget is an ObjectLiteral or an ArrayLiteral, then + if (DestructuringAssignmentTarget.type === 'ObjectLiteral' + || DestructuringAssignmentTarget.type === 'ArrayLiteral') { + // a. Let assignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget. + const assignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget); + // b. Return the result of performing DestructuringAssignmentEvaluation of assignmentPattern with rhsValue as the argument. + return yield* DestructuringAssignmentEvaluation(assignmentPattern, rhsValue); + } + // 6. Return ? PutValue(lref, rhsValue). + return Q(PutValue(lref, rhsValue)); +} + +// ArrayAssignmentPattern : +// `[` `]` +// `[` AssignmentElementList `]` +// `[` AssignmentElementList `,` AssignmentRestElement? `]` +function* DestructuringAssignmentEvaluation_ArrayAssignmentPattern({ AssignmentElementList, AssignmentRestElement }, value) { + // 1. Let iteratorRecord be ? GetIterator(value). + const iteratorRecord = Q(GetIterator(value)); + // 2. Let status be IteratorDestructuringAssignmentEvaluation of AssignmentElementList with argument iteratorRecord. + let status = EnsureCompletion(yield* IteratorDestructuringAssignmentEvaluation(AssignmentElementList, iteratorRecord)); + // 3. If status is an abrupt completion, then + if (status instanceof AbruptCompletion) { + // a. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, status). + if (iteratorRecord.Done === Value.false) { + return Q(IteratorClose(iteratorRecord, status)); + } + // b. Return Completion(status). + return Completion(status); + } + // 4. If Elision is present, then + // ... + // 5. If AssignmentRestElement is present, then + if (AssignmentRestElement) { + // a. Set status to the result of performing IteratorDestructuringAssignmentEvaluation of AssignmentRestElement with iteratorRecord as the argument. + status = EnsureCompletion(yield* IteratorDestructuringAssignmentEvaluation(AssignmentRestElement, iteratorRecord)); + } + // 6. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, status). + if (iteratorRecord.Done === Value.false) { + return Q(IteratorClose(iteratorRecord, status)); + } + return Completion(status); +} + +function* IteratorDestructuringAssignmentEvaluation(node, iteratorRecord) { + if (Array.isArray(node)) { + for (const n of node) { + Q(yield* IteratorDestructuringAssignmentEvaluation(n, iteratorRecord)); + } + return NormalCompletion(undefined); + } + switch (node.type) { + case 'Elision': + // 1. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next) + ReturnIfAbrupt(next); + // d. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } + } + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); + case 'AssignmentElement': { + const { DestructuringAssignmentTarget, Initializer } = node; + let lref; + // 1. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then + if (DestructuringAssignmentTarget.type !== 'ObjectLiteral' + && DestructuringAssignmentTarget.type !== 'ArrayLiteral') { + lref = yield* Evaluate(DestructuringAssignmentTarget); + ReturnIfAbrupt(lref); + } + let value; + // 2. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next); + ReturnIfAbrupt(next); + // d. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } else { // e. Else, + // i. Let value be IteratorValue(next). + value = IteratorValue(next); + // ii. If value is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (value instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // iii. ReturnIfAbrupt(value). + ReturnIfAbrupt(value); + } + } + // 3. If iteratorRecord.[[Done]] is true, let value be undefined. + if (iteratorRecord.Done === Value.true) { + value = Value.undefined; + } + let v; + // 4. If Initializer is present and value is undefined, then + if (Initializer && value === Value.undefined) { + // a. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and IsIdentifierRef of LeftHandSideExpression is true, then + if (IsAnonymousFunctionDefinition(Initializer) && IsIdentifierRef(DestructuringAssignmentTarget)) { + // i. Let v be NamedEvaluation of Initializer with argument GetReferencedName(lref). + v = yield* NamedEvaluation(Initializer, GetReferencedName(lref)); + } else { // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = yield* Evaluate(Initializer); + // ii. Let v be ? GetValue(defaultValue). + v = Q(GetValue(defaultValue)); + } + } else { // 5. Else, let v be value. + v = value; + } + // 6. If DestructuringAssignmentTarget is an ObjectLiteral or an ArrayLiteral, then + if (DestructuringAssignmentTarget.type === 'ObjectLiteral' + || DestructuringAssignmentTarget.type === 'ArrayLiteral') { + // a. Let nestedAssignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget. + const nestedAssignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget); + // b. Return the result of performing DestructuringAssignmentEvaluation of nestedAssignmentPattern with v as the argument. + return yield* DestructuringAssignmentEvaluation(nestedAssignmentPattern, v); + } + // 7. Return ? PutValue(lref, v). + return Q(PutValue(lref, v)); + } + case 'AssignmentRestElement': { + const { DestructuringAssignmentTarget } = node; + let lref; + // 1. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then + if (DestructuringAssignmentTarget.type !== 'ObjectLiteral' + && DestructuringAssignmentTarget.type !== 'ArrayLiteral') { + lref = yield* Evaluate(DestructuringAssignmentTarget); + ReturnIfAbrupt(lref); + } + // 2. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(new Value(0))); + // 3. Let n be 0. + let n = 0; + // 4. Repeat, while iteratorRecord.[[Done]] is false, + while (iteratorRecord.Done === Value.false) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next); + ReturnIfAbrupt(next); + // d. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } else { // e. Else, + // i. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // ii. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // iii. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // iv. Perform ! CreateDataPropertyOrThrow(A, ! ToString(n), nextValue). + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(n))), nextValue)); + // v. Set n to n + 1. + n += 1; + } + } + // 5. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then + if (DestructuringAssignmentTarget.type !== 'ObjectLiteral' + && DestructuringAssignmentTarget.type !== 'ArrayLiteral') { + return Q(PutValue(lref, A)); + } + // 6. Let nestedAssignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget. + const nestedAssignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget); + // 7. Return the result of performing DestructuringAssignmentEvaluation of nestedAssignmentPattern with A as the argument. + return yield* DestructuringAssignmentEvaluation(nestedAssignmentPattern, A); + } + default: + throw new OutOfRange('IteratorDestructuringAssignmentEvaluation', node); + } +} + +export function DestructuringAssignmentEvaluation(node, value) { + switch (node.type) { + case 'ObjectAssignmentPattern': + return DestructuringAssignmentEvaluation_ObjectAssignmentPattern(node, value); + case 'ArrayAssignmentPattern': + return DestructuringAssignmentEvaluation_ArrayAssignmentPattern(node, value); + default: + throw new OutOfRange('DestructuringAssignmentEvaluation', node); + } +} diff --git a/engine262/src/runtime-semantics/EmptyStatement.mjs b/engine262/src/runtime-semantics/EmptyStatement.mjs new file mode 100644 index 0000000..c3d4189 --- /dev/null +++ b/engine262/src/runtime-semantics/EmptyStatement.mjs @@ -0,0 +1,8 @@ +import { NormalCompletion } from '../completion.mjs'; + +// #sec-empty-statement-runtime-semantics-evaluation +// EmptyStatement : `;` +export function Evaluate_EmptyStatement(_EmptyStatement) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/EqualityExpression.mjs b/engine262/src/runtime-semantics/EqualityExpression.mjs new file mode 100644 index 0000000..87b40a3 --- /dev/null +++ b/engine262/src/runtime-semantics/EqualityExpression.mjs @@ -0,0 +1,60 @@ +import { + AbstractEqualityComparison, + GetValue, + StrictEqualityComparison, +} from '../abstract-ops/all.mjs'; +import { ReturnIfAbrupt, Q, X } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; + +// #sec-equality-operators-runtime-semantics-evaluation +// EqualityExpression : +// EqualityExpression `==` RelationalExpression +// EqualityExpression `!=` RelationalExpression +// EqualityExpression `===` RelationalExpression +// EqualityExpression `!==` RelationalExpression +export function* Evaluate_EqualityExpression({ EqualityExpression, operator, RelationalExpression }) { + // 1. Let lref be the result of evaluating EqualityExpression. + const lref = yield* Evaluate(EqualityExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let rref be the result of evaluating RelationalExpression. + const rref = yield* Evaluate(RelationalExpression); + // 4. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + switch (operator) { + case '==': + // 5. Return the result of performing Abstract Equality Comparison rval == lval. + return AbstractEqualityComparison(rval, lval); + case '!=': { + // 5. Let r be the result of performing Abstract Equality Comparison rval == lval. + const r = AbstractEqualityComparison(rval, lval); + // 6. ReturnIfAbrupt(r). + ReturnIfAbrupt(r); + // 7. If r is true, return false. Otherwise, return true. + if (r === Value.true) { + return Value.false; + } else { + return Value.true; + } + } + case '===': + // 5. Return the result of performing Strict Equality Comparison rval === lval. + return StrictEqualityComparison(rval, lval); + case '!==': { + // 5. Let r be the result of performing Strict Equality Comparison rval === lval. + // 6. Assert: r is a normal completion. + const r = X(StrictEqualityComparison(rval, lval)); + // 7. If r.[[Value]] is true, return false. Otherwise, return true. + if (r === Value.true) { + return Value.false; + } else { + return Value.true; + } + } + + default: + throw new OutOfRange('Evaluate_EqualityExpression', operator); + } +} diff --git a/engine262/src/runtime-semantics/EvaluateBody.mjs b/engine262/src/runtime-semantics/EvaluateBody.mjs new file mode 100644 index 0000000..1e96193 --- /dev/null +++ b/engine262/src/runtime-semantics/EvaluateBody.mjs @@ -0,0 +1,148 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + AsyncFunctionStart, + Call, + GeneratorStart, + NewPromiseCapability, + OrdinaryCreateFromConstructor, + AsyncGeneratorStart, + GetValue, +} from '../abstract-ops/all.mjs'; +import { + Completion, + AbruptCompletion, + Q, X, +} from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { + Evaluate_FunctionStatementList, + FunctionDeclarationInstantiation, +} from './all.mjs'; + +export function Evaluate_AnyFunctionBody({ FunctionStatementList }) { + return Evaluate_FunctionStatementList(FunctionStatementList); +} + +// #sec-function-definitions-runtime-semantics-evaluatebody +// FunctionBody : FunctionStatementList +export function* EvaluateBody_FunctionBody({ FunctionStatementList }, functionObject, argumentsList) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Return the result of evaluating FunctionStatementList. + return yield* Evaluate_FunctionStatementList(FunctionStatementList); +} + +// #sec-arrow-function-definitions-runtime-semantics-evaluation +// ExpressionBody : AssignmentExpression +export function* Evaluate_ExpressionBody({ AssignmentExpression }) { + // 1. Let exprRef be the result of evaluating AssignmentExpression. + const exprRef = yield* Evaluate(AssignmentExpression); + // 2. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(GetValue(exprRef)); + // 3. Return Completion { [[Type]]: return, [[Value]]: exprValue, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: exprValue, Target: undefined }); +} + +// #sec-arrow-function-definitions-runtime-semantics-evaluatebody +// ConciseBody : ExpressionBody +export function* EvaluateBody_ConciseBody({ ExpressionBody }, functionObject, argumentsList) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Return the result of evaluating ExpressionBody. + return yield* Evaluate(ExpressionBody); +} + +// #sec-async-arrow-function-definitions-EvaluateBody +// AsyncConciseBody : ExpressionBody +function* EvaluateBody_AsyncConciseBody({ ExpressionBody }, functionObject, argumentsList) { + // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 2. Let declResult be FunctionDeclarationInstantiation(functionObject, argumentsList). + const declResult = yield* FunctionDeclarationInstantiation(functionObject, argumentsList); + // 3. If declResult is not an abrupt completion, then + if (!(declResult instanceof AbruptCompletion)) { + // a. Perform ! AsyncFunctionStart(promiseCapability, ExpressionBody). + X(AsyncFunctionStart(promiseCapability, ExpressionBody)); + } else { // 4. Else + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »). + X(Call(promiseCapability.Reject, Value.undefined, [declResult.Value])); + } + // 5. Return Completion { [[Type]]: return, [[Value]]: promiseCapability.[[Promise]], [[Target]]: empty }. + return new Completion({ Type: 'return', Value: promiseCapability.Promise, Target: undefined }); +} + +// #sec-generator-function-definitions-runtime-semantics-evaluatebody +// GeneratorBody : FunctionBody +export function* EvaluateBody_GeneratorBody(GeneratorBody, functionObject, argumentsList) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Let G be ? OrdinaryCreateFromConstructor(functionObject, "%Generator.prototype%", « [[GeneratorState]], [[GeneratorContext]] »). + const G = Q(OrdinaryCreateFromConstructor(functionObject, '%Generator.prototype%', ['GeneratorState', 'GeneratorContext'])); + // 3. Perform GeneratorStart(G, FunctionBody). + GeneratorStart(G, GeneratorBody); + // 4. Return Completion { [[Type]]: return, [[Value]]: G, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: G, Target: undefined }); +} + +// #sec-asyncgenerator-definitions-evaluatebody +// AsyncGeneratorBody : FunctionBody +export function* EvaluateBody_AsyncGeneratorBody(FunctionBody, functionObject, argumentsList) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Let generator be ? OrdinaryCreateFromConstructor(functionObject, "%AsyncGenerator.prototype%", « [[AsyncGeneratorState]], [[AsyncGeneratorContext]], [[AsyncGeneratorQueue]] »). + const generator = Q(OrdinaryCreateFromConstructor(functionObject, '%AsyncGenerator.prototype%', [ + 'AsyncGeneratorState', + 'AsyncGeneratorContext', + 'AsyncGeneratorQueue', + ])); + // 3. Perform ! AsyncGeneratorStart(generator, FunctionBody). + X(AsyncGeneratorStart(generator, FunctionBody)); + // 4. Return Completion { [[Type]]: return, [[Value]]: generator, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: generator, Target: undefined }); +} + +// #sec-async-function-definitions-EvaluateBody +// AsyncFunctionBody : FunctionBody +export function* EvaluateBody_AsyncFunctionBody(FunctionBody, functionObject, argumentsList) { + // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 2. Let declResult be FunctionDeclarationInstantiation(functionObject, argumentsList). + const declResult = yield* FunctionDeclarationInstantiation(functionObject, argumentsList); + // 3. If declResult is not an abrupt completion, then + if (!(declResult instanceof AbruptCompletion)) { + // a. Perform ! AsyncFunctionStart(promiseCapability, FunctionBody). + X(AsyncFunctionStart(promiseCapability, FunctionBody)); + } else { // 4. Else, + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »). + X(Call(promiseCapability.Reject, Value.undefined, [declResult.Value])); + } + // 5. Return Completion { [[Type]]: return, [[Value]]: promiseCapability.[[Promise]], [[Target]]: empty }. + return new Completion({ Type: 'return', Value: promiseCapability.Promise, Target: undefined }); +} + +// FunctionBody : FunctionStatementList +// ConciseBody : ExpressionBody +// GeneratorBody : FunctionBody +// AsyncGeneratorBody : FunctionBody +// AsyncFunctionBody : FunctionBody +// AsyncConciseBody : ExpressionBody +export function EvaluateBody(Body, functionObject, argumentsList) { + switch (Body.type) { + case 'FunctionBody': + return EvaluateBody_FunctionBody(Body, functionObject, argumentsList); + case 'ConciseBody': + return EvaluateBody_ConciseBody(Body, functionObject, argumentsList); + case 'GeneratorBody': + return EvaluateBody_GeneratorBody(Body, functionObject, argumentsList); + case 'AsyncGeneratorBody': + return EvaluateBody_AsyncGeneratorBody(Body, functionObject, argumentsList); + case 'AsyncFunctionBody': + return EvaluateBody_AsyncFunctionBody(Body, functionObject, argumentsList); + case 'AsyncConciseBody': + return EvaluateBody_AsyncConciseBody(Body, functionObject, argumentsList); + default: + throw new OutOfRange('EvaluateBody', Body); + } +} diff --git a/engine262/src/runtime-semantics/EvaluateCall.mjs b/engine262/src/runtime-semantics/EvaluateCall.mjs new file mode 100644 index 0000000..e42aac9 --- /dev/null +++ b/engine262/src/runtime-semantics/EvaluateCall.mjs @@ -0,0 +1,62 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { + Assert, + IsPropertyReference, + IsCallable, + GetThisValue, + PrepareForTailCall, + Call, + GetBase, +} from '../abstract-ops/all.mjs'; +import { Q, Completion, AbruptCompletion } from '../completion.mjs'; +import { EnvironmentRecord } from '../environment.mjs'; +import { ArgumentListEvaluation } from './all.mjs'; + +// #sec-evaluatecall +export function* EvaluateCall(func, ref, args, tailPosition) { + // 1. If Type(ref) is Reference, then + let thisValue; + if (Type(ref) === 'Reference') { + // a. If IsPropertyReference(ref) is true, then + if (IsPropertyReference(ref) === Value.true) { + // i. Let thisValue be GetThisValue(ref). + thisValue = GetThisValue(ref); + } else { + // i. Assert: the base of ref is an Environment Record. + Assert(ref.BaseValue instanceof EnvironmentRecord); + // ii. Let envRef be GetBase(ref). + const refEnv = GetBase(ref); + // iii. Let thisValue be envRef.WithBaseObject(). + thisValue = refEnv.WithBaseObject(); + } + } else { + // a. Let thisValue be undefined. + thisValue = Value.undefined; + } + // 3. Let argList be ? ArgumentListEvaluation of arguments. + const argList = Q(yield* ArgumentListEvaluation(args)); + // 4. If Type(func) is not Object, throw a TypeError exception. + if (Type(func) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); + } + // 5. If IsCallable(func) is false, throw a TypeError exception. + if (IsCallable(func) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); + } + // 6. If tailPosition is true, perform PrepareForTailCall(). + if (tailPosition) { + PrepareForTailCall(); + } + // 7. Let result be Call(func, thisValue, argList). + const result = Call(func, thisValue, argList); + // 8. Assert: If tailPosition is true, the above call will not return here but instead + // evaluation will continue as if the following return has already occurred. + Assert(!tailPosition); + // 9. Assert: If result is not an abrupt completion, then Type(result) is an ECMAScript language type. + if (!(result instanceof AbruptCompletion)) { + Assert(result instanceof Value || result instanceof Completion); + } + // 10. Return result. + return result; +} diff --git a/engine262/src/runtime-semantics/EvaluatePropertyAccess.mjs b/engine262/src/runtime-semantics/EvaluatePropertyAccess.mjs new file mode 100644 index 0000000..8c769d1 --- /dev/null +++ b/engine262/src/runtime-semantics/EvaluatePropertyAccess.mjs @@ -0,0 +1,46 @@ +import { + RequireObjectCoercible, + GetValue, + ToPropertyKey, + Assert, +} from '../abstract-ops/all.mjs'; +import { Value, Reference } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-evaluate-expression-key-property-access +export function* EvaluatePropertyAccessWithExpressionKey(baseValue, expression, strict) { + // 1. Let propertyNameReference be the result of evaluating expression. + const propertyNameReference = yield* Evaluate(expression); + // 2. Let propertyNameValue be ? GetValue(propertyNameReference). + const propertyNameValue = Q(GetValue(propertyNameReference)); + // 3. Let bv be ? RequireObjectCoercible(baseValue). + const bv = Q(RequireObjectCoercible(baseValue)); + // 4. Let propertyKey be ? ToPropertyKey(propertyNameValue). + const propertyKey = Q(ToPropertyKey(propertyNameValue)); + // 5. Return a value of type Reference whose base value component is bv, whose + // referenced name component is propertyKey, and whose strict reference flag is strict. + return new Reference({ + BaseValue: bv, + ReferencedName: propertyKey, + StrictReference: strict ? Value.true : Value.false, + }); +} + +// #sec-evaluate-identifier-key-property-access +export function EvaluatePropertyAccessWithIdentifierKey(baseValue, identifierName, strict) { + // 1. Assert: identifierName is an IdentifierName. + Assert(identifierName.type === 'IdentifierName'); + // 2. Let bv be ? RequireObjectCoercible(baseValue). + const bv = Q(RequireObjectCoercible(baseValue)); + // 3. Let propertyNameString be StringValue of IdentifierName + const propertyNameString = StringValue(identifierName); + // 4. Return a value of type Reference whose base value component is bv, whose + // referenced name component is propertyNameString, and whose strict reference flag is strict. + return new Reference({ + BaseValue: bv, + ReferencedName: propertyNameString, + StrictReference: strict ? Value.true : Value.false, + }); +} diff --git a/engine262/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mjs b/engine262/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mjs new file mode 100644 index 0000000..ffd93f7 --- /dev/null +++ b/engine262/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mjs @@ -0,0 +1,18 @@ +import { Evaluate } from '../evaluator.mjs'; +import { GetValue } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { ApplyStringOrNumericBinaryOperator } from './all.mjs'; + +// #sec-evaluatestringornumericbinaryexpression +export function* EvaluateStringOrNumericBinaryExpression(leftOperand, opText, rightOperand) { + // 1. Let lref be the result of evaluating leftOperand. + const lref = yield* Evaluate(leftOperand); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let rref be the result of evaluating rightOperand. + const rref = yield* Evaluate(rightOperand); + // 4. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + // 5. Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval). + return Q(ApplyStringOrNumericBinaryOperator(lval, opText, rval)); +} diff --git a/engine262/src/runtime-semantics/ExponentiationExpression.mjs b/engine262/src/runtime-semantics/ExponentiationExpression.mjs new file mode 100644 index 0000000..aad97ae --- /dev/null +++ b/engine262/src/runtime-semantics/ExponentiationExpression.mjs @@ -0,0 +1,9 @@ +import { Q } from '../completion.mjs'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mjs'; + +// #sec-exp-operator-runtime-semantics-evaluation +// ExponentiationExpression : UpdateExpression ** ExponentiationExpression +export function* Evaluate_ExponentiationExpression({ UpdateExpression, ExponentiationExpression }) { + // 1. Return ? EvaluateStringOrNumericBinaryExpression(UpdateExpression, **, ExponentiationExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(UpdateExpression, '**', ExponentiationExpression)); +} diff --git a/engine262/src/runtime-semantics/ExportDeclaration.mjs b/engine262/src/runtime-semantics/ExportDeclaration.mjs new file mode 100644 index 0000000..58846be --- /dev/null +++ b/engine262/src/runtime-semantics/ExportDeclaration.mjs @@ -0,0 +1,88 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { GetValue } from '../abstract-ops/all.mjs'; +import { BoundNames, IsAnonymousFunctionDefinition } from '../static-semantics/all.mjs'; +import { NormalCompletion, Q } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + NamedEvaluation, + InitializeBoundName, + BindingClassDeclarationEvaluation, +} from './all.mjs'; + +// #sec-exports-runtime-semantics-evaluation +// ExportDeclaration : +// `export` ExportFromClause FromClause `;` +// `export` NamedExports `;` +// `export` VariableDeclaration +// `export` Declaration +// `export` `default` HoistableDeclaration +// `export` `default` ClassDeclaration +// `export` `default` AssignmentExpression `;` +export function* Evaluate_ExportDeclaration(ExportDeclaration) { + const { + FromClause, NamedExports, + VariableStatement, + Declaration, + default: isDefault, + HoistableDeclaration, + ClassDeclaration, + AssignmentExpression, + } = ExportDeclaration; + + if (FromClause || NamedExports) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + if (VariableStatement) { + // 1. Return the result of evaluating VariableStatement. + return yield* Evaluate(VariableStatement); + } + if (Declaration) { + // 1. Return the result of evaluating Declaration. + return yield* Evaluate(ExportDeclaration.Declaration); + } + if (!isDefault) { + throw new OutOfRange('Evaluate_ExportDeclaration', ExportDeclaration); + } + if (HoistableDeclaration) { + // 1. Return the result of evaluating HoistableDeclaration. + return yield* Evaluate(HoistableDeclaration); + } + if (ClassDeclaration) { + // 1. Let value be ? BindingClassDeclarationEvaluation of ClassDeclaration. + const value = Q(yield* BindingClassDeclarationEvaluation(ClassDeclaration)); + // 2. Let className be the sole element of BoundNames of ClassDeclaration. + const className = BoundNames(ClassDeclaration)[0]; + // If className is ~default~, then + if (className === 'default') { + // a. Let env be the running execution context's LexicalEnvironment. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // b. Perform ? InitializeBoundName(~default~, value, env). + Q(InitializeBoundName('default', value, env)); + } + // 3. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + if (AssignmentExpression) { + let value; + // 1. If IsAnonymousFunctionDefinition(AssignmentExpression) is true, then + if (IsAnonymousFunctionDefinition(AssignmentExpression)) { + // a. Let value be NamedEvaluation of AssignmentExpression with argument "default". + value = yield* NamedEvaluation(AssignmentExpression, new Value('default')); + } else { // 2. Else, + // a. Let rhs be the result of evaluating AssignmentExpression. + const rhs = yield* Evaluate(AssignmentExpression); + // a. Let value be ? GetValue(rhs). + value = Q(GetValue(rhs)); + } + // 3. Let env be the running execution context's LexicalEnvironment. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Perform ? InitializeBoundName(~default~, value, env). + Q(InitializeBoundName('default', value, env)); + // 5. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + throw new OutOfRange('Evaluate_ExportDeclaration', ExportDeclaration); +} diff --git a/engine262/src/runtime-semantics/ExpressionStatement.mjs b/engine262/src/runtime-semantics/ExpressionStatement.mjs new file mode 100644 index 0000000..1ca2e3d --- /dev/null +++ b/engine262/src/runtime-semantics/ExpressionStatement.mjs @@ -0,0 +1,13 @@ +import { GetValue } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-expression-statement-runtime-semantics-evaluation +// ExpressionStatement : +// Expression `;` +export function* Evaluate_ExpressionStatement({ Expression }) { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // 2. Return ? GetValue(exprRef). + return Q(GetValue(exprRef)); +} diff --git a/engine262/src/runtime-semantics/FunctionDeclaration.mjs b/engine262/src/runtime-semantics/FunctionDeclaration.mjs new file mode 100644 index 0000000..8feead5 --- /dev/null +++ b/engine262/src/runtime-semantics/FunctionDeclaration.mjs @@ -0,0 +1,10 @@ +import { NormalCompletion } from '../completion.mjs'; + +// #sec-function-definitions-runtime-semantics-evaluation +// FunctionDeclaration : +// function BindingIdentifier ( FormalParameters ) { FunctionBody } +// function ( FormalParameters ) { FunctionBody } +export function Evaluate_FunctionDeclaration(_FunctionDeclaration) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/FunctionDeclarationInstantiation.mjs b/engine262/src/runtime-semantics/FunctionDeclarationInstantiation.mjs new file mode 100644 index 0000000..e7be2dd --- /dev/null +++ b/engine262/src/runtime-semantics/FunctionDeclarationInstantiation.mjs @@ -0,0 +1,267 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + Assert, + CreateListIteratorRecord, + CreateMappedArgumentsObject, + CreateUnmappedArgumentsObject, +} from '../abstract-ops/all.mjs'; +import { + BoundNames, + IsConstantDeclaration, + IsSimpleParameterList, + ContainsExpression, + VarDeclaredNames, + VarScopedDeclarations, + LexicallyDeclaredNames, + LexicallyScopedDeclarations, +} from '../static-semantics/all.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { Q, X, NormalCompletion } from '../completion.mjs'; +import { ValueSet } from '../helpers.mjs'; +import { + InstantiateFunctionObject, + IteratorBindingInitialization_FormalParameters, +} from './all.mjs'; + +// #sec-functiondeclarationinstantiation +export function* FunctionDeclarationInstantiation(func, argumentsList) { + // 1. Let calleeContext be the running execution context. + const calleeContext = surroundingAgent.runningExecutionContext; + // 2. Let code be func.[[ECMAScriptCode]]. + const code = func.ECMAScriptCode; + // 3. Let strict be func.[[Strict]]. + const strict = func.Strict; + // 4. Let formals be func.[[FormalParameters]]. + const formals = func.FormalParameters; + // 5. Let parameterNames be BoundNames of formals. + const parameterNames = BoundNames(formals); + // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. + const hasDuplicates = new ValueSet(parameterNames).size !== parameterNames.length; + // 7. Let simpleParameterList be IsSimpleParameterList of formals. + const simpleParameterList = IsSimpleParameterList(formals); + // 8. Let hasParameterExpressions be ContainsExpression of formals. + const hasParameterExpressions = ContainsExpression(formals); + // 9. Let varNames be the VarDeclaredNames of code. + const varNames = VarDeclaredNames(code); + // 10. Let varDeclarations be the VarScopedDeclarations of code. + const varDeclarations = VarScopedDeclarations(code); + // 11. Let lexicalNames be the LexicallyDeclaredNames of code. + const lexicalNames = new ValueSet(LexicallyDeclaredNames(code)); + // 12. Let functionNames be a new empty List. + const functionNames = new ValueSet(); + // 13. Let functionNames be a new empty List. + const functionsToInitialize = []; + // 14. For each d in varDeclarations, in reverse list order, do + for (const d of [...varDeclarations].reverse()) { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + if (d.type !== 'VariableDeclaration' + && d.type !== 'ForBinding' + && d.type !== 'BindingIdentifier') { + // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + Assert(d.type === 'FunctionDeclaration' + || d.type === 'GeneratorDeclaration' + || d.type === 'AsyncFunctionDeclaration' + || d.type === 'AsyncGeneratorDeclaration'); + // ii. Let fn be the sole element of the BoundNames of d. + const fn = BoundNames(d)[0]; + // iii. If fn is not an element of functionNames, then + if (!functionNames.has(fn)) { + // 1. Insert fn as the first element of functionNames. + functionNames.add(fn); + // 2. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + // 3. Insert d as the first element of functionsToInitialize. + functionsToInitialize.unshift(d); + } + } + } + // 15. Let argumentsObjectNeeded be true. + let argumentsObjectNeeded = true; + // If func.[[ThisMode]] is lexical, then + if (func.ThisMode === 'lexical') { + // a. NOTE: Arrow functions never have an arguments objects. + // b. Set argumentsObjectNeeded to false. + argumentsObjectNeeded = false; + } else if (new ValueSet(parameterNames).has(new Value('arguments'))) { + // a. Set argumentsObjectNeeded to false. + argumentsObjectNeeded = false; + } else if (hasParameterExpressions === false) { + // a. If "arguments" is an element of functionNames or if "arguments" is an element of lexicalNames, then + if (functionNames.has(new Value('arguments')) || lexicalNames.has(new Value('arguments'))) { + // i. Set argumentsObjectNeeded to false. + argumentsObjectNeeded = false; + } + } + let env; + // 19. If strict is true or if hasParameterExpressions is false, then + if (strict || hasParameterExpressions === false) { + // a. NOTE: Only a single lexical environment is needed for the parameters and top-level vars. + // b. Let env be the LexicalEnvironment of calleeContext. + env = calleeContext.LexicalEnvironment; + } else { + // a. NOTE: A separate Environment Record is needed to ensure that bindings created by direct eval + // calls in the formal parameter list are outside the environment where parameters are declared. + // b. Let calleeEnv be the LexicalEnvironment of calleeContext. + const calleeEnv = calleeContext.LexicalEnvironment; + // c. Let env be NewDeclarativeEnvironment(calleeEnv). + env = NewDeclarativeEnvironment(calleeEnv); + // d. Assert: The VariableEnvironment of calleeContext is calleeEnv. + Assert(calleeContext.VariableEnvironment === calleeEnv); + // e. Set the LexicalEnvironment of calleeContext to env. + calleeContext.LexicalEnvironment = env; + } + // 21. For each String paramName in parameterNames, do + for (const paramName of parameterNames) { + // a. Let alreadyDeclared be env.HasBinding(paramName). + const alreadyDeclared = env.HasBinding(paramName); + // b. NOTE: Early errors ensure that duplicate parameter names can only occur in + // non-strict functions that do not have parameter default values or rest parameters. + // c. If alreadyDeclared is false, then + if (alreadyDeclared === Value.false) { + // i. Perform ! env.CreateMutableBinding(paramName, false). + X(env.CreateMutableBinding(paramName, Value.false)); + // ii. If hasDuplicates is true, then + if (hasDuplicates === true) { + // 1. Perform ! env.InitializeBinding(paramName, undefined). + X(env.InitializeBinding(paramName, Value.undefined)); + } + } + } + // 22. If argumentsObjectNeeded is true, then + let parameterBindings; + if (argumentsObjectNeeded === true) { + let ao; + // a. If strict is true or if simpleParameterList is false, then + if (strict || simpleParameterList === false) { + // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). + ao = CreateUnmappedArgumentsObject(argumentsList); + } else { + // i. NOTE: mapped argument object is only provided for non-strict functions + // that don't have a rest parameter, any parameter default value initializers, + // or any destructured parameters. + // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). + ao = CreateMappedArgumentsObject(func, formals, argumentsList, env); + } + // c. If strict is true, then + if (strict) { + // i. Perform ! env.CreateImmutableBinding("arguments", false). + X(env.CreateImmutableBinding(new Value('arguments'), Value.false)); + } else { + // i. Perform ! env.CreateMutableBinding("arguments", false). + X(env.CreateMutableBinding(new Value('arguments'), Value.false)); + } + // e. Call env.InitializeBinding("arguments", ao). + env.InitializeBinding(new Value('arguments'), ao); + // f. Let parameterBindings be a new List of parameterNames with "arguments" appended. + parameterBindings = new ValueSet([...parameterNames, new Value('arguments')]); + } else { + // a. Let parameterBindings be parameterNames. + parameterBindings = new ValueSet(parameterNames); + } + // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList). + const iteratorRecord = CreateListIteratorRecord(argumentsList); + // 25. If hasDuplicates is true, then + if (hasDuplicates) { + // a. Perform ? IteratorBindingInitialization for formals with iteratorRecord and undefined as arguments. + Q(yield* IteratorBindingInitialization_FormalParameters(formals, iteratorRecord, Value.undefined)); + } else { + // a. Perform ? IteratorBindingInitialization for formals with iteratorRecord and env as arguments. + Q(yield* IteratorBindingInitialization_FormalParameters(formals, iteratorRecord, env)); + } + let varEnv; + // 27. If hasParameterExpressions is false, then + if (hasParameterExpressions === false) { + // a. NOTE: Only a single lexical environment is needed for the parameters and top-level vars. + // b. Let instantiatedVarNames be a copy of the List parameterBindings. + const instantiatedVarNames = new ValueSet(parameterBindings); + // c. For each n in varNames, do + for (const n of varNames) { + // i. If n is not an element of instantiatedVarNames, then + if (!instantiatedVarNames.has(n)) { + // 1. Append n to instantiatedVarNames. + instantiatedVarNames.add(n); + // 2. Perform ! env.CreateMutableBinding(n, false). + X(env.CreateMutableBinding(n, Value.false)); + // 3. Call env.InitializeBinding(n, undefined). + env.InitializeBinding(n, Value.undefined); + } + } + // d. Let varEnv be env. + varEnv = env; + } else { + // a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions + // in the formal parameter list do not have visibility of declarations in the function body. + // b. Let varEnv be NewDeclarativeEnvironment(env). + varEnv = NewDeclarativeEnvironment(env); + // c. Set the VariableEnvironment of calleeContext to varEnv. + calleeContext.VariableEnvironment = varEnv; + // d. Let instantiatedVarNames be a new empty List. + const instantiatedVarNames = new ValueSet(); + // e. For each n in varNames, do + for (const n of varNames) { + // If n is not an element of instantiatedVarNames, then + if (!instantiatedVarNames.has(n)) { + // 1. Append n to instantiatedVarNames. + instantiatedVarNames.add(n); + // 2. Perform ! varEnv.CreateMutableBinding(n, false). + X(varEnv.CreateMutableBinding(n, Value.false)); + let initialValue; + // 3. If n is not an element of parameterBindings or if n is an element of functionNames, let initialValue be undefined. + if (!parameterBindings.has(n) || functionNames.has(n)) { + initialValue = Value.undefined; + } else { + // a. Let initialValue be ! env.GetBindingValue(n, false). + initialValue = X(env.GetBindingValue(n, Value.false)); + } + // 5. Call varEnv.InitializeBinding(n, initialValue). + varEnv.InitializeBinding(n, initialValue); + // 6. NOTE: vars whose names are the same as a formal parameter, initially have the same value as the corresponding initialized parameter. + } + } + } + // 29. NOTE: Annex B.3.3.1 adds additional steps at this point. + let lexEnv; + // 30. If strict is false, then + if (strict === false) { + // a. Let lexEnv be NewDeclarativeEnvironment(varEnv). + lexEnv = NewDeclarativeEnvironment(varEnv); + // b. NOTE: Non-strict functions use a separate lexical Environment Record for top-level lexical declarations + // so that a direct eval can determine whether any var scoped declarations introduced by the eval code + // conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions + // because a strict direct eval always places all declarations into a new Environment Record. + } else { + // a. Else, let lexEnv be varEnv. + lexEnv = varEnv; + } + // 32. Set the LexicalEnvironment of calleeContext to lexEnv. + calleeContext.LexicalEnvironment = lexEnv; + // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code. + const lexDeclarations = LexicallyScopedDeclarations(code); + // 34. For each element d in lexDeclarations, do + for (const d of lexDeclarations) { + // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal + // parameter, or a var name. Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + for (const dn of BoundNames(d)) { + // i. If IsConstantDeclaration of d is true, then + if (IsConstantDeclaration(d)) { + // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). + X(lexEnv.CreateImmutableBinding(dn, Value.true)); + } else { + // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). + X(lexEnv.CreateMutableBinding(dn, Value.false)); + } + } + } + // 35. For each Parse Node f in functionsToInitialize, do + for (const f of functionsToInitialize) { + // a. Let fn be the sole element of the BoundNames of f. + const fn = BoundNames(f)[0]; + // b. Let fo be InstantiateFunctionObject of f with argument lexEnv. + const fo = InstantiateFunctionObject(f, lexEnv); + // c. Perform ! varEnv.SetMutableBinding(fn, fo, false). + X(varEnv.SetMutableBinding(fn, fo, Value.false)); + } + // 36. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/FunctionExpression.mjs b/engine262/src/runtime-semantics/FunctionExpression.mjs new file mode 100644 index 0000000..509766a --- /dev/null +++ b/engine262/src/runtime-semantics/FunctionExpression.mjs @@ -0,0 +1,42 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { + OrdinaryFunctionCreate, + SetFunctionName, + MakeConstructor, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { NamedEvaluation } from './all.mjs'; + +// #sec-function-definitions-runtime-semantics-evaluation +// FunctionExpression : +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` +export function* Evaluate_FunctionExpression(FunctionExpression) { + const { BindingIdentifier, FormalParameters, FunctionBody } = FunctionExpression; + if (!BindingIdentifier) { + return yield* NamedEvaluation(FunctionExpression, new Value('')); + } + // 1. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let funcEnv be NewDeclarativeEnvironment(scope). + const funcEnv = NewDeclarativeEnvironment(scope); + // 3. Let name be StringValue of BindingIdentifier. + const name = StringValue(BindingIdentifier); + // 4. Perform funcEnv.CreateImmutableBinding(name, false). + funcEnv.CreateImmutableBinding(name, Value.false); + // 5. Let sourceText be the source text matched by FunctionExpression. + const sourceText = sourceTextMatchedBy(FunctionExpression); + // 6. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, FormalParameters, FunctionBody, non-lexical-this, funcEnv). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, FormalParameters, FunctionBody, 'non-lexical-this', funcEnv); + // 7. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 8. Perform MakeConstructor(closure). + MakeConstructor(closure); + // 9. Perform funcEnv.InitializeBinding(name, closure). + funcEnv.InitializeBinding(name, closure); + // 10. Return closure. + return closure; +} diff --git a/engine262/src/runtime-semantics/FunctionStatementList.mjs b/engine262/src/runtime-semantics/FunctionStatementList.mjs new file mode 100644 index 0000000..2c78191 --- /dev/null +++ b/engine262/src/runtime-semantics/FunctionStatementList.mjs @@ -0,0 +1,10 @@ +import { Evaluate_StatementList } from './all.mjs'; + +// #sec-function-definitions-runtime-semantics-evaluation +// FunctionStatementList : [empty] +// +// (implicit) +// FunctionStatementList : StatementList +export function Evaluate_FunctionStatementList(FunctionStatementList) { + return Evaluate_StatementList(FunctionStatementList); +} diff --git a/engine262/src/runtime-semantics/GeneratorExpression.mjs b/engine262/src/runtime-semantics/GeneratorExpression.mjs new file mode 100644 index 0000000..04c4354 --- /dev/null +++ b/engine262/src/runtime-semantics/GeneratorExpression.mjs @@ -0,0 +1,56 @@ +import { + DefinePropertyOrThrow, + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { NamedEvaluation } from './all.mjs'; + +// #sec-generator-function-definitions-runtime-semantics-evaluation +// GeneratorExpression : +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +// `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` +export function* Evaluate_GeneratorExpression(GeneratorExpression) { + const { BindingIdentifier, FormalParameters, GeneratorBody } = GeneratorExpression; + if (!BindingIdentifier) { + // 1. Return the result of performing NamedEvaluation for this GeneratorExpression with argument "". + return yield* NamedEvaluation(GeneratorExpression, new Value('')); + } + // 1. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let funcEnv be NewDeclarativeEnvironment(scope). + const funcEnv = NewDeclarativeEnvironment(scope); + // 3. Let name be StringValue of BindingIdentifier. + const name = StringValue(BindingIdentifier); + // 4. Perform funcEnv.CreateImmutableBinding(name, false). + funcEnv.CreateImmutableBinding(name, Value.false); + // 5. Let sourceText be the source text matched by GeneratorExpression. + const sourceText = sourceTextMatchedBy(GeneratorExpression); + // 6. Let closure be OrdinaryFunctionCreate(%Generator%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, funcEnv). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Generator%'), sourceText, FormalParameters, GeneratorBody, 'non-lexical-this', funcEnv)); + // 7. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 8. Let prototype be OrdinaryObjectCreate(%Generator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Generator.prototype%')); + // 9. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow( + closure, + new Value('prototype'), + Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }), + )); + // 10. Perform funcEnv.InitializeBinding(name, closure). + funcEnv.InitializeBinding(name, closure); + // 11. Return closure. + return closure; +} diff --git a/engine262/src/runtime-semantics/GetSubstitution.mjs b/engine262/src/runtime-semantics/GetSubstitution.mjs new file mode 100644 index 0000000..d1f488e --- /dev/null +++ b/engine262/src/runtime-semantics/GetSubstitution.mjs @@ -0,0 +1,117 @@ +import { + Assert, + Get, + ToString, + IsNonNegativeInteger, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-getsubstitution +export function GetSubstitution(matched, str, position, captures, namedCaptures, replacement) { + // 1. Assert: Type(matched) is String. + Assert(Type(matched) === 'String'); + // 2. Let matchLength be the number of code units in matched. + const matchLength = matched.stringValue().length; + // 3. Assert: Type(str) is String. + Assert(Type(str) === 'String'); + // 4. Let stringLength be the number of code units in str. + const stringLength = str.stringValue().length; + // 5. Assert: ! IsNonNegativeInteger(position) is true. + Assert(X(IsNonNegativeInteger(position)) === Value.true); + // 6. Assert: position ≤ stringLength. + Assert(position.numberValue() <= stringLength); + // 7. Assert: captures is a possibly empty List of Strings. + Assert(Array.isArray(captures) && captures.every((value) => Type(value) === 'String' || Type(value) === 'Undefined')); + // 8. Assert: Type(replacement) is String. + Assert(Type(replacement) === 'String'); + // 9. Let tailPos be position + matchLength. + const tailPos = position.numberValue() + matchLength; + // 10. Let m be the number of elements in captures. + const m = captures.length; + // 11. Let result be the String value derived from replacement by copying code unit elements from replacement + // to result while performing replacements as specified in Table 52. These $ replacements are done left-to-right, + // and, once such a replacement is performed, the new replacement text is not subject to further replacements. + const replacementStr = replacement.stringValue(); + let result = ''; + let i = 0; + while (i < replacementStr.length) { + const currentChar = replacementStr[i]; + if (currentChar === '$' && i < replacementStr.length - 1) { + const nextChar = replacementStr[i + 1]; + if (nextChar === '$') { + result += '$'; + i += 2; + } else if (nextChar === '&') { + result += matched.stringValue(); + i += 2; + } else if (nextChar === '`') { + if (position.numberValue() === 0) { + // Replacement is the empty String + } else { + result += str.stringValue().substring(0, position.numberValue()); + } + i += 2; + } else if (nextChar === '\'') { + if (tailPos >= stringLength) { + // Replacement is the empty String + } else { + result += str.stringValue().substring(tailPos); + } + i += 2; + } else if ('123456789'.includes(nextChar) && (i === replacementStr.length - 2 || !'0123456789'.includes(replacementStr[i + 2]))) { + const n = Number(nextChar); + if (n <= m) { + const capture = captures[n - 1]; + if (capture !== Value.undefined) { + result += capture.stringValue(); + } + } else { + result += `$${nextChar}`; + } + i += 2; + } else if (i < replacementStr.length - 2 && '0123456789'.includes(nextChar) && '0123456789'.includes(replacementStr[i + 2])) { + const nextNextChar = replacementStr[i + 2]; + const n = Number(nextChar + nextNextChar); + if (n !== 0 && n <= m) { + const capture = captures[n - 1]; + if (capture !== Value.undefined) { + result += capture.stringValue(); + } + } else { + result += `$${nextChar}${nextNextChar}`; + } + i += 3; + } else if (nextChar === '<') { + if (namedCaptures === Value.undefined) { + result += '$<'; + i += 2; + } else { + Assert(Type(namedCaptures) === 'Object'); + const nextSign = replacementStr.indexOf('>', i); + if (nextSign === -1) { + result += '$<'; + i += 2; + } else { + const groupName = new Value(replacementStr.substring(i + 2, nextSign)); + const capture = Q(Get(namedCaptures, groupName)); + if (capture === Value.undefined) { + // Replace the text with the empty string + } else { + result += Q(ToString(capture)).stringValue(); + } + i = nextSign + 1; + } + } + } else { + result += '$'; + i += 1; + } + } else { + result += currentChar; + i += 1; + } + } + // 12. Return result. + return new Value(result); +} diff --git a/engine262/src/runtime-semantics/GlobalDeclarationInstantiation.mjs b/engine262/src/runtime-semantics/GlobalDeclarationInstantiation.mjs new file mode 100644 index 0000000..0b70c23 --- /dev/null +++ b/engine262/src/runtime-semantics/GlobalDeclarationInstantiation.mjs @@ -0,0 +1,145 @@ +import { surroundingAgent } from '../engine.mjs'; +import { EnvironmentRecord } from '../environment.mjs'; +import { Assert } from '../abstract-ops/all.mjs'; +import { + BoundNames, + IsConstantDeclaration, + LexicallyDeclaredNames, + LexicallyScopedDeclarations, + VarDeclaredNames, + VarScopedDeclarations, +} from '../static-semantics/all.mjs'; +import { Value } from '../value.mjs'; +import { Q, NormalCompletion } from '../completion.mjs'; +import { ValueSet } from '../helpers.mjs'; +import { InstantiateFunctionObject } from './all.mjs'; + +export function GlobalDeclarationInstantiation(script, env) { + // 1. Assert: env is a global Environment Record. + Assert(env instanceof EnvironmentRecord); + // 2. Let lexNames be the LexicallyDeclaredNames of script. + const lexNames = LexicallyDeclaredNames(script); + // 3. Let varNames be the VarDeclaredNames of script. + const varNames = VarDeclaredNames(script); + // 4. For each name in lexNames, do + for (const name of lexNames) { + // 1. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. + if (env.HasVarDeclaration(name) === Value.true) { + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + } + // 1. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if (env.HasLexicalDeclaration(name) === Value.true) { + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + } + // 1. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). + const hasRestrictedGlobal = Q(env.HasRestrictedGlobalProperty(name)); + // 1. If hasRestrictedGlobal is true, throw a SyntaxError exception. + if (hasRestrictedGlobal === Value.true) { + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + } + } + // 5. For each name in varNames, do + for (const name of varNames) { + // 1. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if (env.HasLexicalDeclaration(name) === Value.true) { + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + } + } + // 6. Let varDeclarations be the VarScopedDeclarations of script. + const varDeclarations = VarScopedDeclarations(script); + // 7. Let functionsToInitialize be a new empty List. + const functionsToInitialize = []; + // 8. Let declaredFunctionNames be a new empty List. + const declaredFunctionNames = new ValueSet(); + // 9. For each d in varDeclarations, in reverse list order, do + for (const d of [...varDeclarations].reverse()) { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + if (d.type !== 'VariableDeclaration' + && d.type !== 'ForBinding' + && d.type !== 'BindingIdentifier') { + // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + Assert(d.type === 'FunctionDeclaration' + || d.type === 'GeneratorDeclaration' + || d.type === 'AsyncFunctionDeclaration' + || d.type === 'AsyncGeneratorDeclaration'); + // ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + // iii. Let fn be the sole element of the BoundNames of d. + const fn = BoundNames(d)[0]; + // iv. If fn is not an element of declaredFunctionNames, then + if (!declaredFunctionNames.has(fn)) { + // 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn). + const fnDefinable = Q(env.CanDeclareGlobalFunction(fn)); + // 2. If fnDefinable is false, throw a TypeError exception. + if (fnDefinable === Value.false) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', fn); + } + // 3. Append fn to declaredFunctionNames. + declaredFunctionNames.add(fn); + // 4. Insert d as the first element of functionsToInitialize. + functionsToInitialize.unshift(d); + } + } + } + // 10. Let declaredVarNames be a new empty List. + const declaredVarNames = new ValueSet(); + // 11. For each d in varDeclarations, do + for (const d of varDeclarations) { + // a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + if (d.type === 'VariableDeclaration' + || d.type === 'ForBinding' + || d.type === 'BindingIdentifier') { + // i. For each String vn in the BoundNames of d, do + for (const vn of BoundNames(d)) { + // 1. If vn is not an element of declaredFunctionNames, then + if (!declaredFunctionNames.has(vn)) { + // a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn). + const vnDefinable = Q(env.CanDeclareGlobalVar(vn)); + // b. If vnDefinable is false, throw a TypeError exception. + if (vnDefinable === Value.false) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', vn); + } + // c. If vn is not an element of declaredVarNames, then + if (!declaredVarNames.has(vn)) { + // i. Append vn to declaredVarNames. + declaredVarNames.add(vn); + } + } + } + } + } + // 12. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. However, if the global object is a Proxy exotic object it may exhibit behaviours that cause abnormal terminations in some of the following steps. + // 13. NOTE: Annex B.3.3.2 adds additional steps at this point. + // 14. Let lexDeclarations be the LexicallyScopedDeclarations of script. + const lexDeclarations = LexicallyScopedDeclarations(script); + // 15. For each element d in lexDeclarations, do + for (const d of lexDeclarations) { + // a. NOTE: Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + for (const dn of BoundNames(d)) { + // 1. If IsConstantDeclaration of d is true, then + if (IsConstantDeclaration(d)) { + // 1. Perform ? env.CreateImmutableBinding(dn, true). + Q(env.CreateImmutableBinding(dn, Value.true)); + } else { // 1. Else, + // 1. Perform ? env.CreateMutableBinding(dn, false). + Q(env.CreateMutableBinding(dn, Value.false)); + } + } + } + // 16. For each Parse Node f in functionsToInitialize, do + for (const f of functionsToInitialize) { + // a. Let fn be the sole element of the BoundNames of f. + const fn = BoundNames(f)[0]; + // b. Let fo be InstantiateFunctionObject of f with argument env. + const fo = InstantiateFunctionObject(f, env); + // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). + Q(env.CreateGlobalFunctionBinding(fn, fo, Value.false)); + } + // 17. For each String vn in declaredVarNames, in list order, do + for (const vn of declaredVarNames) { + // a. Perform ? env.CreateGlobalVarBinding(vn, false). + Q(env.CreateGlobalVarBinding(vn, Value.false)); + } + // 18. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/HoistableDeclaration.mjs b/engine262/src/runtime-semantics/HoistableDeclaration.mjs new file mode 100644 index 0000000..b8ab67d --- /dev/null +++ b/engine262/src/runtime-semantics/HoistableDeclaration.mjs @@ -0,0 +1,11 @@ +import { NormalCompletion } from '../completion.mjs'; + +// #sec-statement-semantics-runtime-semantics-evaluation +// HoistableDeclaration : +// GeneratorDeclaration +// AsyncFunctionDeclaration +// AsyncGeneratorDeclaration +export function Evaluate_HoistableDeclaration(_HoistableDeclaration) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/IdentifierReference.mjs b/engine262/src/runtime-semantics/IdentifierReference.mjs new file mode 100644 index 0000000..7bf035d --- /dev/null +++ b/engine262/src/runtime-semantics/IdentifierReference.mjs @@ -0,0 +1,13 @@ +import { ResolveBinding } from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-identifiers-runtime-semantics-evaluation +// IdentifierReference : +// Identifier +// `yield` +// `await` +export function Evaluate_IdentifierReference(IdentifierReference) { + // 1. Return ? ResolveBinding(StringValue of Identifier). + return Q(ResolveBinding(StringValue(IdentifierReference), undefined, IdentifierReference.strict)); +} diff --git a/engine262/src/runtime-semantics/IfStatement.mjs b/engine262/src/runtime-semantics/IfStatement.mjs new file mode 100644 index 0000000..a2d1d26 --- /dev/null +++ b/engine262/src/runtime-semantics/IfStatement.mjs @@ -0,0 +1,48 @@ +import { Evaluate } from '../evaluator.mjs'; +import { + GetValue, + ToBoolean, +} from '../abstract-ops/all.mjs'; +import { + Completion, + EnsureCompletion, + NormalCompletion, + Q, + UpdateEmpty, +} from '../completion.mjs'; +import { Value } from '../value.mjs'; + +// #sec-if-statement-runtime-semantics-evaluation +// IfStatement : +// `if` `(` Expression `)` Statement `else` Statement +// `if` `(` Expression `)` Statement +export function* Evaluate_IfStatement({ Expression, Statement_a, Statement_b }) { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // 2. Let exprValue be ! ToBoolean(? GetValue(exprRef)). + const exprValue = ToBoolean(Q(GetValue(exprRef))); + if (Statement_b) { + let stmtCompletion; + // 3. If exprValue is true, then + if (exprValue === Value.true) { + // a. Let stmtCompletion be the result of evaluating the first Statement. + stmtCompletion = yield* Evaluate(Statement_a); + } else { // 4. Else, + // a. Let stmtCompletion be the result of evaluating the second Statement. + stmtCompletion = yield* Evaluate(Statement_b); + } + // 5. Return Completion(UpdateEmpty(stmtCompletion, undefined)). + return Completion(UpdateEmpty(EnsureCompletion(stmtCompletion), Value.undefined)); + } else { + // 3. If exprValue is false, then + if (exprValue === Value.false) { + // a. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); + } else { // 4. Else, + // a. Let stmtCompletion be the result of evaluating Statement. + const stmtCompletion = yield* Evaluate(Statement_a); + // b. Return Completion(UpdateEmpty(stmtCompletion, undefined)). + return Completion(UpdateEmpty(EnsureCompletion(stmtCompletion), Value.undefined)); + } + } +} diff --git a/engine262/src/runtime-semantics/ImportCall.mjs b/engine262/src/runtime-semantics/ImportCall.mjs new file mode 100644 index 0000000..17059d1 --- /dev/null +++ b/engine262/src/runtime-semantics/ImportCall.mjs @@ -0,0 +1,30 @@ +import { surroundingAgent, HostImportModuleDynamically } from '../engine.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { + GetValue, + ToString, + NewPromiseCapability, + GetActiveScriptOrModule, +} from '../abstract-ops/all.mjs'; +import { Q, X, IfAbruptRejectPromise } from '../completion.mjs'; + +// #sec-import-calls +// ImportCall : `import` `(` AssignmentExpression `)` +export function* Evaluate_ImportCall({ AssignmentExpression }) { + // 1. Let referencingScriptOrModule be ! GetActiveScriptOrModule(). + const referencingScriptOrModule = X(GetActiveScriptOrModule()); + // 2. Let argRef be the result of evaluating AssignmentExpression. + const argRef = yield* Evaluate(AssignmentExpression); + // 3. Let specifier be ? GetValue(argRef). + const specifier = Q(GetValue(argRef)); + // 4. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 5. Let specifierString be ToString(specifier). + const specifierString = ToString(specifier); + // 6. IfAbruptRejectPromise(specifierString, promiseCapability). + IfAbruptRejectPromise(specifierString, promiseCapability); + // 7. Perform ! HostImportModuleDynamically(referencingScriptOrModule, specifierString, promiseCapability). + X(HostImportModuleDynamically(referencingScriptOrModule, specifierString, promiseCapability)); + // 8. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} diff --git a/engine262/src/runtime-semantics/ImportDeclaration.mjs b/engine262/src/runtime-semantics/ImportDeclaration.mjs new file mode 100644 index 0000000..d2510d8 --- /dev/null +++ b/engine262/src/runtime-semantics/ImportDeclaration.mjs @@ -0,0 +1,8 @@ +import { NormalCompletion } from '../completion.mjs'; + +// #sec-module-semantics-runtime-semantics-evaluation +// ModuleItem : ImportDeclaration +export function Evaluate_ImportDeclaration(_ImportDeclaration) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/ImportMeta.mjs b/engine262/src/runtime-semantics/ImportMeta.mjs new file mode 100644 index 0000000..97cfc48 --- /dev/null +++ b/engine262/src/runtime-semantics/ImportMeta.mjs @@ -0,0 +1,44 @@ +import { HostGetImportMetaProperties, HostFinalizeImportMeta } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { + Assert, + GetActiveScriptOrModule, + OrdinaryObjectCreate, + CreateDataPropertyOrThrow, +} from '../abstract-ops/all.mjs'; +import { X } from '../completion.mjs'; +import { SourceTextModuleRecord } from '../modules.mjs'; + +// #sec-meta-properties +// ImportMeta : `import` `.` `meta` +export function Evaluate_ImportMeta(_ImportMeta) { + // 1. Let module be ! GetActiveScriptOrModule(). + const module = X(GetActiveScriptOrModule()); + // 2. Assert: module is a Source Text Module Record. + Assert(module instanceof SourceTextModuleRecord); + // 3. Let importMeta be module.[[ImportMeta]]. + let importMeta = module.ImportMeta; + // 4. If importMeta is empty, then + if (importMeta === undefined) { + // a. Set importMeta to ! OrdinaryObjectCreate(null). + importMeta = X(OrdinaryObjectCreate(Value.null)); + // b. Let importMetaValues be ! HostGetImportMetaProperties(module). + const importMetaValues = X(HostGetImportMetaProperties(module)); + // c. For each Record { [[Key]], [[Value]] } p that is an element of importMetaValues, do + for (const p of importMetaValues) { + // i. Perform ! CreateDataPropertyOrThrow(importMeta, p.[[Key]], p.[[Value]]). + X(CreateDataPropertyOrThrow(importMeta, p.Key, p.Value)); + } + // d. Perform ! HostFinalizeImportMeta(importMeta, module). + X(HostFinalizeImportMeta(importMeta, module)); + // e. Set module.[[ImportMeta]] to importMeta. + module.ImportMeta = importMeta; + // f. Return importMeta. + return importMeta; + } else { // 5. Else, + // a. Assert: Type(importMeta) is Object. + Assert(Type(importMeta) === 'Object'); + // b. Return importMeta. + return importMeta; + } +} diff --git a/engine262/src/runtime-semantics/InstantiateFunctionObject.mjs b/engine262/src/runtime-semantics/InstantiateFunctionObject.mjs new file mode 100644 index 0000000..019258b --- /dev/null +++ b/engine262/src/runtime-semantics/InstantiateFunctionObject.mjs @@ -0,0 +1,121 @@ +import { + DefinePropertyOrThrow, + MakeConstructor, + OrdinaryObjectCreate, + SetFunctionName, + OrdinaryFunctionCreate, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { X } from '../completion.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { Descriptor, Value } from '../value.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; + +// 14.1.20 #sec-function-definitions-runtime-semantics-instantiatefunctionobject +// FunctionDeclaration : +// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +export function InstantiateFunctionObject_FunctionDeclaration(FunctionDeclaration, scope) { + const { BindingIdentifier, FormalParameters, FunctionBody } = FunctionDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : new Value('default'); + // 2. Let sourceText be the source text matched by FunctionDeclaration. + const sourceText = sourceTextMatchedBy(FunctionDeclaration); + // 3. Let F be OrdinaryFunctionCreate(%Function.prototype%, sourceText, FormalParameters, FunctionBody, non-lexical-this, scope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, FormalParameters, FunctionBody, 'non-lexical-this', scope)); + // 4. Perform SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Perform MakeConstructor(F). + MakeConstructor(F); + // 6. Return F. + return F; +} + +// 14.4.11 #sec-generator-function-definitions-runtime-semantics-instantiatefunctionobject +// GeneratorDeclaration : +// `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +export function InstantiateFunctionObject_GeneratorDeclaration(GeneratorDeclaration, scope) { + const { BindingIdentifier, FormalParameters, GeneratorBody } = GeneratorDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : new Value('default'); + // 2. Let sourceText be the source text matched by GeneratorDeclaration. + const sourceText = sourceTextMatchedBy(GeneratorDeclaration); + // 3. Let F be OrdinaryFunctionCreate(%Generator%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, scope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Generator%'), sourceText, FormalParameters, GeneratorBody, 'non-lexical-this', scope)); + // 4. Perform SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Let prototype be OrdinaryObjectCreate(%Generator.prototype%). + const prototype = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Generator.prototype%'))); + // 6. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(F, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 7. Return F. + return F; +} + +// #sec-async-function-definitions-InstantiateFunctionObject +// AsyncFunctionDeclaration : +// `async` `function` BindingIdentifier `(` FormalParameters `)` `{` AsyncFunctionBody `}` +// `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}` +export function InstantiateFunctionObject_AsyncFunctionDeclaration(AsyncFunctionDeclaration, scope) { + const { BindingIdentifier, FormalParameters, AsyncFunctionBody } = AsyncFunctionDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : new Value('default'); + // 2. Let sourceText be the source text matched by AsyncFunctionDeclaration. + const sourceText = sourceTextMatchedBy(AsyncFunctionDeclaration); + // 3. Let F be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, FormalParameters, AsyncFunctionBody, non-lexical-this, scope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, FormalParameters, AsyncFunctionBody, 'non-lexical-this', scope)); + // 4. Perform ! SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Return F. + return F; +} + +// #sec-asyncgenerator-definitions-evaluatebody +// AsyncGeneratorDeclaration : +// `async` `function` `*` BindingIdentifier `(` FormalParameters`)` `{` AsyncGeneratorBody `}` +// `async` `function` `*` `(` FormalParameters`)` `{` AsyncGeneratorBody `}` +export function InstantiateFunctionObject_AsyncGeneratorDeclaration(AsyncGeneratorDeclaration, scope) { + const { BindingIdentifier, FormalParameters, AsyncGeneratorBody } = AsyncGeneratorDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : new Value('default'); + // 2. Let sourceText be the source text matched by AsyncGeneratorDeclaration. + const sourceText = sourceTextMatchedBy(AsyncGeneratorDeclaration); + // 3. Let F be ! OrdinaryFunctionCreate(%AsyncGenerator%, sourceText, FormalParameters, AsyncGeneratorBody, non-lexical-this, scope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, FormalParameters, AsyncGeneratorBody, 'non-lexical-this', scope)); + // 4. Perform ! SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Let prototype be ! OrdinaryObjectCreate(%AsyncGenerator.prototype%). + const prototype = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGenerator.prototype%'))); + // 6. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(F, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 7. Return F. + return F; +} + +export function InstantiateFunctionObject(AnyFunctionDeclaration, scope) { + switch (AnyFunctionDeclaration.type) { + case 'FunctionDeclaration': + return InstantiateFunctionObject_FunctionDeclaration(AnyFunctionDeclaration, scope); + case 'GeneratorDeclaration': + return InstantiateFunctionObject_GeneratorDeclaration(AnyFunctionDeclaration, scope); + case 'AsyncFunctionDeclaration': + return InstantiateFunctionObject_AsyncFunctionDeclaration(AnyFunctionDeclaration, scope); + case 'AsyncGeneratorDeclaration': + return InstantiateFunctionObject_AsyncGeneratorDeclaration(AnyFunctionDeclaration, scope); + + default: + throw new OutOfRange('InstantiateFunctionObject', AnyFunctionDeclaration); + } +} diff --git a/engine262/src/runtime-semantics/IteratorBindingInitialization.mjs b/engine262/src/runtime-semantics/IteratorBindingInitialization.mjs new file mode 100644 index 0000000..7df96bc --- /dev/null +++ b/engine262/src/runtime-semantics/IteratorBindingInitialization.mjs @@ -0,0 +1,290 @@ +import { Value } from '../value.mjs'; +import { + Assert, + GetValue, + InitializeReferencedBinding, + IteratorStep, + IteratorValue, + PutValue, + ResolveBinding, + ArrayCreate, + CreateDataPropertyOrThrow, + ToString, +} from '../abstract-ops/all.mjs'; +import { + AbruptCompletion, + NormalCompletion, + ReturnIfAbrupt, + Q, X, +} from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { + StringValue, + IsAnonymousFunctionDefinition, +} from '../static-semantics/all.mjs'; +import { NamedEvaluation, BindingInitialization } from './all.mjs'; + +// #sec-function-definitions-runtime-semantics-iteratorbindinginitialization +// FormalParameters : +// [empty] +// FormalParameterList `,` FunctionRestParameter +export function* IteratorBindingInitialization_FormalParameters(FormalParameters, iteratorRecord, environment) { + if (FormalParameters.length === 0) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + for (const FormalParameter of FormalParameters.slice(0, -1)) { + Q(yield* IteratorBindingInitialization_FormalParameter(FormalParameter, iteratorRecord, environment)); + } + + const last = FormalParameters[FormalParameters.length - 1]; + if (last.type === 'BindingRestElement') { + return yield* IteratorBindingInitialization_FunctionRestParameter(last, iteratorRecord, environment); + } + return yield* IteratorBindingInitialization_FormalParameter(last, iteratorRecord, environment); +} + +// FormalParameter : BindingElement +function IteratorBindingInitialization_FormalParameter(BindingElement, iteratorRecord, environment) { + return IteratorBindingInitialization_BindingElement(BindingElement, iteratorRecord, environment); +} + +// FunctionRestParameter : BindingRestElement +function IteratorBindingInitialization_FunctionRestParameter(FunctionRestParameter, iteratorRecord, environment) { + return IteratorBindingInitialization_BindingRestElement(FunctionRestParameter, iteratorRecord, environment); +} + +// BindingElement : +// SingleNameBinding +// BindingPattern +function IteratorBindingInitialization_BindingElement(BindingElement, iteratorRecord, environment) { + if (BindingElement.BindingPattern) { + return IteratorBindingInitialization_BindingPattern(BindingElement, iteratorRecord, environment); + } + return IteratorBindingInitialization_SingleNameBinding(BindingElement, iteratorRecord, environment); +} + +// SingleNameBinding : BindingIdentifier Initializer? +function* IteratorBindingInitialization_SingleNameBinding({ BindingIdentifier, Initializer }, iteratorRecord, environment) { + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(BindingIdentifier); + // 2. Let lhs be ? ResolveBinding(bindingId, environment). + const lhs = Q(ResolveBinding(bindingId, environment, BindingIdentifier.strict)); + let v; + // 3. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } else { // e. Else, + // i. Let v be IteratorValue(next). + v = IteratorValue(next); + // ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (v instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // iii. ReturnIfAbrupt(v). + ReturnIfAbrupt(v); + } + } + // 4. If iteratorRecord.[[Done]] is true, let v be undefined. + if (iteratorRecord.Done === Value.true) { + v = Value.undefined; + } + // 5. If Initializer is present and v is undefined, then + if (Initializer && v === Value.undefined) { + if (IsAnonymousFunctionDefinition(Initializer)) { + v = yield* NamedEvaluation(Initializer, bindingId); + } else { + const defaultValue = yield* Evaluate(Initializer); + v = Q(GetValue(defaultValue)); + } + } + // 6. If environment is undefined, return ? PutValue(lhs, v). + if (environment === Value.undefined) { + return Q(PutValue(lhs, v)); + } + // 7. Return InitializeReferencedBinding(lhs, v). + return InitializeReferencedBinding(lhs, v); +} + +// BindingRestElement : +// `...` BindingIdentiifer +// `...` BindingPattern +function* IteratorBindingInitialization_BindingRestElement({ BindingIdentifier, BindingPattern }, iteratorRecord, environment) { + if (BindingIdentifier) { + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + const lhs = Q(ResolveBinding(StringValue(BindingIdentifier), environment, BindingIdentifier.strict)); + // 2. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(new Value(0))); + // 3. Let n be 0. + let n = 0; + // 4. Repeat, + while (true) { + let next; + // a. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // i. Let next be IteratorStep(iteratorRecord). + next = IteratorStep(iteratorRecord); + // ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // iii. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // iv. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } + } + // b. If iteratorRecord.[[Done]] is true, then + if (iteratorRecord.Done === Value.true) { + // i. If environment is undefined, return ? PutValue(lhs, A). + if (environment === Value.undefined) { + return Q(PutValue(lhs, A)); + } + // ii. Return InitializeReferencedBinding(lhs, A). + return InitializeReferencedBinding(lhs, A); + } + // c. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // e. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(n), nextValue). + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(n))), nextValue)); + // g. Set n to n + 1. + n += 1; + } + } else { + // 1. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(new Value(0))); + // 2. Let n be 0. + let n = 0; + // 3. Repeat, + while (true) { + let next; + // a. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // i. Let next be IteratorStep(iteratorRecord). + next = IteratorStep(iteratorRecord); + // ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // iii. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // iv. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } + } + // b. If iteratorRecord.[[Done]] is true, then + if (iteratorRecord.Done === Value.true) { + // i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments. + return yield* BindingInitialization(BindingPattern, A, environment); + } + // c. Let nextValue be IteratorValue(next). + const nextValue = IteratorValue(next); + // d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (nextValue instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // e. ReturnIfAbrupt(nextValue). + ReturnIfAbrupt(nextValue); + // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(n), nextValue). + X(CreateDataPropertyOrThrow(A, X(ToString(new Value(n))), nextValue)); + // g. Set n to n + 1. + n += 1; + } + } +} + +function* IteratorBindingInitialization_BindingPattern({ BindingPattern, Initializer }, iteratorRecord, environment) { + let v; + // 1. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } else { // e. Else, + // i. Let v be IteratorValue(next). + v = IteratorValue(next); + // ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (v instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // iii. ReturnIfAbrupt(v). + ReturnIfAbrupt(v); + } + } + // 2. If iteratorRecord.[[Done]] is true, let v be undefined. + if (iteratorRecord.Done === Value.true) { + v = Value.undefined; + } + // 3. If Initializer is present and v is undefined, then + if (Initializer && v === Value.undefined) { + // a. Let defaultValue be the result of evaluating Initializer. + const defaultValue = yield* Evaluate(Initializer); + // b. Set v to ? GetValue(defaultValue). + v = Q(GetValue(defaultValue)); + } + // 4. Return the result of performing BindingInitialization of BindingPattern with v and environment as the arguments. + return yield* BindingInitialization(BindingPattern, v, environment); +} + +function IteratorDestructuringAssignmentEvaluation(node, iteratorRecord) { + Assert(node.type === 'Elision'); + // 1. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be IteratorStep(iteratorRecord). + const next = IteratorStep(iteratorRecord); + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + if (next instanceof AbruptCompletion) { + iteratorRecord.Done = Value.true; + } + // c. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // d. If next is false, set iteratorRecord.[[Done]] to true. + if (next === Value.false) { + iteratorRecord.Done = Value.true; + } + } + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} + +export function* IteratorBindingInitialization_ArrayBindingPattern({ BindingElementList, BindingRestElement }, iteratorRecord, environment) { + for (const BindingElement of BindingElementList) { + if (BindingElement.type === 'Elision') { + Q(IteratorDestructuringAssignmentEvaluation(BindingElement, iteratorRecord)); + } else { + Q(yield* IteratorBindingInitialization_BindingElement(BindingElement, iteratorRecord, environment)); + } + } + + if (BindingRestElement) { + return Q(yield* IteratorBindingInitialization_BindingRestElement(BindingRestElement, iteratorRecord, environment)); + } + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/KeyedBindingInitialization.mjs b/engine262/src/runtime-semantics/KeyedBindingInitialization.mjs new file mode 100644 index 0000000..fc41552 --- /dev/null +++ b/engine262/src/runtime-semantics/KeyedBindingInitialization.mjs @@ -0,0 +1,57 @@ +import { Value } from '../value.mjs'; +import { + GetV, + GetValue, + PutValue, + ResolveBinding, + InitializeReferencedBinding, +} from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { StringValue, IsAnonymousFunctionDefinition } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; +import { + NamedEvaluation, + BindingInitialization, +} from './all.mjs'; + +// #sec-runtime-semantics-keyedbindinginitialization +export function* KeyedBindingInitialization(node, value, environment, propertyName) { + if (node.type === 'BindingElement') { + // 1. Let v be ? GetV(value, propertyName). + let v = Q(GetV(value, propertyName)); + // 2. If Initializer is present and v is undefined, then + if (node.Initializer && v === Value.undefined) { + // a. Let defaultValue be the result of evaluating Initializer. + const defaultValue = yield* Evaluate(node.Initializer); + // b. Set v to ? GetValue(defaultValue). + v = Q(GetValue(defaultValue)); + } + // 2. Return the result of performing BindingInitialization for BindingPattern passing v and environment as arguments. + return yield* BindingInitialization(node.BindingPattern, v, environment); + } else { + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(node.BindingIdentifier); + // 2. Let lhs be ? ResolveBinding(bindingId, environment). + const lhs = Q(ResolveBinding(bindingId, environment, node.BindingIdentifier.strict)); + // 3. Let v be ? GetV(value, propertyName). + let v = Q(GetV(value, propertyName)); + if (node.Initializer && v === Value.undefined) { + // a. If IsAnonymousFunctionDefinition(Initializer) is true, then + if (IsAnonymousFunctionDefinition(node.Initializer)) { + // i. Set v to the result of performing NamedEvaluation for Initializer with argument bindingId. + v = yield* NamedEvaluation(node.Initializer, bindingId); + } else { // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = yield* Evaluate(node.Initializer); + // ii. Set v to ? GetValue(defaultValue). + v = Q(GetValue(defaultValue)); + } + } + // 5. If environment is undefined, return ? PutValue(lhs, v). + if (environment === Value.undefined) { + return Q(PutValue(lhs, v)); + } + // 6. Return InitializeReferencedBinding(lhs, v). + return InitializeReferencedBinding(lhs, v); + } +} diff --git a/engine262/src/runtime-semantics/LabelledEvaluation.mjs b/engine262/src/runtime-semantics/LabelledEvaluation.mjs new file mode 100644 index 0000000..7c8b37e --- /dev/null +++ b/engine262/src/runtime-semantics/LabelledEvaluation.mjs @@ -0,0 +1,741 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { NewDeclarativeEnvironment, DeclarativeEnvironmentRecord } from '../environment.mjs'; +import { + Assert, + Call, + GetIterator, + GetValue, + PutValue, + GetV, + ResolveBinding, + InitializeReferencedBinding, + IteratorComplete, + IteratorValue, + IteratorClose, + AsyncIteratorClose, + ToBoolean, + ToObject, + SameValue, +} from '../abstract-ops/all.mjs'; +import { + BoundNames, + IsConstantDeclaration, + IsDestructuring, + StringValue, +} from '../static-semantics/all.mjs'; +import { CreateForInIterator } from '../intrinsics/ForInIteratorPrototype.mjs'; +import { + Completion, + NormalCompletion, + AbruptCompletion, + UpdateEmpty, + EnsureCompletion, + ReturnIfAbrupt, + Await, + Q, X, +} from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + Evaluate_SwitchStatement, + Evaluate_VariableDeclarationList, + BindingInitialization, + DestructuringAssignmentEvaluation, + refineLeftHandSideExpression, +} from './all.mjs'; + +// #sec-loopcontinues +function LoopContinues(completion, labelSet) { + // 1. If completion.[[Type]] is normal, return true. + if (completion.Type === 'normal') { + return Value.true; + } + // 2. If completion.[[Type]] is not continue, return false. + if (completion.Type !== 'continue') { + return Value.false; + } + // 3. If completion.[[Target]] is empty, return true. + if (completion.Target === undefined) { + return Value.true; + } + // 4. If completion.[[Target]] is an element of labelSet, return true. + if (labelSet.has(completion.Target)) { + return Value.true; + } + // 5. Return false. + return Value.false; +} + +export function LabelledEvaluation(node, labelSet) { + switch (node.type) { + case 'DoWhileStatement': + case 'WhileStatement': + case 'ForStatement': + case 'ForInStatement': + case 'ForOfStatement': + case 'ForAwaitStatement': + case 'SwitchStatement': + return LabelledEvaluation_BreakableStatement(node, labelSet); + case 'LabelledStatement': + return LabelledEvaluation_LabelledStatement(node, labelSet); + default: + throw new OutOfRange('LabelledEvaluation', node); + } +} + +// #sec-labelled-statements-runtime-semantics-labelledevaluation +// LabelledStatement : LabelIdentifier `:` LabelledItem +function* LabelledEvaluation_LabelledStatement({ LabelIdentifier, LabelledItem }, labelSet) { + // 1. Let label be the StringValue of LabelIdentifier. + const label = StringValue(LabelIdentifier); + // 2. Append label as an element of labelSet. + labelSet.add(label); + // 3. Let stmtResult be LabelledEvaluation of LabelledItem with argument labelSet. + let stmtResult = EnsureCompletion(yield* LabelledEvaluation_LabelledItem(LabelledItem, labelSet)); + // 4. If stmtResult.[[Type]] is break and SameValue(stmtResult.[[Target]], label) is true, then + if (stmtResult.Type === 'break' && SameValue(stmtResult.Target, label) === Value.true) { + // a. Set stmtResult to NormalCompletion(stmtResult.[[Value]]). + stmtResult = NormalCompletion(stmtResult.Value); + } + // 5. Return Completion(stmtResult). + return Completion(stmtResult); +} + +// LabelledItem : +// Statement +// FunctionDeclaration +function LabelledEvaluation_LabelledItem(LabelledItem, labelSet) { + switch (LabelledItem.type) { + case 'DoWhileStatement': + case 'WhileStatement': + case 'ForStatement': + case 'ForInStatement': + case 'ForOfStatement': + case 'SwitchStatement': + case 'LabelledStatement': + return LabelledEvaluation(LabelledItem, labelSet); + default: + return Evaluate(LabelledItem); + } +} + +// #sec-statement-semantics-runtime-semantics-labelledevaluation +// BreakableStatement : +// IterationStatement +// SwitchStatement +// +// IterationStatement : +// (DoWhileStatement) +// (WhileStatement) +function* LabelledEvaluation_BreakableStatement(BreakableStatement, labelSet) { + switch (BreakableStatement.type) { + case 'DoWhileStatement': + case 'WhileStatement': + case 'ForStatement': + case 'ForInStatement': + case 'ForOfStatement': + case 'ForAwaitStatement': { + // 1. Let stmtResult be LabelledEvaluation of IterationStatement with argument labelSet. + let stmtResult = EnsureCompletion(yield* LabelledEvaluation_IterationStatement(BreakableStatement, labelSet)); + // 2. If stmtResult.[[Type]] is break, then + if (stmtResult.Type === 'break') { + // a. If stmtResult.[[Target]] is empty, then + if (stmtResult.Target === undefined) { + // i. If stmtResult.[[Value]] is empty, set stmtResult to NormalCompletion(undefined). + if (stmtResult.Value === undefined) { + stmtResult = NormalCompletion(Value.undefined); + } else { // ii. Else, set stmtResult to NormalCompletion(stmtResult.[[Value]]). + stmtResult = NormalCompletion(stmtResult.Value); + } + } + } + // 3. Return Completion(stmtResult). + return Completion(stmtResult); + } + case 'SwitchStatement': { + // 1. Let stmtResult be LabelledEvaluation of SwitchStatement. + let stmtResult = EnsureCompletion(yield* Evaluate_SwitchStatement(BreakableStatement)); + // 2. If stmtResult.[[Type]] is break, then + if (stmtResult.Type === 'break') { + // a. If stmtResult.[[Target]] is empty, then + if (stmtResult.Target === undefined) { + // i. If stmtResult.[[Value]] is empty, set stmtResult to NormalCompletion(undefined). + if (stmtResult.Value === undefined) { + stmtResult = NormalCompletion(Value.undefined); + } else { // ii. Else, set stmtResult to NormalCompletion(stmtResult.[[Value]]). + stmtResult = NormalCompletion(stmtResult.Value); + } + } + } + // 3. Return Completion(stmtResult). + return Completion(stmtResult); + } + default: + throw new OutOfRange('LabelledEvaluation_BreakableStatement', BreakableStatement); + } +} + +function LabelledEvaluation_IterationStatement(IterationStatement, labelSet) { + switch (IterationStatement.type) { + case 'DoWhileStatement': + return LabelledEvaluation_IterationStatement_DoWhileStatement(IterationStatement, labelSet); + case 'WhileStatement': + return LabelledEvaluation_IterationStatement_WhileStatement(IterationStatement, labelSet); + case 'ForStatement': + return LabelledEvaluation_BreakableStatement_ForStatement(IterationStatement, labelSet); + case 'ForInStatement': + return LabelledEvaluation_IterationStatement_ForInStatement(IterationStatement, labelSet); + case 'ForOfStatement': + return LabelledEvaluation_IterationStatement_ForOfStatement(IterationStatement, labelSet); + case 'ForAwaitStatement': + return LabelledEvaluation_IterationStatement_ForAwaitStatement(IterationStatement, labelSet); + default: + throw new OutOfRange('LabelledEvaluation_IterationStatement', IterationStatement); + } +} + +// #sec-do-while-statement-runtime-semantics-labelledevaluation +// IterationStatement : +// `do` Statement `while` `(` Expression `)` `;` +function* LabelledEvaluation_IterationStatement_DoWhileStatement({ Statement, Expression }, labelSet) { + // 1. Let V be undefined. + let V = Value.undefined; + // 2. Repeat, + while (true) { + // a. Let stmtResult be the result of evaluating Statement. + const stmtResult = EnsureCompletion(yield* Evaluate(Statement)); + // b. If LoopContinues(stmtResult, labelSet) is false, return Completion(UpdateEmpty(stmtResult, V)). + if (LoopContinues(stmtResult, labelSet) === Value.false) { + return Completion(UpdateEmpty(stmtResult, V)); + } + // c. If stmtResult.[[Value]] is not empty, set V to stmtResult.[[Value]]. + if (stmtResult.Value !== undefined) { + V = stmtResult.Value; + } + // d. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // e. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(GetValue(exprRef)); + // f. If ! ToBoolean(exprValue) is false, return NormalCompletion(V). + if (X(ToBoolean(exprValue)) === Value.false) { + return NormalCompletion(V); + } + } +} + + +// #sec-while-statement-runtime-semantics-labelledevaluation +// IterationStatement : +// `while` `(` Expression `)` Statement +function* LabelledEvaluation_IterationStatement_WhileStatement({ Expression, Statement }, labelSet) { + // 1. Let V be undefined. + let V = Value.undefined; + // 2. Repeat, + while (true) { + // a. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // b. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(GetValue(exprRef)); + // c. If ! ToBoolean(exprValue) is false, return NormalCompletion(V). + if (X(ToBoolean(exprValue)) === Value.false) { + return NormalCompletion(V); + } + // d. Let stmtResult be the result of evaluating Statement. + const stmtResult = EnsureCompletion(yield* Evaluate(Statement)); + // e. If LoopContinues(stmtResult, labelSet) is false, return Completion(UpdateEmpty(stmtResult, V)). + if (LoopContinues(stmtResult, labelSet) === Value.false) { + return Completion(UpdateEmpty(stmtResult, V)); + } + // f. If stmtResult.[[Value]] is not empty, set V to stmtResult.[[Value]]. + if (stmtResult.Value !== undefined) { + V = stmtResult.Value; + } + } +} + +// #sec-for-statement-runtime-semantics-labelledevaluation +// IterationStatement : +// `for` `(` Expression? `;` Expression? `;` Expresssion? `)` Statement +// `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement +// `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement +function* LabelledEvaluation_BreakableStatement_ForStatement(ForStatement, labelSet) { + const { + VariableDeclarationList, LexicalDeclaration, + Expression_a, Expression_b, Expression_c, + Statement, + } = ForStatement; + switch (true) { + case !!LexicalDeclaration: { + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv). + const loopEnv = NewDeclarativeEnvironment(oldEnv); + // 3. Let isConst be IsConstantDeclaration of LexicalDeclaration. + const isConst = IsConstantDeclaration(LexicalDeclaration); + // 4. Let boundNames be the BoundNames of LexicalDeclaration. + const boundNames = BoundNames(LexicalDeclaration); + // 5. For each element dn of boundNames, do + for (const dn of boundNames) { + // a. If isConst is true, then + if (isConst) { + // i. Perform ! loopEnv.CreateImmutableBinding(dn, true). + X(loopEnv.CreateImmutableBinding(dn, Value.true)); + } else { // b. Else, + // i. Perform ! loopEnv.CreateMutableBinding(dn, false). + X(loopEnv.CreateMutableBinding(dn, Value.false)); + } + } + // 6. Set the running execution context's LexicalEnvironment to loopEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = loopEnv; + // 7. Let forDcl be the result of evaluating LexicalDeclaration. + const forDcl = yield* Evaluate(LexicalDeclaration); + // 8. If forDcl is an abrupt completion, then + if (forDcl instanceof AbruptCompletion) { + // a. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // b. Return Completion(forDcl). + return Completion(forDcl); + } + // 9. If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be « ». + let perIterationLets; + if (isConst === false) { + perIterationLets = boundNames; + } else { + perIterationLets = []; + } + // 10. Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet). + const bodyResult = yield* ForBodyEvaluation(Expression_a, Expression_b, Statement, perIterationLets, labelSet); + // 11. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 12. Return Completion(bodyResult). + return Completion(bodyResult); + } + case !!VariableDeclarationList: { + // 1. Let varDcl be the result of evaluating VariableDeclarationList. + const varDcl = yield* Evaluate_VariableDeclarationList(VariableDeclarationList); + // 2. ReturnIfAbrupt(varDcl). + ReturnIfAbrupt(varDcl); + // 3. Return ? ForBodyEvaluation(the first Expression, the second Expression, Statement, « », labelSet). + return Q(yield* ForBodyEvaluation(Expression_a, Expression_b, Statement, [], labelSet)); + } + default: { + // 1. If the first Expression is present, then + if (Expression_a) { + // a. Let exprRef be the result of evaluating the first Expression. + const exprRef = yield* Evaluate(Expression_a); + // b. Perform ? GetValue(exprRef). + Q(GetValue(exprRef)); + } + // 2. Return ? ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet). + return Q(yield* ForBodyEvaluation(Expression_b, Expression_c, Statement, [], labelSet)); + } + } +} + +function* LabelledEvaluation_IterationStatement_ForInStatement(ForInStatement, labelSet) { + const { + LeftHandSideExpression, + ForBinding, + ForDeclaration, + Expression, + Statement, + } = ForInStatement; + switch (true) { + case !!LeftHandSideExpression && !!Expression: { + // IterationStatement : `for` `(` LeftHandSideExpression `in` Expression `)` Statement + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », Expression, enumerate). + const keyResult = Q(yield* ForInOfHeadEvaluation([], Expression, 'enumerate')); + // 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, enumerate, assignment, labelSet). + return Q(yield* ForInOfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, 'enumerate', 'assignment', labelSet)); + } + case !!ForBinding && !!Expression: { + // IterationStatement :`for` `(` `var` ForBinding `in` Expression `)` Statement + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », Expression, enumerate). + const keyResult = Q(yield* ForInOfHeadEvaluation([], Expression, 'enumerate')); + // 2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, enumerate, varBinding, labelSet). + return Q(yield* ForInOfBodyEvaluation(ForBinding, Statement, keyResult, 'enumerate', 'varBinding', labelSet)); + } + case !!ForDeclaration && !!Expression: { + // IterationStatement : `for` `(` ForDeclaration `in` Expression `)` Statement + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, Expression, enumerate). + const keyResult = Q(yield* ForInOfHeadEvaluation(BoundNames(ForDeclaration), Expression, 'enumerate')); + // 2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, enumerate, lexicalBinding, labelSet). + return Q(yield* ForInOfBodyEvaluation(ForDeclaration, Statement, keyResult, 'enumerate', 'lexicalBinding', labelSet)); + } + default: + throw new OutOfRange('LabelledEvaluation_IterationStatement_ForInStatement', ForInStatement); + } +} + +// IterationStatement : +// `for` `await` `(` LeftHandSideExpression `of` AssignmentExpression `)` Statement +// `for` `await` `(` `var` ForBinding `of` AssignmentExpression `)` Statement +// `for` `await` `(` ForDeclaration`of` AssignmentExpression `)` Statement +function* LabelledEvaluation_IterationStatement_ForAwaitStatement(ForAwaitStatement, labelSet) { + const { + LeftHandSideExpression, + ForBinding, + ForDeclaration, + AssignmentExpression, + Statement, + } = ForAwaitStatement; + switch (true) { + case !!LeftHandSideExpression: { + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, async-iterate). + const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'async-iterate')); + // 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, iterate, assignment, labelSet, async). + return Q(yield* ForInOfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, 'iterate', 'assignment', labelSet, 'async')); + } + case !!ForBinding: { + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, async-iterate). + const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'async-iterate')); + // 2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, iterate, varBinding, labelSet, async). + return Q(yield* ForInOfBodyEvaluation(ForBinding, Statement, keyResult, 'iterate', 'varBinding', labelSet, 'async')); + } + case !!ForDeclaration: { + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, AssignmentExpression, async-iterate). + const keyResult = Q(yield* ForInOfHeadEvaluation(BoundNames(ForDeclaration), AssignmentExpression, 'async-iterate')); + // 2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, iterate, lexicalBinding, labelSet, async). + return Q(yield* ForInOfBodyEvaluation(ForDeclaration, Statement, keyResult, 'iterate', 'lexicalBinding', labelSet, 'async')); + } + default: + throw new OutOfRange('LabelledEvaluation_IterationStatement_ForAwaitStatement', ForAwaitStatement); + } +} + +// #sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation +// IterationStatement : +// `for` `(` LeftHandSideExpression `of` AssignmentExpression `)` Statement +// `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement +// `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement +function* LabelledEvaluation_IterationStatement_ForOfStatement(ForOfStatement, labelSet) { + const { + LeftHandSideExpression, + ForBinding, + ForDeclaration, + AssignmentExpression, + Statement, + } = ForOfStatement; + switch (true) { + case !!LeftHandSideExpression: { + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, iterate). + const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'iterate')); + // 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, iterate, assignment, labelSet). + return Q(yield* ForInOfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, 'iterate', 'assignment', labelSet)); + } + case !!ForBinding: { + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, iterate). + const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'iterate')); + // 2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, iterate, varBinding, labelSet). + return Q(yield* ForInOfBodyEvaluation(ForBinding, Statement, keyResult, 'iterate', 'varBinding', labelSet)); + } + case !!ForDeclaration: { + // 1. Let keyResult be ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, AssignmentExpression, iterate). + const keyResult = Q(yield* ForInOfHeadEvaluation(BoundNames(ForDeclaration), AssignmentExpression, 'iterate')); + // 2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, iterate, lexicalBinding, labelSet). + return Q(yield* ForInOfBodyEvaluation(ForDeclaration, Statement, keyResult, 'iterate', 'lexicalBinding', labelSet)); + } + default: + throw new OutOfRange('LabelledEvaluation_BreakableStatement_ForOfStatement', ForOfStatement); + } +} + +// #sec-forbodyevaluation +function* ForBodyEvaluation(test, increment, stmt, perIterationBindings, labelSet) { + // 1. Let V be undefined. + let V = Value.undefined; + // 2. Perform ? CreatePerIterationEnvironment(perIterationBindings). + Q(CreatePerIterationEnvironment(perIterationBindings)); + // 3. Repeat, + while (true) { + // a. If test is not [empty], then + if (test) { + // i. Let testRef be the result of evaluating test. + const testRef = yield* Evaluate(test); + // ii. Let testValue be ? GetValue(testRef). + const testValue = Q(GetValue(testRef)); + // iii. If ! ToBoolean(testValue) is false, return NormalCompletion(V). + if (X(ToBoolean(testValue)) === Value.false) { + return NormalCompletion(V); + } + } + // b. Let result be the result of evaluating stmt. + const result = EnsureCompletion(yield* Evaluate(stmt)); + // c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)). + if (LoopContinues(result, labelSet) === Value.false) { + return Completion(UpdateEmpty(result, V)); + } + // d. If result.[[Value]] is not empty, set V to result.[[Value]]. + if (result.Value !== undefined) { + V = result.Value; + } + // e. Perform ? CreatePerIterationEnvironment(perIterationBindings). + Q(CreatePerIterationEnvironment(perIterationBindings)); + // f. If increment is not [empty], then + if (increment) { + // i. Let incRef be the result of evaluating increment. + const incRef = yield* Evaluate(increment); + // ii. Perform ? GetValue(incRef). + Q(GetValue(incRef)); + } + } +} + +// #sec-createperiterationenvironment +function CreatePerIterationEnvironment(perIterationBindings) { + // 1. If perIterationBindings has any elements, then + if (perIterationBindings.length > 0) { + // a. Let lastIterationEnv be the running execution context's LexicalEnvironment. + const lastIterationEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // b. Let outer be lastIterationEnv.[[OuterEnv]]. + const outer = lastIterationEnv.OuterEnv; + // c. Assert: outer is not null. + Assert(outer !== Value.null); + // d. Let thisIterationEnv be NewDeclarativeEnvironment(outer). + const thisIterationEnv = NewDeclarativeEnvironment(outer); + // e. For each element bn of perIterationBindings, do + for (const bn of perIterationBindings) { + // i. Perform ! thisIterationEnv.CreateMutableBinding(bn, false). + X(thisIterationEnv.CreateMutableBinding(bn, Value.false)); + // ii. Let lastValue be ? lastIterationEnv.GetBindingValue(bn, true). + const lastValue = Q(lastIterationEnv.GetBindingValue(bn, Value.true)); + // iii. Perform thisIterationEnv.InitializeBinding(bn, lastValue). + thisIterationEnv.InitializeBinding(bn, lastValue); + } + // f. Set the running execution context's LexicalEnvironment to thisIterationEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = thisIterationEnv; + } + // 2. Return undefined. + return Value.undefined; +} + +// #sec-runtime-semantics-forinofheadevaluation +function* ForInOfHeadEvaluation(uninitializedBoundNames, expr, iterationKind) { + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. If uninitializedBoundNames is not an empty List, then + if (uninitializedBoundNames.length > 0) { + // a. Assert: uninitializedBoundNames has no duplicate entries. + // b. Let newEnv be NewDeclarativeEnvironment(oldEnv). + const newEnv = NewDeclarativeEnvironment(oldEnv); + // c. For each string name in uninitializedBoundNames, do + for (const name of uninitializedBoundNames) { + // i. Perform ! newEnv.CreateMutableBinding(name, false). + X(newEnv.CreateMutableBinding(name, Value.false)); + } + // d. Set the running execution context's LexicalEnvironment to newEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = newEnv; + } + // 3. Let exprRef be the result of evaluating expr. + const exprRef = yield* Evaluate(expr); + // 4. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 5. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(GetValue(exprRef)); + // 6. If iterationKind is enumerate, then + if (iterationKind === 'enumerate') { + // a. If exprValue is undefined or null, then + if (exprValue === Value.undefined || exprValue === Value.null) { + // i. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }. + return new Completion({ Type: 'break', Value: undefined, Target: undefined }); + } + // b. Let obj be ! ToObject(exprValue). + const obj = X(ToObject(exprValue)); + // c. Let iterator be ? EnumerateObjectProperties(obj). + const iterator = Q(EnumerateObjectProperties(obj)); + // d. Let nextMethod be ! GetV(iterator, "next"). + const nextMethod = X(GetV(iterator, new Value('next'))); + // e. Return the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. + return { Iterator: iterator, NextMethod: nextMethod, Done: Value.false }; + } else { // 7. Else, + // a. Assert: iterationKind is iterate or async-iterate. + Assert(iterationKind === 'iterate' || iterationKind === 'async-iterate'); + // b. If iterationKind is async-iterate, let iteratorHint be async. + // c. Else, let iteratorHint be sync. + const iteratorHint = iterationKind === 'async-iterate' ? 'async' : 'sync'; + // d. Return ? GetIterator(exprValue, iteratorHint). + return Q(GetIterator(exprValue, iteratorHint)); + } +} + +// #sec-enumerate-object-properties +function EnumerateObjectProperties(O) { + return CreateForInIterator(O); +} + +// #sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +function* ForInOfBodyEvaluation(lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet, iteratorKind) { + // 1. If iteratorKind is not present, set iteratorKind to sync. + if (iterationKind === undefined) { + iterationKind = 'sync'; + } + // 2. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let V be undefined. + let V = Value.undefined; + // 4. Let destructuring be IsDestructuring of lhs. + const destructuring = IsDestructuring(lhs); + // 5. If destructuring is true and if lhsKind is assignment, then + let assignmentPattern; + if (destructuring && lhsKind === 'assignment') { + // a. Assert: lhs is a LeftHandSideExpression. + // b. Let assignmentPattern be the AssignmentPattern that is covered by lhs. + assignmentPattern = refineLeftHandSideExpression(lhs); + } + // 6. Repeat, + while (true) { + // a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + let nextResult = Q(Call(iteratorRecord.NextMethod, iteratorRecord.Iterator)); + // b. If iteratorKind is async, then set nextResult to ? Await(nextResult). + if (iteratorKind === 'async') { + nextResult = Q(yield* Await(nextResult)); + } + // c. If Type(nextResult) is not Object, throw a TypeError exception. + if (Type(nextResult) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', nextResult); + } + // d. Let done be ? IteratorComplete(nextResult). + const done = Q(IteratorComplete(nextResult)); + // e. If done is true, return NormalCompletion(V). + if (done === Value.true) { + return NormalCompletion(V); + } + // f. Let nextValue be ? IteratorValue(nextResult). + const nextValue = Q(IteratorValue(nextResult)); + // g. If lhsKind is either assignment or varBinding, then + let lhsRef; + let iterationEnv; + if (lhsKind === 'assignment' || lhsKind === 'varBinding') { + // i. If destructuring is false, then + if (destructuring === false) { + // 1. Let lhsRef be the result of evaluating lhs. (It may be evaluated repeatedly.) + lhsRef = yield* Evaluate(lhs); + } + } else { // h. Else, + // i. Assert: lhsKind is lexicalBinding. + Assert(lhsKind === 'lexicalBinding'); + // ii. Assert: lhs is a ForDeclaration. + Assert(lhs.type === 'ForDeclaration'); + // iii. Let iterationEnv be NewDeclarativeEnvironment(oldEnv). + iterationEnv = NewDeclarativeEnvironment(oldEnv); + // iv. Perform BindingInstantiation for lhs passing iterationEnv as the argument. + BindingInstantiation(lhs, iterationEnv); + // v. Set the running execution context's LexicalEnvironment to iterationEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = iterationEnv; + // vi. If destructuring is false, then + if (destructuring === false) { + // 1. Assert: lhs binds a single name. + // 2. Let lhsName be the sole element of BoundNames of lhs. + const lhsName = BoundNames(lhs)[0]; + // 3. Let lhsRef be ! ResolveBinding(lhsName). + lhsRef = X(ResolveBinding(lhsName, undefined, lhs.strict)); + } + } + let status; + // i. If destructuring is false, then + if (destructuring === false) { + // i. If lhsRef is an abrupt completion, then + if (lhsRef instanceof AbruptCompletion) { + // 1. Let status be lhsRef. + status = lhsRef; + } else if (lhsKind === 'lexicalBinding') { // ii. Else is lhsKind is lexicalBinding, then + // 1. Let status be InitializeReferencedBinding(lhsRef, nextValue). + status = InitializeReferencedBinding(lhsRef, nextValue); + } else { // iii. Else, + status = PutValue(lhsRef, nextValue); + } + } else { // j. Else, + // i. If lhsKind is assignment, then + if (lhsKind === 'assignment') { + // 1. Let status be DestructuringAssignmentEvaluation of assignmentPattern with argument nextValue. + status = yield* DestructuringAssignmentEvaluation(assignmentPattern, nextValue); + } else if (lhsKind === 'varBinding') { // ii. Else if lhsKind is varBinding, then + // 1. Assert: lhs is a ForBinding. + Assert(lhs.type === 'ForBinding'); + // 2. Let status be BindingInitialization of lhs with arguments nextValue and undefined. + status = yield* BindingInitialization(lhs, nextValue, Value.undefined); + } else { // iii. Else, + // 1. Assert: lhsKind is lexicalBinding. + Assert(lhsKind === 'lexicalBinding'); + // 2. Assert: lhs is a ForDeclaration. + Assert(lhs.type === 'ForDeclaration'); + // 3. Let status be BindingInitialization of lhs with arguments nextValue and iterationEnv. + status = yield* BindingInitialization(lhs, nextValue, iterationEnv); + } + } + // k. If status is an abrupt completion, then + if (status instanceof AbruptCompletion) { + // i. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // ii. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). + if (iteratorKind === 'async') { + return Q(yield* AsyncIteratorClose(iteratorRecord, status)); + } + // iii. if iterationKind is enumerate, then + if (iterationKind === 'enumerate') { + // 1. Return status. + return status; + } else { // iv. Else, + // 1. Assert: iterationKind is iterate. + Assert(iterationKind === 'iterate'); + // 2 .Return ? IteratorClose(iteratorRecord, status). + return Q(IteratorClose(iteratorRecord, status)); + } + } + // l. Let result be the result of evaluating stmt. + const result = EnsureCompletion(yield* Evaluate(stmt)); + // m. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // n. If LoopContinues(result, labelSet) is false, then + if (LoopContinues(result, labelSet) === Value.false) { + // i. If iterationKind is enumerate, then + if (iterationKind === 'enumerate') { + // 1. Return Completion(UpdateEmpty(result, V)). + return Completion(UpdateEmpty(result, V)); + } else { // ii. Else, + // 1. Assert: iterationKind is iterate. + Assert(iterationKind === 'iterate'); + // 2. Set status to UpdateEmpty(result, V). + status = UpdateEmpty(result, V); + // 3. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). + if (iteratorKind === 'async') { + return Q(yield* AsyncIteratorClose(iteratorRecord, status)); + } + // 4. Return ? IteratorClose(iteratorRecord, status). + return Q(IteratorClose(iteratorRecord, status)); + } + } + // o. If result.[[Value]] is not empty, set V to result.[[Value]]. + if (result.Value !== undefined) { + V = result.Value; + } + } +} + +// #sec-runtime-semantics-bindinginstantiation +// ForDeclaration : LetOrConst ForBinding +function BindingInstantiation({ LetOrConst, ForBinding }, environment) { + // 1. Assert: environment is a declarative Environment Record. + Assert(environment instanceof DeclarativeEnvironmentRecord); + // 2. For each element name of the BoundNames of ForBinding, do + for (const name of BoundNames(ForBinding)) { + // a. If IsConstantDeclaration of LetOrConst is true, then + if (IsConstantDeclaration(LetOrConst)) { + // i. Perform ! environment.CreateImmutableBinding(name, true). + X(environment.CreateImmutableBinding(name, Value.true)); + } else { // b. Else, + // i. Perform ! environment.CreateMutableBinding(name, false). + X(environment.CreateMutableBinding(name, Value.false)); + } + } +} + +// #sec-for-in-and-for-of-statements-runtime-semantics-evaluation +// ForBinding : BindingIdentifier +export function Evaluate_ForBinding({ BindingIdentifier, strict }) { + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(BindingIdentifier); + // 2. Return ? ResolveBinding(bindingId). + return Q(ResolveBinding(bindingId, undefined, strict)); +} diff --git a/engine262/src/runtime-semantics/LabelledStatement.mjs b/engine262/src/runtime-semantics/LabelledStatement.mjs new file mode 100644 index 0000000..2330ff9 --- /dev/null +++ b/engine262/src/runtime-semantics/LabelledStatement.mjs @@ -0,0 +1,10 @@ +import { ValueSet } from '../helpers.mjs'; +import { LabelledEvaluation } from './all.mjs'; + +// #sec-labelled-statements-runtime-semantics-evaluation +export function Evaluate_LabelledStatement(LabelledStatement) { + // 1. Let newLabelSet be a new empty List. + const newLabelSet = new ValueSet(); + // 2. Return LabelledEvaluation of this LabelledStatement with argument newLabelSet. + return LabelledEvaluation(LabelledStatement, newLabelSet); +} diff --git a/engine262/src/runtime-semantics/LexicalDeclaration.mjs b/engine262/src/runtime-semantics/LexicalDeclaration.mjs new file mode 100644 index 0000000..6e96af0 --- /dev/null +++ b/engine262/src/runtime-semantics/LexicalDeclaration.mjs @@ -0,0 +1,96 @@ +import { Evaluate } from '../evaluator.mjs'; +import { + NormalCompletion, + ReturnIfAbrupt, + Q, X, +} from '../completion.mjs'; +import { Value } from '../value.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { + GetValue, + InitializeReferencedBinding, + ResolveBinding, +} from '../abstract-ops/all.mjs'; +import { IsAnonymousFunctionDefinition, StringValue } from '../static-semantics/all.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { NamedEvaluation, BindingInitialization } from './all.mjs'; + +// #sec-let-and-const-declarations-runtime-semantics-evaluation +// LexicalBinding : +// BindingIdentifier +// BindingIdentifier Initializer +function* Evaluate_LexicalBinding_BindingIdentifier({ BindingIdentifier, Initializer, strict }) { + if (Initializer) { + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(BindingIdentifier); + // 2. Let lhs be ResolveBinding(bindingId). + const lhs = X(ResolveBinding(bindingId, undefined, strict)); + let value; + // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + if (IsAnonymousFunctionDefinition(Initializer)) { + // a. Let value be NamedEvaluation of Initializer with argument bindingId. + value = yield* NamedEvaluation(Initializer, bindingId); + } else { // 4. Else, + // a. Let rhs be the result of evaluating Initializer. + const rhs = yield* Evaluate(Initializer); + // b. Let value be ? GetValue(rhs). + value = Q(GetValue(rhs)); + } + // 5. Return InitializeReferencedBinding(lhs, value). + return InitializeReferencedBinding(lhs, value); + } else { + // 1. Let lhs be ResolveBinding(StringValue of BindingIdentifier). + const lhs = ResolveBinding(StringValue(BindingIdentifier), undefined, strict); + // 2. Return InitializeReferencedBinding(lhs, undefined). + return InitializeReferencedBinding(lhs, Value.undefined); + } +} + +// #sec-let-and-const-declarations-runtime-semantics-evaluation +// LexicalBinding : BindingPattern Initializer +function* Evaluate_LexicalBinding_BindingPattern(LexicalBinding) { + const { BindingPattern, Initializer } = LexicalBinding; + const rhs = yield* Evaluate(Initializer); + const value = Q(GetValue(rhs)); + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + return yield* BindingInitialization(BindingPattern, value, env); +} + +export function* Evaluate_LexicalBinding(LexicalBinding) { + switch (true) { + case !!LexicalBinding.BindingIdentifier: + return yield* Evaluate_LexicalBinding_BindingIdentifier(LexicalBinding); + case !!LexicalBinding.BindingPattern: + return yield* Evaluate_LexicalBinding_BindingPattern(LexicalBinding); + default: + throw new OutOfRange('Evaluate_LexicalBinding', LexicalBinding); + } +} + +// #sec-let-and-const-declarations-runtime-semantics-evaluation +// BindingList : BindingList `,` LexicalBinding +// +// (implicit) +// BindingList : LexicalBinding +export function* Evaluate_BindingList(BindingList) { + // 1. Let next be the result of evaluating BindingList. + // 2. ReturnIfAbrupt(next). + // 3. Return the result of evaluating LexicalBinding. + let next; + for (const LexicalBinding of BindingList) { + next = yield* Evaluate_LexicalBinding(LexicalBinding); + ReturnIfAbrupt(next); + } + return next; +} + +// #sec-let-and-const-declarations-runtime-semantics-evaluation +// LexicalDeclaration : LetOrConst BindingList `;` +export function* Evaluate_LexicalDeclaration({ BindingList }) { + // 1. Let next be the result of evaluating BindingList. + const next = yield* Evaluate_BindingList(BindingList); + // 2. ReturnIfAbrupt(next). + ReturnIfAbrupt(next); + // 3. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/Literal.mjs b/engine262/src/runtime-semantics/Literal.mjs new file mode 100644 index 0000000..829200a --- /dev/null +++ b/engine262/src/runtime-semantics/Literal.mjs @@ -0,0 +1,34 @@ +import { Value } from '../value.mjs'; +import { StringValue, NumericValue } from '../static-semantics/all.mjs'; +import { OutOfRange } from '../helpers.mjs'; + +// #sec-literals-runtime-semantics-evaluation +// Literal : +// NullLiteral +// BooleanLiteral +// NumericLiteral +// StringLiteral +export function Evaluate_Literal(Literal) { + switch (Literal.type) { + case 'NullLiteral': + // 1. Return null. + return Value.null; + case 'BooleanLiteral': + // 1. If BooleanLiteral is the token false, return false. + if (Literal.value === false) { + return Value.false; + } + // 2. If BooleanLiteral is the token true, return true. + if (Literal.value === true) { + return Value.true; + } + throw new OutOfRange('Evaluate_Literal', Literal); + case 'NumericLiteral': + // 1. Return the NumericValue of NumericLiteral as defined in 11.8.3. + return NumericValue(Literal); + case 'StringLiteral': + return StringValue(Literal); + default: + throw new OutOfRange('Evaluate_Literal', Literal); + } +} diff --git a/engine262/src/runtime-semantics/LogicalANDExpression.mjs b/engine262/src/runtime-semantics/LogicalANDExpression.mjs new file mode 100644 index 0000000..4e364ad --- /dev/null +++ b/engine262/src/runtime-semantics/LogicalANDExpression.mjs @@ -0,0 +1,24 @@ +import { Value } from '../value.mjs'; +import { GetValue, ToBoolean } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-binary-logical-operators-runtime-semantics-evaluation +// LogicalANDExpression : +// LogicalANDExpression `&&` BitwiseORExpression +export function* Evaluate_LogicalANDExpression({ LogicalANDExpression, BitwiseORExpression }) { + // 1. Let lref be the result of evaluating LogicalANDExpression. + const lref = yield* Evaluate(LogicalANDExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let lbool be ! ToBoolean(lval). + const lbool = X(ToBoolean(lval)); + // 4. If lbool is false, return lval. + if (lbool === Value.false) { + return lval; + } + // 5. Let rref be the result of evaluating BitwiseORExpression. + const rref = yield* Evaluate(BitwiseORExpression); + // 6. Return ? GetValue(rref). + return Q(GetValue(rref)); +} diff --git a/engine262/src/runtime-semantics/LogicalORExpression.mjs b/engine262/src/runtime-semantics/LogicalORExpression.mjs new file mode 100644 index 0000000..91cb677 --- /dev/null +++ b/engine262/src/runtime-semantics/LogicalORExpression.mjs @@ -0,0 +1,24 @@ +import { Value } from '../value.mjs'; +import { GetValue, ToBoolean } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-binary-logical-operators-runtime-semantics-evaluation +// LogicalORExpression : +// LogicalORExpression `||` LogicalANDExpression +export function* Evaluate_LogicalORExpression({ LogicalORExpression, LogicalANDExpression }) { + // 1. Let lref be the result of evaluating LogicalORExpression. + const lref = yield* Evaluate(LogicalORExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let lbool be ! ToBoolean(lval). + const lbool = X(ToBoolean(lval)); + // 4. If lbool is false, return lval. + if (lbool === Value.true) { + return lval; + } + // 5. Let rref be the result of evaluating LogicalANDExpression. + const rref = yield* Evaluate(LogicalANDExpression); + // 6. Return ? GetValue(rref). + return Q(GetValue(rref)); +} diff --git a/engine262/src/runtime-semantics/MV.mjs b/engine262/src/runtime-semantics/MV.mjs new file mode 100644 index 0000000..3c23587 --- /dev/null +++ b/engine262/src/runtime-semantics/MV.mjs @@ -0,0 +1,10 @@ +import { Value } from '../value.mjs'; + +// 7.1.3.1.1 #sec-runtime-semantics-mv-s +// StringNumericLiteral ::: +// [empty] +// StrWhiteSpace +// StrWhiteSpace_opt StrNumericLiteral StrWhiteSpace_opt +export function MV_StringNumericLiteral(StringNumericLiteral) { + return new Value(Number(StringNumericLiteral)); +} diff --git a/engine262/src/runtime-semantics/MemberExpression.mjs b/engine262/src/runtime-semantics/MemberExpression.mjs new file mode 100644 index 0000000..26c1c1b --- /dev/null +++ b/engine262/src/runtime-semantics/MemberExpression.mjs @@ -0,0 +1,52 @@ +import { GetValue } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + EvaluatePropertyAccessWithExpressionKey, + EvaluatePropertyAccessWithIdentifierKey, +} from './all.mjs'; + +// 12.3.2.1 #sec-property-accessors-runtime-semantics-evaluation +// MemberExpression : MemberExpression `[` Expression `]` +// CallExpression : CallExpression `[` Expression `]` +function* Evaluate_MemberExpression_Expression({ strict, MemberExpression, Expression }) { + // 1. Let baseReference be the result of evaluating |MemberExpression|. + const baseReference = yield* Evaluate(MemberExpression); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(GetValue(baseReference)); + // 3. If the code matched by this |MemberExpression| is strict mode code, let strict be true; else let strict be false. + // 4. Return ? EvaluatePropertyAccessWithExpressionKey(baseValue, |Expression|, strict). + return Q(yield* EvaluatePropertyAccessWithExpressionKey(baseValue, Expression, strict)); +} + +// 12.3.2.1 #sec-property-accessors-runtime-semantics-evaluation +// MemberExpression : MemberExpression `.` IdentifierName +// CallExpression : CallExpression `.` IdentifierName +function* Evaluate_MemberExpression_IdentifierName({ strict, MemberExpression, IdentifierName }) { + // 1. Let baseReference be the result of evaluating |MemberExpression|. + const baseReference = yield* Evaluate(MemberExpression); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(GetValue(baseReference)); + // 3. If the code matched by this |MemberExpression| is strict mode code, let strict be true; else let strict be false. + // 4. Return ? EvaluatePropertyAccessWithIdentifierKey(baseValue, |IdentifierName|, strict). + return Q(EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict)); +} + +// 12.3.2.1 #sec-property-accessors-runtime-semantics-evaluation +// MemberExpression : +// MemberExpression `[` Expression `]` +// MemberExpression `.` IdentifierName +// CallExpression : +// CallExpression `[` Expression `]` +// CallExpression `.` IdentifierName +export function Evaluate_MemberExpression(MemberExpression) { + switch (true) { + case !!MemberExpression.Expression: + return Evaluate_MemberExpression_Expression(MemberExpression); + case !!MemberExpression.IdentifierName: + return Evaluate_MemberExpression_IdentifierName(MemberExpression); + default: + throw new OutOfRange('Evaluate_MemberExpression', MemberExpression); + } +} diff --git a/engine262/src/runtime-semantics/Module.mjs b/engine262/src/runtime-semantics/Module.mjs new file mode 100644 index 0000000..7262e27 --- /dev/null +++ b/engine262/src/runtime-semantics/Module.mjs @@ -0,0 +1,14 @@ +import { Value } from '../value.mjs'; +import { NormalCompletion } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; + +// #sec-module-semantics-runtime-semantics-evaluation +// Module : +// [empty] +// ModuleBody +export function* Evaluate_Module({ ModuleBody }) { + if (!ModuleBody) { + return NormalCompletion(Value.undefined); + } + return yield* Evaluate(ModuleBody); +} diff --git a/engine262/src/runtime-semantics/ModuleBody.mjs b/engine262/src/runtime-semantics/ModuleBody.mjs new file mode 100644 index 0000000..2bf22ef --- /dev/null +++ b/engine262/src/runtime-semantics/ModuleBody.mjs @@ -0,0 +1,7 @@ +import { Evaluate_StatementList } from './all.mjs'; + +// #sec-module-semantics-runtime-semantics-evaluation +// ModuleBody : ModuleItemList +export function Evaluate_ModuleBody({ ModuleItemList }) { + return Evaluate_StatementList(ModuleItemList); +} diff --git a/engine262/src/runtime-semantics/MultiplicativeExpression.mjs b/engine262/src/runtime-semantics/MultiplicativeExpression.mjs new file mode 100644 index 0000000..2d818e6 --- /dev/null +++ b/engine262/src/runtime-semantics/MultiplicativeExpression.mjs @@ -0,0 +1,16 @@ +import { Q } from '../completion.mjs'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mjs'; + +// #sec-multiplicative-operators-runtime-semantics-evaluation +// MultiplicativeExpression : +// MultiplicativeExpression MultiplicativeOperator ExponentiationExpression +export function* Evaluate_MultiplicativeExpression({ + MultiplicativeExpression, + MultiplicativeOperator, + ExponentiationExpression, +}) { + // 1. Let opText be the source text matched by MultiplicativeOperator. + const opText = MultiplicativeOperator; + // 2. Return ? EvaluateStringOrNumericBinaryExpression(MultiplicativeExpression, opText, ExponentiationExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(MultiplicativeExpression, opText, ExponentiationExpression)); +} diff --git a/engine262/src/runtime-semantics/NamedEvaluation.mjs b/engine262/src/runtime-semantics/NamedEvaluation.mjs new file mode 100644 index 0000000..56a6df4 --- /dev/null +++ b/engine262/src/runtime-semantics/NamedEvaluation.mjs @@ -0,0 +1,178 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Descriptor } from '../value.mjs'; +import { + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + MakeConstructor, + DefinePropertyOrThrow, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { ReturnIfAbrupt, X } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { ClassDefinitionEvaluation } from './all.mjs'; + +// #sec-function-definitions-runtime-semantics-namedevaluation +// FunctionExpression : +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +function NamedEvaluation_FunctionExpression(FunctionExpression, name) { + const { FormalParameters, FunctionBody } = FunctionExpression; + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let sourceText be the source text matched by FunctionExpression. + const sourceText = sourceTextMatchedBy(FunctionExpression); + // 3. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, FormalParameters, FunctionBody, non-lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, FormalParameters, FunctionBody, 'non-lexical-this', scope); + // 4. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 5. Perform MakeConstructor(closure). + MakeConstructor(closure); + // 6. Return closure. + return closure; +} + + +// #sec-generator-function-definitions-runtime-semantics-namedevaluation +// GeneratorExpression : +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +function NamedEvaluation_GeneratorExpression(GeneratorExpression, name) { + const { FormalParameters, GeneratorBody } = GeneratorExpression; + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let sourceText be the source text matched by GeneratorExpression. + const sourceText = sourceTextMatchedBy(GeneratorExpression); + // 3. Let closure be OrdinaryFunctionCreate(%Generator%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Generator%'), sourceText, FormalParameters, GeneratorBody, 'non-lexical-this', scope); + // 4. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 5. Let prototype be OrdinaryObjectCreate(%Generator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Generator.prototype%')); + // 6. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + DefinePropertyOrThrow(closure, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + })); + // 7. Return closure. + return closure; +} + +// #sec-async-function-definitions-runtime-semantics-namedevaluation +// AsyncFunctionExpression : +// `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}` +function NamedEvaluation_AsyncFunctionExpression(AsyncFunctionExpression, name) { + const { FormalParameters, AsyncFunctionBody } = AsyncFunctionExpression; + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let sourceText be the source text matched by AsyncFunctionExpression. + const sourceText = sourceTextMatchedBy(AsyncFunctionExpression); + // 3. Let closure be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, FormalParameters, AsyncFunctionBody, non-lexical-this, scope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, FormalParameters, AsyncFunctionBody, 'non-lexical-this', scope)); + // 4. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 5. Return closure. + return closure; +} + +// #sec-asyncgenerator-definitions-namedevaluation +// AsyncGeneratorExpression : +// `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +function NamedEvaluation_AsyncGeneratorExpression(AsyncGeneratorExpression, name) { + const { FormalParameters, AsyncGeneratorBody } = AsyncGeneratorExpression; + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let sourceText be the source text matched by AsyncGeneratorExpression. + const sourceText = sourceTextMatchedBy(AsyncGeneratorExpression); + // 3. Let closure be OrdinaryFunctionCreate(%AsyncGeneratorFunction.prototype%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, FormalParameters, AsyncGeneratorBody, 'non-lexical-this', scope); + // 4. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 5. Let prototype be OrdinaryObjectCreate(%AsyncGenerator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGenerator.prototype%')); + // 6. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + DefinePropertyOrThrow(closure, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + })); + // 7. Return closure. + return closure; +} + +// #sec-arrow-function-definitions-runtime-semantics-namedevaluation +// ArrowFunction : +// ArrowParameters `=>` ConciseBody +function NamedEvaluation_ArrowFunction(ArrowFunction, name) { + const { ArrowParameters, ConciseBody } = ArrowFunction; + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let sourceText be the source text matched by ArrowFunction. + const sourceText = sourceTextMatchedBy(ArrowFunction); + // 3. Let parameters be CoveredFormalsList of ArrowParameters. + const parameters = ArrowParameters; + // 4. Let closure be OrdinaryFunctionCreate(%Function.prototype%, parameters, ConciseBody, lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, parameters, ConciseBody, 'lexical-this', scope); + // 5. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 6. Return closure. + return closure; +} + +// #sec-arrow-function-definitions-runtime-semantics-namedevaluation +// AsyncArrowFunction : +// ArrowParameters `=>` AsyncConciseBody +function NamedEvaluation_AsyncArrowFunction(AsyncArrowFunction, name) { + const { ArrowParameters, AsyncConciseBody } = AsyncArrowFunction; + // 1. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let sourceText be the source text matched by ArrowFunction. + const sourceText = sourceTextMatchedBy(AsyncArrowFunction); + // 3. Let head be CoveredAsyncArrowHead of CoverCallExpressionAndAsyncArrowHead. + // 4. Let parameters be the ArrowFormalParameters of head. + const parameters = ArrowParameters; + // 5. Let closure be OrdinaryFunctionCreate(%Function.prototype%, parameters, ConciseBody, lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, parameters, AsyncConciseBody, 'lexical-this', scope); + // 6. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 7. Return closure. + return closure; +} + +// #sec-class-definitions-runtime-semantics-namedevaluation +// ClassExpression : `class` ClassTail +function* NamedEvaluation_ClassExpression(ClassExpression, name) { + const { ClassTail } = ClassExpression; + // 1. Let value be the result of ClassDefinitionEvaluation of ClassTail with arguments undefined and name. + const value = yield* ClassDefinitionEvaluation(ClassTail, Value.undefined, name); + // 2. ReturnIfAbrupt(value). + ReturnIfAbrupt(value); + // 3. Set value.[[SourceText]] to the source text matched by ClassExpression. + value.SourceText = sourceTextMatchedBy(ClassExpression); + // 4. Return value. + return value; +} + +export function* NamedEvaluation(F, name) { + switch (F.type) { + case 'FunctionExpression': + return NamedEvaluation_FunctionExpression(F, name); + case 'GeneratorExpression': + return NamedEvaluation_GeneratorExpression(F, name); + case 'AsyncFunctionExpression': + return NamedEvaluation_AsyncFunctionExpression(F, name); + case 'AsyncGeneratorExpression': + return NamedEvaluation_AsyncGeneratorExpression(F, name); + case 'ArrowFunction': + return NamedEvaluation_ArrowFunction(F, name); + case 'AsyncArrowFunction': + return NamedEvaluation_AsyncArrowFunction(F, name); + case 'ClassExpression': + return yield* NamedEvaluation_ClassExpression(F, name); + case 'ParenthesizedExpression': + return yield* NamedEvaluation(F.Expression, name); + default: + throw new OutOfRange('NamedEvaluation', F); + } +} diff --git a/engine262/src/runtime-semantics/NewExpression.mjs b/engine262/src/runtime-semantics/NewExpression.mjs new file mode 100644 index 0000000..68c515e --- /dev/null +++ b/engine262/src/runtime-semantics/NewExpression.mjs @@ -0,0 +1,50 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + Construct, + GetValue, + IsConstructor, +} from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q } from '../completion.mjs'; +import { ArgumentListEvaluation } from './all.mjs'; + +// #sec-evaluatenew +function* EvaluateNew(constructExpr, args) { + // 1. Assert: constructExpr is either a NewExpression or a MemberExpression. + // 2. Assert: arguments is either empty or an Arguments. + Assert(args === undefined || Array.isArray(args)); + // 3. Let ref be the result of evaluating constructExpr. + const ref = yield* Evaluate(constructExpr); + // 4. Let constructor be ? GetValue(ref). + const constructor = Q(GetValue(ref)); + let argList; + // 5. If arguments is empty, let argList be a new empty List. + if (args === undefined) { + argList = []; + } else { // 6. Else, + // a. Let argList be ? ArgumentListEvaluation of arguments. + argList = Q(yield* ArgumentListEvaluation(args)); + } + // 7. If IsConstructor(constructor) is false, throw a TypeError exception. + if (IsConstructor(constructor) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', constructor); + } + // 8. Return ? Construct(constructor, argList). + return Q(Construct(constructor, argList)); +} + +// #sec-new-operator-runtime-semantics-evaluation +// NewExpression : +// `new` NewExpression +// `new` MemberExpression Arguments +export function* Evaluate_NewExpression({ MemberExpression, Arguments }) { + if (!Arguments) { + // 1. Return ? EvaluateNew(NewExpression, empty). + return Q(yield* EvaluateNew(MemberExpression, undefined)); + } else { + // 1. Return ? EvaluateNew(MemberExpression, Arguments). + return Q(yield* EvaluateNew(MemberExpression, Arguments)); + } +} diff --git a/engine262/src/runtime-semantics/NewTarget.mjs b/engine262/src/runtime-semantics/NewTarget.mjs new file mode 100644 index 0000000..aef6da6 --- /dev/null +++ b/engine262/src/runtime-semantics/NewTarget.mjs @@ -0,0 +1,8 @@ +import { GetNewTarget } from '../abstract-ops/all.mjs'; + +// #sec-meta-properties-runtime-semantics-evaluation +// NewTarget : `new` `.` `target` +export function Evaluate_NewTarget() { + // 1. Return GetNewTarget(). + return GetNewTarget(); +} diff --git a/engine262/src/runtime-semantics/NumberToBigInt.mjs b/engine262/src/runtime-semantics/NumberToBigInt.mjs new file mode 100644 index 0000000..d440d94 --- /dev/null +++ b/engine262/src/runtime-semantics/NumberToBigInt.mjs @@ -0,0 +1,15 @@ +import { surroundingAgent } from '../engine.mjs'; +import { IsInteger, Assert } from '../abstract-ops/all.mjs'; +import { Value, Type } from '../value.mjs'; + +// #sec-numbertobigint +export function NumberToBigInt(number) { + // 1. Assert: Type(number) is Number. + Assert(Type(number) === 'Number'); + // 2. If IsInteger(number) is false, throw a RangeError exception. + if (IsInteger(number) === Value.false) { + return surroundingAgent.Throw('RangeError', 'CannotConvertDecimalToBigInt', number); + } + // 3. Return the BigInt value that represents the mathematical value of number. + return new Value(BigInt(number.numberValue())); +} diff --git a/engine262/src/runtime-semantics/ObjectLiteral.mjs b/engine262/src/runtime-semantics/ObjectLiteral.mjs new file mode 100644 index 0000000..133eb02 --- /dev/null +++ b/engine262/src/runtime-semantics/ObjectLiteral.mjs @@ -0,0 +1,24 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { OrdinaryObjectCreate } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; +import { + PropertyDefinitionEvaluation_PropertyDefinitionList, +} from './all.mjs'; + +// #sec-object-initializer-runtime-semantics-evaluation +// ObjectLiteral : +// `{` `}` +// `{` PropertyDefinitionList `}` +// `{` PropertyDefinitionList `,` `}` +export function* Evaluate_ObjectLiteral({ PropertyDefinitionList }) { + // 1. Let obj be OrdinaryObjectCreate(%Object.prototype%). + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + if (PropertyDefinitionList.length === 0) { + return obj; + } + // 2. Perform ? PropertyDefinitionEvaluation of PropertyDefinitionList with arguments obj and true. + Q(yield* PropertyDefinitionEvaluation_PropertyDefinitionList(PropertyDefinitionList, obj, Value.true)); + // 3. Return obj. + return obj; +} diff --git a/engine262/src/runtime-semantics/OptionalExpression.mjs b/engine262/src/runtime-semantics/OptionalExpression.mjs new file mode 100644 index 0000000..9f862f2 --- /dev/null +++ b/engine262/src/runtime-semantics/OptionalExpression.mjs @@ -0,0 +1,106 @@ +import { Value } from '../value.mjs'; +import { GetValue } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q } from '../completion.mjs'; +import { IsInTailPosition } from '../static-semantics/all.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + EvaluateCall, + EvaluatePropertyAccessWithExpressionKey, + EvaluatePropertyAccessWithIdentifierKey, +} from './all.mjs'; + +// #sec-optional-chaining-evaluation +// OptionalExpression : +// MemberExpression OptionalChain +// CallExpression OptionalChain +// OptionalExpression OptionalChain +export function* Evaluate_OptionalExpression({ MemberExpression, OptionalChain }) { + // 1. Let baseReference be the result of evaluating MemberExpression. + const baseReference = yield* Evaluate(MemberExpression); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(GetValue(baseReference)); + // 3. If baseValue is undefined or null, then + if (baseValue === Value.undefined || baseValue === Value.null) { + // a. Return undefined. + return Value.undefined; + } + // 4. Return the result of performing ChainEvaluation of OptionalChain with arguments baseValue and baseReference. + return yield* ChainEvaluation(OptionalChain, baseValue, baseReference); +} + +// #sec-optional-chaining-chain-evaluation +// OptionalChain : +// `?.` Arguments +// `?.` `[` Expression `]` +// `?.` IdentifierName +// OptionalChain Arguments +// OptionalChain `[` Expression `]` +// OptionalChain `.` IdentifierName +function* ChainEvaluation(node, baseValue, baseReference) { + const { + OptionalChain, + Arguments, + Expression, + IdentifierName, + } = node; + if (Arguments) { + if (OptionalChain) { + // 1. Let optionalChain be OptionalChain. + const optionalChain = OptionalChain; + // 2. Let newReference be ? ChainEvaluation of optionalChain with arguments baseValue and baseReference. + const newReference = Q(yield* ChainEvaluation(optionalChain, baseValue, baseReference)); + // 3. Let newValue be ? GetValue(newReference). + const newValue = Q(GetValue(newReference)); + // 4. Let thisChain be this OptionalChain. + const thisChain = node; + // 5. Let tailCall be IsInTailPosition(thisChain). + const tailCall = IsInTailPosition(thisChain); + // 6. Return ? EvaluateCall(newValue, newReference, Arguments, tailCall). + return Q(yield* EvaluateCall(newValue, newReference, Arguments, tailCall)); + } + // 1. Let thisChain be this OptionalChain. + const thisChain = node; + // 2. Let tailCall be IsInTailPosition(thisChain). + const tailCall = IsInTailPosition(thisChain); + // 3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall). + return Q(yield* EvaluateCall(baseValue, baseReference, Arguments, tailCall)); + } + if (Expression) { + if (OptionalChain) { + // 1. Let optionalChain be OptionalChain. + const optionalChain = OptionalChain; + // 2. Let newReference be ? ChainEvaluation of optionalChain with arguments baseValue and baseReference. + const newReference = Q(yield* ChainEvaluation(optionalChain, baseValue, baseReference)); + // 3. Let newValue be ? GetValue(newReference). + const newValue = Q(GetValue(newReference)); + // 4. If the code matched by this OptionalChain is strict mode code, let strict be true; else let strict be false. + const strict = node.strict; + // 5. Return ? EvaluatePropertyAccessWithExpressionKey(newValue, Expression, strict). + return Q(yield* EvaluatePropertyAccessWithExpressionKey(newValue, Expression, strict)); + } + // 1. If the code matched by this OptionalChain is strict mode code, let strict be true; else let strict be false. + const strict = node.strict; + // 2. Return ? EvaluatePropertyAccessWithExpressionKey(baseValue, Expression, strict). + return Q(yield* EvaluatePropertyAccessWithExpressionKey(baseValue, Expression, strict)); + } + if (IdentifierName) { + if (OptionalChain) { + // 1. Let optionalChain be OptionalChain. + const optionalChain = OptionalChain; + // 2. Let newReference be ? ChainEvaluation of optionalChain with arguments baseValue and baseReference. + const newReference = Q(yield* ChainEvaluation(optionalChain, baseValue, baseReference)); + // 3. Let newValue be ? GetValue(newReference). + const newValue = Q(GetValue(newReference)); + // 4. If the code matched by this OptionalChain is strict mode code, let strict be true; else let strict be false. + const strict = node.strict; + // 5. Return ? EvaluatePropertyAccessWithIdentifierKey(newValue, IdentifierName, strict). + return Q(EvaluatePropertyAccessWithIdentifierKey(newValue, IdentifierName, strict)); + } + // 1. If the code matched by this OptionalChain is strict mode code, let strict be true; else let strict be false. + const strict = node.strict; + // 2. Return ? EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict). + return Q(EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict)); + } + throw new OutOfRange('ChainEvaluation', node); +} diff --git a/engine262/src/runtime-semantics/ParenthesizedExpression.mjs b/engine262/src/runtime-semantics/ParenthesizedExpression.mjs new file mode 100644 index 0000000..c42e0a5 --- /dev/null +++ b/engine262/src/runtime-semantics/ParenthesizedExpression.mjs @@ -0,0 +1,7 @@ +import { Evaluate } from '../evaluator.mjs'; + +// #sec-grouping-operator-runtime-semantics-evaluation +export function* Evaluate_ParenthesizedExpression({ Expression }) { + // 1. Return the result of evaluating Expression. This may be of type Reference. + return yield* Evaluate(Expression); +} diff --git a/engine262/src/runtime-semantics/PropertyBindingInitialization.mjs b/engine262/src/runtime-semantics/PropertyBindingInitialization.mjs new file mode 100644 index 0000000..ea9bbb8 --- /dev/null +++ b/engine262/src/runtime-semantics/PropertyBindingInitialization.mjs @@ -0,0 +1,40 @@ +import { BoundNames } from '../static-semantics/all.mjs'; +import { Q, ReturnIfAbrupt } from '../completion.mjs'; +import { Evaluate_PropertyName, KeyedBindingInitialization } from './all.mjs'; + +// #sec-destructuring-binding-patterns-runtime-semantics-propertybindinginitialization +// BindingPropertyList : BIndingPropertyList `,` BindingProperty +// BindingProperty : +// SingleNameBinding +// PropertyName `:` BindingElement +export function* PropertyBindingInitialization(node, value, environment) { + if (Array.isArray(node)) { + // 1. Let boundNames be ? PropertyBindingInitialization of BindingPropertyList with arguments value and environment. + // 2. Let nextNames be ? PropertyBindingInitialization of BindingProperty with arguments value and environment. + // 3. Append each item in nextNames to the end of boundNames. + // 4. Return boundNames. + const boundNames = []; + for (const item of node) { + const nextNames = Q(yield* PropertyBindingInitialization(item, value, environment)); + boundNames.push(...nextNames); + } + return boundNames; + } + if (node.PropertyName) { + // 1. Let P be the result of evaluating PropertyName. + const P = yield* Evaluate_PropertyName(node.PropertyName); + // 2. ReturnIfAbrupt(P). + ReturnIfAbrupt(P); + // 3. Perform ? KeyedBindingInitialization of BindingElement with value, environment, and P as the arguments. + Q(yield* KeyedBindingInitialization(node.BindingElement, value, environment, P)); + // 4. Return a new List containing P. + return [P]; + } else { + // 1. Let name be the string that is the only element of BoundNames of SingleNameBinding. + const name = BoundNames(node)[0]; + // 2. Perform ? KeyedBindingInitialization for SingleNameBinding using value, environment, and name as the arguments. + Q(yield* KeyedBindingInitialization(node, value, environment, name)); + // 3. Return a new List containing name. + return [name]; + } +} diff --git a/engine262/src/runtime-semantics/PropertyDefinitionEvaluation.mjs b/engine262/src/runtime-semantics/PropertyDefinitionEvaluation.mjs new file mode 100644 index 0000000..55aaa08 --- /dev/null +++ b/engine262/src/runtime-semantics/PropertyDefinitionEvaluation.mjs @@ -0,0 +1,307 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value, Descriptor } from '../value.mjs'; +import { + Assert, + GetValue, + OrdinaryObjectCreate, + OrdinaryFunctionCreate, + CreateDataPropertyOrThrow, + CopyDataProperties, + DefinePropertyOrThrow, + SetFunctionName, + MakeMethod, + sourceTextMatchedBy, +} from '../abstract-ops/all.mjs'; +import { StringValue, IsAnonymousFunctionDefinition } from '../static-semantics/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { ReturnIfAbrupt, Q, X } from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { NamedEvaluation, DefineMethod, Evaluate_PropertyName } from './all.mjs'; + +// #sec-object-initializer-runtime-semantics-propertydefinitionevaluation +// PropertyDefinitionList : +// PropertyDefinitionList `,` PropertyDefinition +export function* PropertyDefinitionEvaluation_PropertyDefinitionList(PropertyDefinitionList, object, enumerable) { + let lastReturn; + for (const PropertyDefinition of PropertyDefinitionList) { + lastReturn = Q(yield* PropertyDefinitionEvaluation_PropertyDefinition( + PropertyDefinition, object, enumerable, + )); + } + return lastReturn; +} + +// PropertyDefinition : +// `...` AssignmentExpression +// IdentifierReference +// PropertyName `:` AssignmentExpression +function* PropertyDefinitionEvaluation_PropertyDefinition(PropertyDefinition, object, enumerable) { + switch (PropertyDefinition.type) { + case 'IdentifierReference': + return yield* PropertyDefinitionEvaluation_PropertyDefinition_IdentifierReference(PropertyDefinition, object, enumerable); + case 'PropertyDefinition': + break; + case 'MethodDefinition': + return yield* PropertyDefinitionEvaluation_MethodDefinition(PropertyDefinition, object, enumerable); + case 'GeneratorMethod': + return yield* PropertyDefinitionEvaluation_GeneratorMethod(PropertyDefinition, object, enumerable); + case 'AsyncMethod': + return yield* PropertyDefinitionEvaluation_AsyncMethod(PropertyDefinition, object, enumerable); + case 'AsyncGeneratorMethod': + return yield* PropertyDefinitionEvaluation_AsyncGeneratorMethod(PropertyDefinition, object, enumerable); + default: + throw new OutOfRange('PropertyDefinitionEvaluation_PropertyDefinition', PropertyDefinition); + } + // PropertyDefinition : + // PropertyName `:` AssignmentExpression + // `...` AssignmentExpression + const { PropertyName, AssignmentExpression } = PropertyDefinition; + if (!PropertyName) { + // 1. Let exprValue be the result of evaluating AssignmentExpression. + const exprValue = yield* Evaluate(AssignmentExpression); + // 2. Let fromValue be ? GetValue(exprValue). + const fromValue = Q(GetValue(exprValue)); + // 3. Let excludedNames be a new empty List. + const excludedNames = []; + // 4. Return ? CopyDataProperties(object, fromValue, excludedNames). + return Q(CopyDataProperties(object, fromValue, excludedNames)); + } + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + let propValue; + // 3. If IsAnonymousFunctionDefinition(AssignmentExpression) is true, then + if (IsAnonymousFunctionDefinition(AssignmentExpression)) { + // a. Let propValue be NamedEvaluation of AssignmentExpression with argument propKey. + propValue = yield* NamedEvaluation(AssignmentExpression, propKey); + } else { // 4. Else, + // a. Let exprValueRef be the result of evaluating AssignmentExpression. + const exprValueRef = yield* Evaluate(AssignmentExpression); + // b. Let propValue be ? GetValue(exprValueRef). + propValue = Q(GetValue(exprValueRef)); + } + // 5. Assert: enumerable is true. + Assert(enumerable === Value.true); + // 6. Assert: object is an ordinary, extensible object with no non-configurable properties. + // 7. Return ! CreateDataPropertyOrThrow(object, propKey, propValue). + return X(CreateDataPropertyOrThrow(object, propKey, propValue)); +} + +// PropertyDefinition : IdentifierReference +function* PropertyDefinitionEvaluation_PropertyDefinition_IdentifierReference(IdentifierReference, object, enumerable) { + // 1. Let propName be StringValue of IdentifierReference. + const propName = StringValue(IdentifierReference); + // 2. Let exprValue be the result of evaluating IdentifierReference. + const exprValue = yield* Evaluate(IdentifierReference); + // 3. Let propValue be ? GetValue(exprValue). + const propValue = Q(GetValue(exprValue)); + // 4. Assert: enumerable is true. + Assert(enumerable === Value.true); + // 5. Assert: object is an ordinary, extensible object with no non-configurable properties. + // 6. Return ! CreateDataPropertyOrThrow(object, propName, propValue). + return X(CreateDataPropertyOrThrow(object, propName, propValue)); +} + +// MethodDefinition : +// PropertyName `(` UniqueFormalParameters `)` `{` FunctionBody `}` +// `get` PropertyName `(` `)` `{` FunctionBody `}` +// `set` PropertyName `(` PropertySetParameterList `)` `{` FunctionBody `}` +function* PropertyDefinitionEvaluation_MethodDefinition(MethodDefinition, object, enumerable) { + switch (true) { + case !!MethodDefinition.UniqueFormalParameters: { + // 1. Let methodDef be ? DefineMethod of MethodDefinition with argument object. + const methodDef = Q(yield* DefineMethod(MethodDefinition, object)); + // 2. Perform SetFunctionName(methodDef.[[Closure]], methodDef.[[Key]]). + SetFunctionName(methodDef.Closure, methodDef.Key); + // 3. Let desc be the PropertyDescriptor { [[Value]]: methodDef.[[Closure]], [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Value: methodDef.Closure, + Writable: Value.true, + Enumerable: enumerable, + Configurable: Value.true, + }); + // 4. Return ? DefinePropertyOrThrow(object, methodDef.[[Key]], desc). + return Q(DefinePropertyOrThrow(object, methodDef.Key, desc)); + } + case !!MethodDefinition.PropertySetParameterList: { + const { PropertyName, PropertySetParameterList, FunctionBody } = MethodDefinition; + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let sourceText be the source text matched by MethodDefinition. + const sourceText = sourceTextMatchedBy(MethodDefinition); + // 5. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, PropertySetParameterList, FunctionBody, non-lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, PropertySetParameterList, FunctionBody, 'non-lexical-this', scope); + // 6. Perform MakeMethod(closure, object). + MakeMethod(closure, object); + // 7. Perform SetFunctionName(closure, propKey, "get"). + SetFunctionName(closure, propKey, new Value('set')); + // 8. Let desc be the PropertyDescriptor { [[Get]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Set: closure, + Enumerable: enumerable, + Configurable: Value.true, + }); + // 9. Return ? DefinePropertyOrThrow(object, propKey, desc). + return Q(DefinePropertyOrThrow(object, propKey, desc)); + } + case !MethodDefinition.UniqueFormalParameters && !MethodDefinition.PropertySetParameterList: { + const { PropertyName, FunctionBody } = MethodDefinition; + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let formalParameterList be an instance of the production FormalParameters : [empty]. + const formalParameterList = []; + // 5. Let sourceText be the source text matched by MethodDefinition. + const sourceText = sourceTextMatchedBy(MethodDefinition); + // 6. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameterList, FunctionBody, non-lexical-this, scope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, formalParameterList, FunctionBody, 'non-lexical-this', scope); + // 7. Perform MakeMethod(closure, object). + MakeMethod(closure, object); + // 8. Perform SetFunctionName(closure, propKey, "get"). + SetFunctionName(closure, propKey, new Value('get')); + // 9. Let desc be the PropertyDescriptor { [[Get]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Get: closure, + Enumerable: enumerable, + Configurable: Value.true, + }); + // 10. Return ? DefinePropertyOrThrow(object, propKey, desc). + return Q(DefinePropertyOrThrow(object, propKey, desc)); + } + default: + throw new OutOfRange('PropertyDefinitionEvaluation_MethodDefinition', MethodDefinition); + } +} + +// #sec-async-function-definitions-PropertyDefinitionEvaluation +// AsyncMethod : +// `async` PropertyName `(` UniqueFormalParameters `)` `{` AsyncFunctionBody `}` +function* PropertyDefinitionEvaluation_AsyncMethod(AsyncMethod, object, enumerable) { + const { PropertyName, UniqueFormalParameters, AsyncFunctionBody } = AsyncMethod; + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let sourceText be the source text matched by AsyncMethod. + const sourceText = sourceTextMatchedBy(AsyncMethod); + // 5. Let closure be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, UniqueFormalParameters, AsyncFunctionBody, non-lexical-this, scope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, UniqueFormalParameters, AsyncFunctionBody, 'non-lexical-this', scope)); + // 6. Perform ! MakeMethod(closure, object). + X(MakeMethod(closure, object)); + // 7. Perform ! SetFunctionName(closure, propKey). + X(SetFunctionName(closure, propKey)); + // 8. Let desc be the PropertyDescriptor { [[Value]]: closure, [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Value: closure, + Writable: Value.true, + Enumerable: enumerable, + Configurable: Value.true, + }); + // 9. Return ? DefinePropertyOrThrow(object, propKey, desc). + return Q(DefinePropertyOrThrow(object, propKey, desc)); +} + +// #sec-generator-function-definitions-runtime-semantics-propertydefinitionevaluation +// GeneratorMethod : +// `*` PropertyName `(` UniqueFormalParameters `)` `{` GeneratorBody `}` +function* PropertyDefinitionEvaluation_GeneratorMethod(GeneratorMethod, object, enumerable) { + const { PropertyName, UniqueFormalParameters, GeneratorBody } = GeneratorMethod; + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let sourceText be the source text matched by GeneratorMethod. + const sourceText = sourceTextMatchedBy(GeneratorMethod); + // 5. Let closure be ! OrdinaryFunctionCreate(%Generator%, sourceText, UniqueFormalParameters, AsyncFunctionBody, non-lexical-this, scope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Generator%'), sourceText, UniqueFormalParameters, GeneratorBody, 'non-lexical-this', scope)); + // 6. Perform ! MakeMethod(closure, object). + X(MakeMethod(closure, object)); + // 7. Perform ! SetFunctionName(closure, propKey). + X(SetFunctionName(closure, propKey)); + // 8. Let prototype be OrdinaryObjectCreate(%Generator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Generator.prototype%')); + // 9. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + DefinePropertyOrThrow(closure, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + })); + // 10. Let desc be the PropertyDescriptor { [[Value]]: closure, [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Value: closure, + Writable: Value.true, + Enumerable: enumerable, + Configurable: Value.true, + }); + // 11. Return ? DefinePropertyOrThrow(object, propKey, desc). + return Q(DefinePropertyOrThrow(object, propKey, desc)); +} + +// #sec-asyncgenerator-definitions-propertydefinitionevaluation +// AsyncGeneratorMethod : +// `async` `*` PropertyName `(` UniqueFormalParameters `)` `{` AsyncGeneratorBody `}` +function* PropertyDefinitionEvaluation_AsyncGeneratorMethod(AsyncGeneratorMethod, object, enumerable) { + const { PropertyName, UniqueFormalParameters, AsyncGeneratorBody } = AsyncGeneratorMethod; + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = yield* Evaluate_PropertyName(PropertyName); + // 2. ReturnIfAbrupt(propKey). + ReturnIfAbrupt(propKey); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let sourceText be the source text matched by AsyncGeneratorMethod. + const sourceText = sourceTextMatchedBy(AsyncGeneratorMethod); + // 5. Let closure be ! OrdinaryFunctionCreate(%AsyncGenerator%, sourceText, UniqueFormalParameters, AsyncGeneratorBody, non-lexical-this, scope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, UniqueFormalParameters, AsyncGeneratorBody, 'non-lexical-this', scope)); + // 6. Perform ! MakeMethod(closure, object). + X(MakeMethod(closure, object)); + // 7. Perform ! SetFunctionName(closure, propKey). + X(SetFunctionName(closure, propKey)); + // 8. Let prototype be OrdinaryObjectCreate(%AsyncGenerator.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGenerator.prototype%')); + // 9. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + DefinePropertyOrThrow(closure, new Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + })); + // 10. Let desc be the PropertyDescriptor { [[Value]]: closure, [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Value: closure, + Writable: Value.true, + Enumerable: enumerable, + Configurable: Value.true, + }); + // 11. Return ? DefinePropertyOrThrow(object, propKey, desc). + return Q(DefinePropertyOrThrow(object, propKey, desc)); +} + +export function PropertyDefinitionEvaluation(node, object, enumerable) { + switch (node.type) { + case 'MethodDefinition': + return PropertyDefinitionEvaluation_MethodDefinition(node, object, enumerable); + case 'AsyncMethod': + return PropertyDefinitionEvaluation_AsyncMethod(node, object, enumerable); + case 'GeneratorMethod': + return PropertyDefinitionEvaluation_GeneratorMethod(node, object, enumerable); + case 'AsyncGeneratorMethod': + return PropertyDefinitionEvaluation_AsyncGeneratorMethod(node, object, enumerable); + case 'ClassElement': + return PropertyDefinitionEvaluation(node.MethodDefinition, object, enumerable); + default: + throw new OutOfRange('PropertyDefinitionEvaluation', node); + } +} diff --git a/engine262/src/runtime-semantics/PropertyName.mjs b/engine262/src/runtime-semantics/PropertyName.mjs new file mode 100644 index 0000000..f3918bf --- /dev/null +++ b/engine262/src/runtime-semantics/PropertyName.mjs @@ -0,0 +1,38 @@ +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { StringValue, NumericValue } from '../static-semantics/all.mjs'; +import { ToString, GetValue, ToPropertyKey } from '../abstract-ops/all.mjs'; +import { Q, X } from '../completion.mjs'; + +// #sec-object-initializer-runtime-semantics-evaluation +// PropertyName : +// LiteralPropertyName +// ComputedPropertyName +// LiteralPropertyName : +// IdentifierName +// StringLiteral +// NumericLiteral +// ComputedPropertyName : +// `[` AssignmentExpression `]` +export function* Evaluate_PropertyName(PropertyName) { + switch (PropertyName.type) { + case 'IdentifierName': + return StringValue(PropertyName); + case 'StringLiteral': + return new Value(PropertyName.value); + case 'NumericLiteral': { + // 1. Let nbr be the NumericValue of NumericLiteral. + const nbr = NumericValue(PropertyName); + // 2. Return ! ToString(nbr). + return X(ToString(nbr)); + } + default: { + // 1. Let exprValue be the result of evaluating AssignmentExpression. + const exprValue = yield* Evaluate(PropertyName.ComputedPropertyName); + // 2. Let propName be ? GetValue(exprValue). + const propName = Q(GetValue(exprValue)); + // 3. Return ? ToPropertyKey(propName). + return Q(ToPropertyKey(propName)); + } + } +} diff --git a/engine262/src/runtime-semantics/RegExp.mjs b/engine262/src/runtime-semantics/RegExp.mjs new file mode 100644 index 0000000..40ee181 --- /dev/null +++ b/engine262/src/runtime-semantics/RegExp.mjs @@ -0,0 +1,1164 @@ +import unicodeCaseFoldingCommon from 'unicode-13.0.0/Case_Folding/C/symbols.js'; +import unicodeCaseFoldingSimple from 'unicode-13.0.0/Case_Folding/S/symbols.js'; +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { Assert, IsNonNegativeInteger } from '../abstract-ops/all.mjs'; +import { CharacterValue, StringToCodePoints } from '../static-semantics/all.mjs'; +import { X } from '../completion.mjs'; +import { isLineTerminator, isWhitespace, isDecimalDigit } from '../parser/Lexer.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + UnicodeMatchProperty, + UnicodeMatchPropertyValue, + UnicodeGeneralCategoryValues, + BinaryUnicodeProperties, + NonbinaryUnicodeProperties, + getUnicodePropertyValueSet, +} from './all.mjs'; + +// #sec-pattern +class State { + constructor(endIndex, captures) { + this.endIndex = endIndex; + this.captures = captures; + } +} + +export { State as RegExpState }; + +function isContinuation(v) { + return typeof v === 'function' && v.length === 1; +} + +class CharSet { + union(other) { + const concrete = new Set(); + const fns = new Set(); + const add = (cs) => { + if (cs.fns) { + cs.fns.forEach((fn) => { + fns.add(fn); + }); + cs.concrete.forEach((c) => { + concrete.add(c); + }); + } else if (cs.fn) { + fns.add(cs.fn); + } else { + cs.concrete.forEach((c) => { + concrete.add(c); + }); + } + }; + add(this); + add(other); + return new UnionCharSet(concrete, fns); + } +} + +class UnionCharSet extends CharSet { + constructor(concrete, fns) { + super(); + + this.concrete = concrete; + this.fns = fns; + } + + has(c) { + if (this.concrete.has(c)) { + return true; + } + for (const fn of this.fns) { + if (fn(c)) { + return true; + } + } + return false; + } +} + +class ConcreteCharSet extends CharSet { + constructor(items) { + super(); + this.concrete = items instanceof Set ? items : new Set(items); + } + + has(c) { + return this.concrete.has(c); + } + + get size() { + return this.concrete.size; + } + + first() { + Assert(this.concrete.size >= 1); + return this.concrete.values().next().value; + } +} + +class VirtualCharSet extends CharSet { + constructor(fn) { + super(); + this.fn = fn; + } + + has(c) { + return this.fn(c); + } +} + +class Range { + constructor(startIndex, endIndex) { + Assert(startIndex <= endIndex); + this.startIndex = startIndex; + this.endIndex = endIndex; + } +} + +// #sec-pattern +// Pattern :: Disjunction +export function Evaluate_Pattern(Pattern, flags) { + // The descriptions below use the following variables: + // * Input is a List consisting of all of the characters, in order, of the String being matched + // by the regular expression pattern. Each character is either a code unit or a code point, + // depending upon the kind of pattern involved. The notation Input[n] means the nth character + // of Input, where n can range between 0 (inclusive) and InputLength (exclusive). + // * InputLength is the number of characters in Input. + // * NcapturingParens is the total number of left-capturing parentheses (i.e. the total number of + // Atom :: `(` GroupSpecifier Disjunction `)` Parse Nodes) in the pattern. A left-capturing parenthesis + // is any `(` pattern character that is matched by the `(` terminal of the Atom :: `(` GroupSpecifier Disjunction `)` + // production. + // * DotAll is true if the RegExp object's [[OriginalFlags]] internal slot contains "s" and otherwise is false. + // * IgnoreCase is true if the RegExp object's [[OriginalFlags]] internal slot contains "i" and otherwise is false. + // * Multiline is true if the RegExp object's [[OriginalFlags]] internal slot contains "m" and otherwise is false. + // * Unicode is true if the RegExp object's [[OriginalFlags]] internal slot contains "u" and otherwise is false. + let Input; + let InputLength; + const NcapturingParens = Pattern.capturingGroups.length; + const DotAll = flags.includes('s'); + const IgnoreCase = flags.includes('i'); + const Multiline = flags.includes('m'); + const Unicode = flags.includes('u'); + + { + // 1. Evaluate Disjunction with +1 as its direction argument to obtain a Matcher m. + const m = Evaluate(Pattern.Disjunction, +1); + // 2. Return a new abstract closure with parameters (str, index) that captures m and performs the following steps when called: + return (str, index) => { + // a. Assert: Type(str) is String. + Assert(Type(str) === 'String'); + // b. Assert: ! IsNonNegativeInteger(index) is true and index ≤ the length of str. + Assert(X(IsNonNegativeInteger(index)) === Value.true + && index.numberValue() <= str.stringValue().length); + // c. If Unicode is true, let Input be a List consisting of the sequence of code points of ! StringToCodePoints(str). + // Otherwise, let Input be a List consisting of the sequence of code units that are the elements of str. + // Input will be used throughout the algorithms in 21.2.2. Each element of Input is considered to be a character. + if (Unicode) { + Input = X(StringToCodePoints(str.stringValue())); + } else { + Input = str.stringValue().split('').map((c) => c.charCodeAt(0)); + } + // d. Let InputLength be the number of characters contained in Input. This variable will be used throughout the algorithms in 21.2.2. + InputLength = Input.length; + // e. Let listIndex be the index into Input of the character that was obtained from element index of str. + const listIndex = index.numberValue(); + // f. Let c be a new Continuation with parameters (y) that captures nothing and performs the following steps when called: + const c = (y) => { + // i. Assert: y is a State. + Assert(y instanceof State); + // ii. Return y. + return y; + }; + // g. Let cap be a List of NcapturingParens undefined values, indexed 1 through NcapturingParens. + const cap = Array.from({ length: NcapturingParens + 1 }, () => Value.undefined); + // h. Let x be the State (listIndex, cap). + const x = new State(listIndex, cap); + // i. Call m(x, c) and return its result. + return m(x, c); + }; + } + + function Evaluate(node, ...args) { + switch (node.type) { + case 'Disjunction': + return Evaluate_Disjunction(node, ...args); + case 'Alternative': + return Evaluate_Alternative(node, ...args); + case 'Term': + return Evaluate_Term(node, ...args); + case 'Assertion': + return Evaluate_Assertion(node, ...args); + case 'Quantifier': + return Evaluate_Quantifier(node, ...args); + case 'Atom': + return Evaluate_Atom(node, ...args); + case 'AtomEscape': + return Evaluate_AtomEscape(node, ...args); + case 'CharacterEscape': + return Evaluate_CharacterEscape(node, ...args); + case 'DecimalEscape': + return Evaluate_DecimalEscape(node, ...args); + case 'CharacterClassEscape': + return Evaluate_CharacterClassEscape(node, ...args); + case 'UnicodePropertyValueExpression': + return Evaluate_UnicodePropertyValueExpression(node, ...args); + case 'CharacterClass': + return Evaluate_CharacterClass(node, ...args); + case 'ClassAtom': + return Evaluate_ClassAtom(node, ...args); + case 'ClassEscape': + return Evaluate_ClassEscape(node, ...args); + default: + throw new OutOfRange('Evaluate', node); + } + } + + // #sec-disjunction + // Disjunction :: + // Alternative + // Alternative `|` Disjunction + function Evaluate_Disjunction({ Alternative, Disjunction }, direction) { + if (!Disjunction) { + // 1. Evaluate Alternative with argument direction to obtain a Matcher m. + const m = Evaluate(Alternative, direction); + // 2. Return m. + return m; + } + // 1. Evaluate Alternative with argument direction to obtain a Matcher m1. + const m1 = Evaluate(Alternative, direction); + // 2. Evaluate Disjunction with argument direction to obtain a Matcher m2. + const m2 = Evaluate(Disjunction, direction); + // 3. Return a new Matcher with parameters (x, c) that captures m1 and m2 and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Call m1(x, c) and let r be its result. + const r = m1(x, c); + // d. If r is not failure, return r. + if (r !== 'failure') { + return r; + } + // e. Call m2(x, c) and return its result. + return m2(x, c); + }; + } + + // #sec-alternative + // Alternative :: + // [empty] + // Alternative Term + function Evaluate_Alternative({ Alternative, Term }, direction) { + if (!Alternative && !Term) { + // 1. Return a new Matcher with parameters (x, c) that captures nothing and performs the following steps when called: + return (x, c) => { + // 1. Assert: x is a State. + Assert(x instanceof State); + // 2. Assert: c is a Continuation. + Assert(isContinuation(c)); + // 3. Call c(x) and return its result. + return c(x); + }; + } + // 1. Evaluate Alternative with argument direction to obtain a Matcher m1. + const m1 = Evaluate(Alternative, direction); + // 2. Evaluate Term with argument direction to obtain a Matcher m2. + const m2 = Evaluate(Term, direction); + // 3. If direction is equal to +1, then + if (direction === +1) { + // a. Return a new Matcher with parameters (x, c) that captures m1 and m2 and performs the following steps when called: + return (x, c) => { + // i. Assert: x is a State. + Assert(x instanceof State); + // ii. Assert: c is a Continuation. + Assert(isContinuation(c)); + // iii. Let d be a new Continuation with parameters (y) that captures c and m2 and performs the following steps when called: + const d = (y) => { + // 1. Assert: y is a State. + Assert(y instanceof State); + // 2. Call m2(y, c) and return its result. + return m2(y, c); + }; + // iv. Call m1(x, d) and return its result. + return m1(x, d); + }; + } else { // 4. Else, + // a. Assert: direction is equal to -1. + Assert(direction === -1); + // b. Return a new Matcher with parameters (x, c) that captures m1 and m2 and performs the following steps when called: + return (x, c) => { + // i. Assert: x is a State. + Assert(x instanceof State); + // ii. Assert: c is a Continuation. + Assert(isContinuation(c)); + // iii. Let d be a new Continuation with parameters (y) that captures c and m1 and performs the following steps when called: + const d = (y) => { + // 1. Assert: y is a State. + Assert(y instanceof State); + // 2. Call m1(y, c) and return its result. + return m1(y, c); + }; + // iv. Call m2(x, d) and return its result. + return m2(x, d); + }; + } + } + + // #sec-term + // Term :: + // Assertion + // Atom + // Atom Quantifier + function Evaluate_Term(Term, direction) { + const { Atom, Quantifier } = Term; + if (!Quantifier) { + // 1. Return the Matcher that is the result of evaluating Atom with argument direction. + return Evaluate(Atom, direction); + } + // 1. Evaluate Atom with argument direction to obtain a Matcher m. + const m = Evaluate(Atom, direction); + // 2. Evaluate Quantifier to obtain the three results: an integer min, an integer (or ∞) max, and Boolean greedy. + const [min, max, greedy] = Evaluate(Quantifier); + // 3. Assert: If max is finite, then max is not less than min. + Assert(!Number.isFinite(max) || (max >= min)); + // 4. Let parenIndex be the number of left-capturing parentheses in the entire regular expression that occur to the + // left of this Term. This is the total number of Atom :: `(` GroupSpecifier Disjunction `)` Parse Nodes prior to + // or enclosing this Term. + const parenIndex = Term.capturingParenthesesBefore; + // 5. Let parenCount be the number of left-capturing parentheses in Atom. This is the total number of + // Atom :: `(` GroupSpecifier Disjunction `)` Parse Nodes enclosed by Atom. + const parenCount = Atom.enclosedCapturingParentheses; + // 6. Return a new Matcher with parameters (x, c) that captures m, min, max, greedy, parenIndex, and parenCount and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Call RepeatMatcher(m, min, max, greedy, x, c, parenIndex, parenCount) and return its result. + return RepeatMatcher(m, min, max, greedy, x, c, parenIndex, parenCount); + }; + } + + // #sec-runtime-semantics-repeatmatcher-abstract-operation + function RepeatMatcher(m, min, max, greedy, x, c, parenIndex, parenCount) { + // 1. If max is zero, return c(x). + if (max === 0) { + return c(x); + } + // 2. Let d be a new Continuation with parameters (y) that captures m, min, max, greedy, x, c, parenIndex, and parenCount and performs the following steps when called: + const d = (y) => { + // a. Assert: y is a State. + Assert(y instanceof State); + // b. If min is zero and y's endIndex is equal to x's endIndex, return failure. + if (min === 0 && y.endIndex === x.endIndex) { + return 'failure'; + } + // c. If min is zero, let min2 be zero; otherwise let min2 be min - 1. + let min2; + if (min === 0) { + min2 = 0; + } else { + min2 = min - 1; + } + // d. If max is ∞, let max2 be ∞; otherwise let max2 be max - 1. + let max2; + if (max === Infinity) { + max2 = Infinity; + } else { + max2 = max - 1; + } + // e. Call RepeatMatcher(m, min2, max2, greedy, y, c, parenIndex, parenCount) and return its result. + return RepeatMatcher(m, min2, max2, greedy, y, c, parenIndex, parenCount); + }; + // 3. Let cap be a copy of x's captures List. + const cap = [...x.captures]; + // 4. For each integer k that satisfies parenIndex < k and k ≤ parenIndex + parenCount, set cap[k] to undefined. + for (let k = parenIndex + 1; k <= parenIndex + parenCount; k += 1) { + cap[k] = Value.undefined; + } + // 5. Let e be x's endIndex. + const e = x.endIndex; + // 6. Let xr be the State (e, cap). + const xr = new State(e, cap); + // 7. If min is not zero, return m(xr, d). + if (min !== 0) { + return m(xr, d); + } + // 8. If greedy is false, then + if (greedy === false) { + // a. Call c(x) and let z be its result. + const z = c(x); + // b. If z is not failure, return z. + if (z !== 'failure') { + return z; + } + // c. Call m(xr, d) and return its result. + return m(xr, d); + } + // 9. Call m(xr, d) and let z be its result. + const z = m(xr, d); + // 10. If z is not failure, return z. + if (z !== 'failure') { + return z; + } + // 11. Call c(x) and return its result. + return c(x); + } + + // #sec-assertion + // Assertion :: + // `^` + // `$` + // `\` `b` + // `\` `B` + // `(` `?` `=` Disjunction `)` + // `(` `?` `!` Disjunction `)` + // `(` `?` `<=` Disjunction `)` + // `(` `?` ` { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let e be x's endIndex. + const e = x.endIndex; + // d. If e is zero, or if Multiline is true and the character Input[e - 1] is one of LineTerminator, then + if (e === 0 || (Multiline && isLineTerminator(String.fromCodePoint(Input[e - 1])))) { + // i. Call c(x) and return its result. + return c(x); + } + // e. Return failure. + return 'failure'; + }; + case '$': + // 1. Return a new Matcher with parameters (x, c) that captures nothing and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let e be x's endIndex. + const e = x.endIndex; + // d. If e is equal to InputLength, or if Multiline is true and the character Input[e] is one of LineTerminator, then + if (e === InputLength || (Multiline && isLineTerminator(String.fromCodePoint(Input[e])))) { + // i. Call c(x) and return its result. + return c(x); + } + // e. Return failure. + return 'failure'; + }; + case 'b': + // 1. Return a new Matcher with parameters (x, c) that captures nothing and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let e be x's endIndex. + const e = x.endIndex; + // d. Call IsWordChar(e - 1) and let a be the Boolean result. + const a = IsWordChar(e - 1); + // e. Call IsWordChar(e) and let b be the Boolean result. + const b = IsWordChar(e); + // f. If a is true and b is false, or if a is false and b is true, then + if ((a && !b) || (!a && b)) { + // i. Call c(x) and return its result. + return c(x); + } + // g. Return failure. + return 'failure'; + }; + case 'B': + // 1. Return a new Matcher with parameters (x, c) that captures nothing and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let e be x's endIndex. + const e = x.endIndex; + // d. Call IsWordChar(e - 1) and let a be the Boolean result. + const a = IsWordChar(e - 1); + // e. Call IsWordChar(e) and let b be the Boolean result. + const b = IsWordChar(e); + // f. If a is true and b is true, or if a is false and b is false, then + if ((a && b) || (!a && !b)) { + // i. Call c(x) and return its result. + return c(x); + } + // g. Return failure. + return 'failure'; + }; + case '?=': { + // 1. Evaluate Disjunction with +1 as its direction argument to obtain a Matcher m. + const m = Evaluate(Disjunction, +1); + // 2. Return a new Matcher with parameters (x, c) that captures m and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let d be a new Continuation with parameters (y) that captures nothing and performs the following steps when called: + const d = (y) => { + // i. Assert: y is a State. + Assert(y instanceof State); + // ii. Return y. + return y; + }; + // d. Call m(x, d) and let r be its result. + const r = m(x, d); + // e. If r is failure, return failure. + if (r === 'failure') { + return 'failure'; + } + // f. Let y be r's State. + const y = r; + // g. Let cap be y's captures List. + const cap = y.captures; + // h. Let xe be x's endIndex. + const xe = x.endIndex; + // i. Let z be the State (xe, cap). + const z = new State(xe, cap); + // j. Call c(z) and return its result. + return c(z); + }; + } + case '?!': { + // 1. Evaluate Disjunction with +1 as its direction argument to obtain a Matcher m. + const m = Evaluate(Disjunction, +1); + // 2. Return a new Matcher with parameters (x, c) that captures m and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let d be a new Continuation with parameters (y) that captures nothing and performs the following steps when called: + const d = (y) => { + // i. Assert: y is a State. + Assert(y instanceof State); + // ii. Return y. + return y; + }; + // d. Call m(x, d) and let r be its result. + const r = m(x, d); + // e. If r is not failure, return failure. + if (r !== 'failure') { + return 'failure'; + } + // f. Call c(x) and return its result. + return c(x); + }; + } + case '?<=': { + // 1. Evaluate Disjunction with -1 as its direction argument to obtain a Matcher m. + const m = Evaluate(Disjunction, -1); + // 2. Return a new Matcher with parameters (x, c) that captures m and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let d be a new Continuation with parameters (y) that captures nothing and performs the following steps when called: + const d = (y) => { + // i. Assert: y is a State. + Assert(y instanceof State); + // ii. Return y. + return y; + }; + // d. Call m(x, d) and let r be its result. + const r = m(x, d); + // e. If r is failure, return failure. + if (r === 'failure') { + return 'failure'; + } + // f. Let y be r's State. + const y = r; + // g. Let cap be y's captures List. + const cap = y.captures; + // h. Let xe be x's endIndex. + const xe = x.endIndex; + // i. Let z be the State (xe, cap). + const z = new State(xe, cap); + // j. Call c(z) and return its result. + return c(z); + }; + } + case '? { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let d be a new Continuation with parameters (y) that captures nothing and performs the following steps when called: + const d = (y) => { + // i. Assert: y is a State. + Assert(y instanceof State); + // ii. Return y. + return y; + }; + // d. Call m(x, d) and let r be its result. + const r = m(x, d); + // e. If r is not failure, return failure. + if (r !== 'failure') { + return 'failure'; + } + // f. Call c(x) and return its result. + return c(x); + }; + } + default: + throw new OutOfRange('Evaluate_Assertion', subtype); + } + } + + // #sec-runtime-semantics-wordcharacters-abstract-operation + function WordCharacters() { + // 1. Let A be a set of characters containing the sixty-three characters: + // a b c d e f g h i j k l m n o p q r s t u v w x y z + // A B C D E F G H I J K L M N O P Q R S T U V W X Y Z + // 0 1 2 3 4 5 6 7 8 9 _ + // 2. Let U be an empty set. + // 3. For each character c not in set A where Canonicalize(c) is in A, add c to U. + // 4. Assert: Unless Unicode and IgnoreCase are both true, U is empty. + // 5. Add the characters in set U to set A. + // Return A. + const A = new ConcreteCharSet([ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', + ].map((c) => c.codePointAt(0))); + if (Unicode && IgnoreCase) { + return new VirtualCharSet((c) => { + if (A.has(c)) { + return true; + } + if (A.has(Canonicalize(c))) { + return true; + } + return false; + }); + } + return A; + } + + // #sec-runtime-semantics-iswordchar-abstract-operation + function IsWordChar(e) { + // 1. If e is -1 or e is InputLength, return false. + if (e === -1 || e === InputLength) { + return false; + } + // 2. Let c be the character Input[e]. + const c = Input[e]; + // 3. Let wordChars be the result of ! WordCharacters(). + const wordChars = X(WordCharacters()); + // 4. If c is in wordChars, return true. + if (wordChars.has(c)) { + return true; + } + // 5. Return false. + return false; + } + + // #sec-quantifier + // Quantifier :: + // QuantifierPrefix + // QuantifierPrefix `?` + function Evaluate_Quantifier({ QuantifierPrefix, greedy }) { + switch (QuantifierPrefix) { + case '*': + return [0, Infinity, greedy]; + case '+': + return [1, Infinity, greedy]; + case '?': + return [0, 1, greedy]; + default: + break; + } + const { DecimalDigits_a, DecimalDigits_b } = QuantifierPrefix; + return [DecimalDigits_a, DecimalDigits_b || DecimalDigits_a, greedy]; + } + + // #sec-atom + // Atom :: + // PatternCharacter + // `.` + // `\` AtomEscape + // CharacterClass + // `(` GroupSpecifier Disjunction `)` + // `(` `?` `:` Disjunction `)` + function Evaluate_Atom(Atom, direction) { + switch (true) { + case !!Atom.PatternCharacter: { + // 1. Let ch be the character matched by PatternCharacter. + const ch = Atom.PatternCharacter.codePointAt(0); + // 2. Let A be a one-element CharSet containing the character ch. + const A = new ConcreteCharSet([Canonicalize(ch)]); + // 3. Call CharacterSetMatcher(A, false, direction) and return its Matcher result. + return CharacterSetMatcher(A, false, direction); + } + case Atom.subtype === '.': { + let A; + // 1. If DotAll is true, then + if (DotAll) { + // a. Let A be the set of all characters. + A = new VirtualCharSet((_c) => true); + } else { + // 2. Otherwise, let A be the set of all characters except LineTerminator. + A = new VirtualCharSet((c) => !isLineTerminator(String.fromCodePoint(c))); + } + // 3. Call CharacterSetMatcher(A, false, direction) and return its Matcher result. + return CharacterSetMatcher(A, false, direction); + } + case !!Atom.CharacterClass: { + // 1. Evaluate CharacterClass to obtain a CharSet A and a Boolean invert. + const { A, invert } = Evaluate(Atom.CharacterClass); + // 2. Call CharacterSetMatcher(A, invert, direction) and return its Matcher result. + return CharacterSetMatcher(A, invert, direction); + } + case Atom.capturing: { + // 1. Evaluate Disjunction with argument direction to obtain a Matcher m. + const m = Evaluate(Atom.Disjunction, direction); + // 2. Let parenIndex be the number of left-capturing parentheses in the entire regular expression + // that occur to the left of this Atom. This is the total number of Atom :: `(` GroupSpecifier Disjunction `)` + // Parse Nodes prior to or enclosing this Atom. + const parenIndex = Atom.capturingParenthesesBefore; + // 3. Return a new Matcher with parameters (x, c) that captures direction, m, and parenIndex and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let d be a new Continuation with parameters (y) that captures x, c, direction, and parenIndex and performs the following steps when called: + const d = (y) => { + // i. Assert: y is a State. + Assert(y instanceof State); + // ii. Let cap be a copy of y's captures List. + const cap = [...y.captures]; + // iii. Let xe be x's endIndex. + const xe = x.endIndex; + // iv. Let ye be y's endIndex. + const ye = y.endIndex; + let s; + // v. If direction is equal to +1, then + if (direction === +1) { + // 1. Assert: xe ≤ ye. + Assert(xe <= ye); + if (surroundingAgent.feature('regexp-match-indices')) { + // 2. Let r be the Range (xe, ye). + s = new Range(xe, ye); + } else { + // 2. Let s be a new List whose elements are the characters of Input at indices xe (inclusive) through ye (exclusive). + s = Input.slice(xe, ye); + } + } else { // vi. Else, + // 1. Assert: direction is equal to -1. + Assert(direction === -1); + // 2. Assert: ye ≤ xe. + Assert(ye <= xe); + if (surroundingAgent.feature('regexp-match-indices')) { + // 3. Let r be the Range (ye, xe). + s = new Range(ye, xe); + } else { + // 3. Let s be a new List whose elements are the characters of Input at indices ye (inclusive) through xe (exclusive). + s = Input.slice(ye, xe); + } + } + // vii. Set cap[parenIndex + 1] to s. + cap[parenIndex + 1] = s; + // viii. Let z be the State (ye, cap). + const z = new State(ye, cap); + // ix. Call c(z) and return its result. + return c(z); + }; + // d. Call m(x, d) and return its result. + return m(x, d); + }; + } + case !!Atom.Disjunction: + return Evaluate(Atom.Disjunction, direction); + default: + throw new OutOfRange('Evaluate_Atom', Atom); + } + } + + // #sec-runtime-semantics-charactersetmatcher-abstract-operation + function CharacterSetMatcher(A, invert, direction) { + // 1. Return a new Matcher with parameters (x, c) that captures A, invert, and direction and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let e be x's endIndex. + const e = x.endIndex; + // d. Let f be e + direction. + const f = e + direction; + // e. If f < 0 or f > InputLength, return failure. + if (f < 0 || f > InputLength) { + return 'failure'; + } + // f. Let index be min(e, f). + const index = Math.min(e, f); + // g. Let ch be the character Input[index]. + const ch = Input[index]; + // h. Let cc be Canonicalize(ch). + const cc = Canonicalize(ch); + // i. If invert is false, then + if (invert === false) { + // i. If there does not exist a member a of set A such that Canonicalize(a) is cc, return failure. + if (!A.has(cc)) { + return 'failure'; + } + } else { // j. Else + // i. Assert: invert is true. + Assert(invert === true); + // ii. If there exists a member a of set A such that Canonicalize(a) is cc, return failure. + if (A.has(cc)) { + return 'failure'; + } + } + // k. Let cap be x's captures List. + const cap = x.captures; + // Let y be the State (f, cap). + const y = new State(f, cap); + // Call c(y) and return its result. + return c(y); + }; + } + + // #sec-runtime-semantics-canonicalize-ch + function Canonicalize(ch) { + // 1. If IgnoreCase is false, return ch. + if (IgnoreCase === false) { + return ch; + } + // 2. If Unicode is true, then + if (Unicode === true) { + const s = String.fromCodePoint(ch); + // a. If the file CaseFolding.txt of the Unicode Character Database provides a simple or common case folding mapping for ch, return the result of applying that mapping to ch. + if (unicodeCaseFoldingSimple.has(s)) { + return unicodeCaseFoldingSimple.get(s).codePointAt(0); + } + if (unicodeCaseFoldingCommon.has(s)) { + return unicodeCaseFoldingCommon.get(s).codePointAt(0); + } + // b. Return ch. + return ch; + } else { // 3. Else + // a. Assert: ch is a UTF-16 code unit. + // b. Let s be the String value consisting of the single code unit ch. + const s = String.fromCodePoint(ch); + // c. Let u be the same result produced as if by performing the algorithm for String.prototype.toUpperCase using s as the this value. + const u = s.toUpperCase(); + // d. Assert: Type(u) is String. + Assert(typeof u === 'string'); + // e. If u does not consist of a single code unit, return ch. + if (u.length !== 1) { + return ch; + } + // f. Let cu be u's single code unit element. + const cu = u.codePointAt(0); + // g. If the numeric value of ch ≥ 128 and the numeric value of cu < 128, return ch. + if (ch >= 128 && cu < 128) { + return ch; + } + // h. Return cu. + return cu; + } + } + + // #sec-atomescape + // AtomEscape :: + // DecimalEscape + // CharacterEscape + // CharacterClassEscape + // `k` GroupName + function Evaluate_AtomEscape(AtomEscape, direction) { + switch (true) { + case !!AtomEscape.DecimalEscape: { + // 1. Evaluate DecimalEscape to obtain an integer n. + const n = Evaluate(AtomEscape.DecimalEscape); + // 2. Assert: n ≤ NcapturingParens. + Assert(n <= NcapturingParens); + // 3. Call BackreferenceMatcher(n, direction) and return its Matcher result. + return BackreferenceMatcher(n, direction); + } + case !!AtomEscape.CharacterEscape: { + // 1. Evaluate CharacterEscape to obtain a character ch. + const ch = Evaluate(AtomEscape.CharacterEscape); + // 2. Let A be a one-element CharSet containing the character ch. + const A = new ConcreteCharSet([Canonicalize(ch)]); + // 3. Call CharacterSetMatcher(A, false, direction) and return its Matcher result. + return CharacterSetMatcher(A, false, direction); + } + case !!AtomEscape.CharacterClassEscape: { + // 1. Evaluate CharacterClassEscape to obtain a CharSet A. + const A = Evaluate(AtomEscape.CharacterClassEscape); + // 2. Call CharacterSetMatcher(A, false, direction) and return its Matcher result. + return CharacterSetMatcher(A, false, direction); + } + case !!AtomEscape.GroupName: { + // 1. Search the enclosing Pattern for an instance of a GroupSpecifier for a RegExpIdentifierName which has a StringValue equal to the StringValue of the RegExpIdentifierName contained in GroupName. + // 2. Assert: A unique such GroupSpecifier is found. + // 3. Let parenIndex be the number of left-capturing parentheses in the entire regular expression that occur to the left of the located GroupSpecifier. This is the total number of Atom :: `(` GroupSpecifier Disjunction `)` Parse Nodes prior to or enclosing the located GroupSpecifier. + const parenIndex = Pattern.groupSpecifiers.get(AtomEscape.GroupName); + Assert(parenIndex !== undefined); + // 4. Call BackreferenceMatcher(parenIndex, direction) and return its Matcher result. + return BackreferenceMatcher(parenIndex + 1, direction); + } + default: + throw new OutOfRange('Evaluate_AtomEscape', AtomEscape); + } + } + + // #sec-backreference-matcher + function BackreferenceMatcher(n, direction) { + // 1. Return a new Matcher with parameters (x, c) that captures n and direction and performs the following steps when called: + return (x, c) => { + // a. Assert: x is a State. + Assert(x instanceof State); + // b. Assert: c is a Continuation. + Assert(isContinuation(c)); + // c. Let cap be x's captures List. + const cap = x.captures; + // d. Let s be cap[n]. + const s = cap[n]; + // e. If s is undefined, return c(x). + if (s === Value.undefined) { + return c(x); + } + // f. Let e be x's endIndex. + const e = x.endIndex; + let len; + if (surroundingAgent.feature('regexp-match-indices')) { + // g. Let rs be r's startIndex. + const rs = s.startIndex; + // h. Let re be r's endIndex. + const re = s.endIndex; + // i. Let len be the number of elements in re - rs. + len = re - rs; + } else { + // g. Let len be the number of elements in s. + len = s.length; + } + // h. Let f be e + direction × len. + const f = e + direction * len; + // i. If f < 0 or f > InputLength, return failure. + if (f < 0 || f > InputLength) { + return 'failure'; + } + // j. Let g be min(e, f). + const g = Math.min(e, f); + // k. If there exists an integer i between 0 (inclusive) and len (exclusive) such that Canonicalize(s[i]) is not the same character value as Canonicalize(Input[g + i]), return failure. + for (let i = 0; i < len; i += 1) { + const part = surroundingAgent.feature('regexp-match-indices') + ? Input[s.startIndex + i] + : s[i]; + if (Canonicalize(part) !== Canonicalize(Input[g + i])) { + return 'failure'; + } + } + // l. Let y be the State (f, cap). + const y = new State(f, cap); + // m. Call c(y) and return its result. + return c(y); + }; + } + + // #sec-characterescape + // CharacterEscape :: + // ControlEscape + // `c` ControlLetter + // `0` [lookahead != DecimalDigit] + // HexEscapeSequence + // RegExpUnicodeEscapeSequence + // IdentityEscape + function Evaluate_CharacterEscape(CharacterEscape) { + // 1. Let cv be the CharacterValue of this CharacterEscape. + const cv = CharacterValue(CharacterEscape); + // 2. Return the character whose character value is cv. + return cv; + } + + // #sec-decimalescape + // DecimalEscape :: + // NonZeroDigit DecimalDigits? + function Evaluate_DecimalEscape(DecimalEscape) { + return DecimalEscape.value; + } + + // #sec-characterclassescape + // CharacterClassEscape :: + // `d` + // `D` + // `s` + // `S` + // `w` + // `W` + // `p{` UnicodePropertyValueExpression `}` + // `P{` UnicodePropertyValueExpression `}` + function Evaluate_CharacterClassEscape(node) { + switch (node.value) { + case 'd': + // 1. Return the ten-element set of characters containing the characters 0 through 9 inclusive. + return new ConcreteCharSet(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map((c) => c.codePointAt(0))); + case 'D': + // 1. Return the set of all characters not included in the set returned by CharacterClassEscape :: `d`. + return new VirtualCharSet((c) => !isDecimalDigit(String.fromCodePoint(c))); + case 's': + // 1. Return the set of characters containing the characters that are on the right-hand side of the WhiteSpace or LineTerminator productions. + return new VirtualCharSet((c) => { + const s = String.fromCodePoint(c); + return isWhitespace(s) || isLineTerminator(s); + }); + case 'S': + // 1. Return the set of all characters not included in the set returned by CharacterClassEscape :: `s`. + return new VirtualCharSet((c) => { + const s = String.fromCodePoint(c); + return !isWhitespace(s) && !isLineTerminator(s); + }); + case 'w': + // 1. Return the set of all characters returned by WordCharacters(). + return WordCharacters(); + case 'W': { + // 1. Return the set of all characters not included in the set returned by CharacterClassEscape :: `w`. + const s = WordCharacters(); + return new VirtualCharSet((c) => !s.has(c)); + } + case 'p': + // 1. Return the CharSet containing all Unicode code points included in the CharSet returned by UnicodePropertyValueExpression. + return Evaluate(node.UnicodePropertyValueExpression); + case 'P': + // 1. Return the CharSet containing all Unicode code points not included in the CharSet returned by UnicodePropertyValueExpression. + return new ConcreteCharSet([]); + default: + throw new OutOfRange('Evaluate_CharacterClassEscape', node); + } + } + + // UnicodePropertyValueExpression :: + // UnicodePropertyName `=` UnicodePropertyValue + // LoneUnicodePropertyNameOrValue + function Evaluate_UnicodePropertyValueExpression(UnicodePropertyValueExpression) { + if (UnicodePropertyValueExpression.LoneUnicodePropertyNameOrValue) { + // 1. Let s be SourceText of LoneUnicodePropertyNameOrValue. + const s = UnicodePropertyValueExpression.LoneUnicodePropertyNameOrValue; + // 2. If ! UnicodeMatchPropertyValue(General_Category, s) is identical to a List of Unicode code points that is the name of a Unicode general category or general category alias listed in the “Property value and aliases” column of Table 57, then + if (X(UnicodeMatchPropertyValue('General_Category', s) in UnicodeGeneralCategoryValues)) { + // a. Return the CharSet containing all Unicode code points whose character database definition includes the property “General_Category” with value s. + return getUnicodePropertyValueSet('General_Category', s); + } + // 3. Let p be ! UnicodeMatchProperty(s). + const p = X(UnicodeMatchProperty(s)); + // 4. Assert: p is a binary Unicode property or binary property alias listed in the “Property name and aliases” column of Table 56. + Assert(p in BinaryUnicodeProperties); + // 5. Return the CharSet containing all Unicode code points whose character database definition includes the property p with value “True”. + return getUnicodePropertyValueSet(p); + } + // 1. Let ps be SourceText of UnicodePropertyName. + const ps = UnicodePropertyValueExpression.UnicodePropertyName; + // 2. Let p be ! UnicodeMatchProperty(ps). + const p = X(UnicodeMatchProperty(ps)); + // 3. Assert: p is a Unicode property name or property alias listed in the “Property name and aliases” column of Table 55. + Assert(p in NonbinaryUnicodeProperties); + // 4. Let vs be SourceText of UnicodePropertyValue. + const vs = UnicodePropertyValueExpression.UnicodePropertyValue; + // 5. Let v be ! UnicodeMatchPropertyValue(p, vs). + const v = X(UnicodeMatchPropertyValue(p, vs)); + // 6. Return the CharSet containing all Unicode code points whose character database definition includes the property p with value v. + return getUnicodePropertyValueSet(p, v); + } + + // #sec-characterclass + // CharacterClass :: + // `[` ClassRanges `]` + // `[` `^` ClassRanges `]` + function Evaluate_CharacterClass({ invert, ClassRanges }) { + let A = new ConcreteCharSet([]); + for (const range of ClassRanges) { + if (Array.isArray(range)) { + const B = Evaluate(range[0]); + const C = Evaluate(range[1]); + const D = CharacterRange(B, C); + A = A.union(D); + } else { + A = A.union(Evaluate(range)); + } + } + return { A, invert }; + } + + // #sec-runtime-semantics-characterrange-abstract-operation + function CharacterRange(A, B) { + // 1. Assert: A and B each contain exactly one character. + Assert(A.size === 1 && B.size === 1); + // 2. Let a be the one character in CharSet A. + const a = A.first(); + // 3. Let b be the one character in CharSet B. + const b = B.first(); + // 4. Let i be the character value of character a. + const i = a; + // 5. Let j be the character value of character b. + const j = b; + // 6. Assert: i ≤ j. + Assert(i <= j); + // 7. Return the set containing all characters numbered i through j, inclusive. + const set = new Set(); + for (let k = i; k <= j; k += 1) { + set.add(Canonicalize(k)); + } + return new ConcreteCharSet(set); + } + + // #sec-classatom + // ClassAtom :: + // `-` + // ClassAtomNoDash + // ClassAtomNoDash :: + // SourceCharacter + // `\` ClassEscape + function Evaluate_ClassAtom(ClassAtom) { + switch (true) { + case !!ClassAtom.SourceCharacter: + // 1. Return the CharSet containing the character matched by SourceCharacter. + return new ConcreteCharSet([Canonicalize(ClassAtom.SourceCharacter.codePointAt(0))]); + case ClassAtom.value === '-': + // 1. Return the CharSet containing the single character - U+002D (HYPHEN-MINUS). + return new ConcreteCharSet([0x002D]); + default: + throw new OutOfRange('Evaluate_ClassAtom', ClassAtom); + } + } + + // #sec-classescape + // ClassEscape :: + // `b` + // `-` + // CharacterEscape + // CharacterClassEscape + function Evaluate_ClassEscape(ClassEscape) { + switch (true) { + case ClassEscape.value === 'b': + case ClassEscape.value === '-': + case !!ClassEscape.CharacterEscape: { + // 1. Let cv be the CharacterValue of this ClassEscape. + const cv = CharacterValue(ClassEscape); + // 2. Let c be the character whose character value is cv. + const c = cv; + // 3. Return the CharSet containing the single character c. + return new ConcreteCharSet([Canonicalize(c)]); + } + default: + throw new OutOfRange('Evaluate_ClassEscape', ClassEscape); + } + } +} diff --git a/engine262/src/runtime-semantics/RegularExpressionLiteral.mjs b/engine262/src/runtime-semantics/RegularExpressionLiteral.mjs new file mode 100644 index 0000000..59d9794 --- /dev/null +++ b/engine262/src/runtime-semantics/RegularExpressionLiteral.mjs @@ -0,0 +1,15 @@ +import { Value } from '../value.mjs'; +import { RegExpCreate } from '../abstract-ops/all.mjs'; +import { BodyText, FlagText } from '../static-semantics/all.mjs'; + +// #sec-regular-expression-literals-runtime-semantics-evaluation +// RegularExpressionLiteral : +// `/` RegularExpressionBody `/` RegularExpressionFlags +export function Evaluate_RegularExpressionLiteral(RegularExpressionLiteral) { + // 1. Let pattern be ! UTF16Encode(BodyText of RegularExpressionLiteral). + const pattern = new Value(BodyText(RegularExpressionLiteral)); + // 2. Let flags be ! UTF16Encode(FlagText of RegularExpressionLiteral). + const flags = new Value(FlagText(RegularExpressionLiteral)); + // 3. Return RegExpCreate(pattern, flags). + return RegExpCreate(pattern, flags); +} diff --git a/engine262/src/runtime-semantics/RelationalExpression.mjs b/engine262/src/runtime-semantics/RelationalExpression.mjs new file mode 100644 index 0000000..d7eab0d --- /dev/null +++ b/engine262/src/runtime-semantics/RelationalExpression.mjs @@ -0,0 +1,120 @@ +import { + surroundingAgent, +} from '../engine.mjs'; +import { + AbstractRelationalComparison, + Call, + GetMethod, + GetValue, + HasProperty, + IsCallable, + OrdinaryHasInstance, + ToBoolean, + ToPropertyKey, +} from '../abstract-ops/all.mjs'; +import { + Type, + Value, + wellKnownSymbols, +} from '../value.mjs'; +import { Q, X, ReturnIfAbrupt } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { OutOfRange } from '../helpers.mjs'; + +// #sec-instanceofoperator +export function InstanceofOperator(V, target) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (Type(target) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). + const instOfHandler = Q(GetMethod(target, wellKnownSymbols.hasInstance)); + // 3. If instOfHandler is not undefined, then + if (instOfHandler !== Value.undefined) { + // a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)). + return X(ToBoolean(Q(Call(instOfHandler, target, [V])))); + } + // 4. If IsCallable(target) is false, throw a TypeError exception. + if (IsCallable(target) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', target); + } + // 5. Return ? OrdinaryHasInstance(target, V). + return Q(OrdinaryHasInstance(target, V)); +} + +// #sec-relational-operators-runtime-semantics-evaluation +// RelationalExpression : +// RelationalExpression `<` ShiftExpression +// RelationalExpression `>` ShiftExpression +// RelationalExpression `<=` ShiftExpression +// RelationalExpression `>=` ShiftExpression +// RelationalExpression `instanceof` ShiftExpression +// RelationalExpression `in` ShiftExpression +export function* Evaluate_RelationalExpression({ RelationalExpression, operator, ShiftExpression }) { + // 1. Let lref be the result of evaluating RelationalExpression. + const lref = yield* Evaluate(RelationalExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let rref be the result of evaluating ShiftExpression. + const rref = yield* Evaluate(ShiftExpression); + // 4. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + switch (operator) { + case '<': { + // 5. Let r be the result of performing Abstract Relational Comparison lval < rval. + const r = AbstractRelationalComparison(lval, rval); + // 6. ReturnIfAbrupt(r). + ReturnIfAbrupt(r); + // 7. If r is undefined, return false. Otherwise, return r. + if (r === Value.undefined) { + return Value.false; + } + return r; + } + case '>': { + // 5. Let r be the result of performing Abstract Relational Comparison rval < lval with LeftFirst equal to false. + const r = AbstractRelationalComparison(rval, lval, false); + // 6. ReturnIfAbrupt(r). + ReturnIfAbrupt(r); + // 7. If r is undefined, return false. Otherwise, return r. + if (r === Value.undefined) { + return Value.false; + } + return r; + } + case '<=': { + // 5. Let r be the result of performing Abstract Relational Comparison rval < lval with LeftFirst equal to false. + const r = AbstractRelationalComparison(rval, lval, false); + // 6. ReturnIfAbrupt(r). + ReturnIfAbrupt(r); + // 7. If r is true or undefined, return false. Otherwise, return true. + if (r === Value.true || r === Value.undefined) { + return Value.false; + } + return Value.true; + } + case '>=': { + // 5. Let r be the result of performing Abstract Relational Comparison lval < rval. + const r = AbstractRelationalComparison(lval, rval); + // 6. ReturnIfAbrupt(r). + ReturnIfAbrupt(r); + // 7. If r is true or undefined, return false. Otherwise, return true. + if (r === Value.true || r === Value.undefined) { + return Value.false; + } + return Value.true; + } + case 'instanceof': + // 5. Return ? InstanceofOperator(lval, rval). + return Q(InstanceofOperator(lval, rval)); + case 'in': + // 5. Return ? InstanceofOperator(lval, rval). + if (Type(rval) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', rval); + } + // 6. Return ? HasProperty(rval, ? ToPropertyKey(lval)). + return Q(HasProperty(rval, ToPropertyKey(lval))); + default: + throw new OutOfRange('Evaluate_RelationalExpression', operator); + } +} diff --git a/engine262/src/runtime-semantics/RestBindingInitialization.mjs b/engine262/src/runtime-semantics/RestBindingInitialization.mjs new file mode 100644 index 0000000..749431e --- /dev/null +++ b/engine262/src/runtime-semantics/RestBindingInitialization.mjs @@ -0,0 +1,27 @@ +import { Value } from '../value.mjs'; +import { surroundingAgent } from '../engine.mjs'; +import { + CopyDataProperties, + InitializeReferencedBinding, + OrdinaryObjectCreate, + PutValue, + ResolveBinding, +} from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; + +// BindingRestProperty : `...` BindingIdentifier +export function RestBindingInitialization({ BindingIdentifier }, value, environment, excludedNames) { + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + const lhs = Q(ResolveBinding(StringValue(BindingIdentifier), environment, BindingIdentifier.strict)); + // 2. Let restObj be OrdinaryObjectCreate(%Object.prototype%). + const restObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). + Q(CopyDataProperties(restObj, value, excludedNames)); + // 4. If environment is undefined, return PutValue(lhs, restObj). + if (environment === Value.undefined) { + return PutValue(lhs, restObj); + } + // 5. Return InitializeReferencedBinding(lhs, restObj). + return InitializeReferencedBinding(lhs, restObj); +} diff --git a/engine262/src/runtime-semantics/ReturnStatement.mjs b/engine262/src/runtime-semantics/ReturnStatement.mjs new file mode 100644 index 0000000..2f07564 --- /dev/null +++ b/engine262/src/runtime-semantics/ReturnStatement.mjs @@ -0,0 +1,29 @@ +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { GetValue, GetGeneratorKind } from '../abstract-ops/all.mjs'; +import { + Completion, + Await, + Q, X, +} from '../completion.mjs'; + +// #sec-return-statement-runtime-semantics-evaluation +// ReturnStatement : +// `return` `;` +// `return` Expression `;` +export function* Evaluate_ReturnStatement({ Expression }) { + if (!Expression) { + // 1. Return Completion { [[Type]]: return, [[Value]]: undefined, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: Value.undefined, Target: undefined }); + } + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // 1. Let exprValue be ? GetValue(exprRef). + let exprValue = Q(GetValue(exprRef)); + // 1. If ! GetGeneratorKind() is async, set exprValue to ? Await(exprValue). + if (X(GetGeneratorKind()) === 'async') { + exprValue = Q(yield* Await(exprValue)); + } + // 1. Return Completion { [[Type]]: return, [[Value]]: exprValue, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: exprValue, Target: undefined }); +} diff --git a/engine262/src/runtime-semantics/Script.mjs b/engine262/src/runtime-semantics/Script.mjs new file mode 100644 index 0000000..339c455 --- /dev/null +++ b/engine262/src/runtime-semantics/Script.mjs @@ -0,0 +1,14 @@ +import { Value } from '../value.mjs'; +import { NormalCompletion } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; + +// #sec-script-semantics-runtime-semantics-evaluation +// Script : +// [empty] +// ScriptBody +export function* Evaluate_Script({ ScriptBody }) { + if (!ScriptBody) { + return NormalCompletion(Value.undefined); + } + return yield* Evaluate(ScriptBody); +} diff --git a/engine262/src/runtime-semantics/ScriptBody.mjs b/engine262/src/runtime-semantics/ScriptBody.mjs new file mode 100644 index 0000000..7ecfae6 --- /dev/null +++ b/engine262/src/runtime-semantics/ScriptBody.mjs @@ -0,0 +1,6 @@ +import { Evaluate_StatementList } from './all.mjs'; + +// ScriptBody : StatementList +export function Evaluate_ScriptBody(ScriptBody) { + return Evaluate_StatementList(ScriptBody.StatementList); +} diff --git a/engine262/src/runtime-semantics/ShiftExpression.mjs b/engine262/src/runtime-semantics/ShiftExpression.mjs new file mode 100644 index 0000000..eaf0a52 --- /dev/null +++ b/engine262/src/runtime-semantics/ShiftExpression.mjs @@ -0,0 +1,15 @@ +import { Q } from '../completion.mjs'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mjs'; + +// #sec-left-shift-operator-runtime-semantics-evaluation +// ShiftExpression : +// ShiftExpression `<<` AdditiveExpression +// #sec-signed-right-shift-operator-runtime-semantics-evaluation +// ShiftExpression : +// ShiftExpression `>>` AdditiveExpression +// #sec-unsigned-right-shift-operator-runtime-semantics-evaluation +// ShiftExpression : +// ShiftExpression `>>>` AdditiveExpression +export function* Evaluate_ShiftExpression({ ShiftExpression, operator, AdditiveExpression }) { + return Q(yield* EvaluateStringOrNumericBinaryExpression(ShiftExpression, operator, AdditiveExpression)); +} diff --git a/engine262/src/runtime-semantics/StatementList.mjs b/engine262/src/runtime-semantics/StatementList.mjs new file mode 100644 index 0000000..0e4095a --- /dev/null +++ b/engine262/src/runtime-semantics/StatementList.mjs @@ -0,0 +1,30 @@ +import { Evaluate } from '../evaluator.mjs'; +import { + EnsureCompletion, + ReturnIfAbrupt, + UpdateEmpty, + NormalCompletion, +} from '../completion.mjs'; + +// #sec-block-runtime-semantics-evaluation +export function* Evaluate_StatementList(StatementList) { + if (StatementList.length === 0) { + return NormalCompletion(undefined); + } + + let sl = yield* Evaluate(StatementList[0]); + if (StatementList.length === 1) { + return sl; + } + + for (const StatementListItem of StatementList.slice(1)) { + ReturnIfAbrupt(sl); + let s = yield* Evaluate(StatementListItem); + // We don't always return a Completion value, but here we actually need it + // to be a Completion. + s = EnsureCompletion(s); + sl = UpdateEmpty(s, sl); + } + + return sl; +} diff --git a/engine262/src/runtime-semantics/StringIndexOf.mjs b/engine262/src/runtime-semantics/StringIndexOf.mjs new file mode 100644 index 0000000..a6917d5 --- /dev/null +++ b/engine262/src/runtime-semantics/StringIndexOf.mjs @@ -0,0 +1,43 @@ +import { Type, Value } from '../value.mjs'; +import { Assert } from '../abstract-ops/all.mjs'; + +// https://tc39.es/proposal-string-replaceall/#sec-stringindexof +export function StringIndexOf(string, searchValue, fromIndex) { + // 1. Assert: Type(string) is String. + Assert(Type(string) === 'String'); + // 2. Assert: Type(searchValue) is String. + Assert(Type(searchValue) === 'String'); + // 3. Assert: fromIndex is a nonnegative integer. + Assert(Number.isInteger(fromIndex) && fromIndex >= 0); + const stringStr = string.stringValue(); + const searchStr = searchValue.stringValue(); + // 4. Let len be the length of string. + const len = stringStr.length; + // 5. If searchValue is the empty string, and fromIndex <= len, return fromIndex. + if (searchStr === '' && fromIndex <= len) { + return new Value(fromIndex); + } + // 6. Let searchLen be the length of searchValue. + const searchLen = searchStr.length; + // 7. If there exists any integer k such that fromIndex ≤ k ≤ len - searchLen and for all nonnegative integers j less than searchLen, + // the code unit at index k + j within string is the same as the code unit at index j within searchValue, let pos be the smallest (closest to -∞) such integer. + // Otherwise, let pos be -1. + let k = fromIndex; + let pos = -1; + while (k + searchLen <= len) { + let match = true; + for (let j = 0; j < searchLen; j += 1) { + if (searchStr[j] !== stringStr[k + j]) { + match = false; + break; + } + } + if (match) { + pos = k; + break; + } + k += 1; + } + // 8. Return pos. + return new Value(pos); +} diff --git a/engine262/src/runtime-semantics/StringPad.mjs b/engine262/src/runtime-semantics/StringPad.mjs new file mode 100644 index 0000000..e9cbbe2 --- /dev/null +++ b/engine262/src/runtime-semantics/StringPad.mjs @@ -0,0 +1,31 @@ +import { Value } from '../value.mjs'; +import { Assert, ToString, ToLength } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-stringpad +export function StringPad(O, maxLength, fillString, placement) { + Assert(placement === 'start' || placement === 'end'); + const S = Q(ToString(O)); + const intMaxLength = Q(ToLength(maxLength)).numberValue(); + const stringLength = S.stringValue().length; + if (intMaxLength <= stringLength) { + return S; + } + let filler; + if (fillString === Value.undefined) { + filler = ' '; + } else { + filler = Q(ToString(fillString)).stringValue(); + } + if (filler === '') { + return S; + } + const fillLen = intMaxLength - stringLength; + const stringFiller = filler.repeat(Math.ceil(fillLen / filler.length)); + const truncatedStringFiller = stringFiller.slice(0, fillLen); + if (placement === 'start') { + return new Value(truncatedStringFiller + S.stringValue()); + } else { + return new Value(S.stringValue() + truncatedStringFiller); + } +} diff --git a/engine262/src/runtime-semantics/SuperCall.mjs b/engine262/src/runtime-semantics/SuperCall.mjs new file mode 100644 index 0000000..848cd8e --- /dev/null +++ b/engine262/src/runtime-semantics/SuperCall.mjs @@ -0,0 +1,52 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + Construct, + GetNewTarget, + GetThisEnvironment, + IsConstructor, + isECMAScriptFunctionObject, +} from '../abstract-ops/all.mjs'; +import { Type, Value } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; +import { FunctionEnvironmentRecord } from '../environment.mjs'; +import { ArgumentListEvaluation } from './all.mjs'; + +// #sec-super-keyword-runtime-semantics-evaluation +// SuperCall : `super` Arguments +export function* Evaluate_SuperCall({ Arguments }) { + // 1. Let newTarget be GetNewTarget(). + const newTarget = GetNewTarget(); + // 2. Assert: Type(newTarget) is Object. + Assert(Type(newTarget) === 'Object'); + // 3. Let func be ! GetSuperConstructor(). + const func = X(GetSuperConstructor()); + // 4. Let argList be ? ArgumentListEvaluation of Arguments. + const argList = Q(yield* ArgumentListEvaluation(Arguments)); + // 5. If IsConstructor(func) is false, throw a TypeError exception. + if (IsConstructor(func) === Value.false) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', func); + } + // 6. Let result be ? Construct(func, argList, newTarget). + const result = Q(Construct(func, argList, newTarget)); + // 7. Let thisER be GetThisEnvironment(). + const thisER = GetThisEnvironment(); + // 8. Return ? thisER.BindThisValue(result). + return Q(thisER.BindThisValue(result)); +} + +// #sec-getsuperconstructor +function GetSuperConstructor() { + // 1. Let envRec be GetThisEnvironment(). + const envRec = GetThisEnvironment(); + // 2. Assert: envRec is a function Environment Record. + Assert(envRec instanceof FunctionEnvironmentRecord); + // 3. Let activeFunction be envRec.[[FunctionObject]]. + const activeFunction = envRec.FunctionObject; + // 4. Assert: activeFunction is an ECMAScript function object. + Assert(isECMAScriptFunctionObject(activeFunction)); + // 5. Let superConstructor be ! activeFunction.[[GetPrototypeOf]](). + const superConstructor = X(activeFunction.GetPrototypeOf()); + // 6. Return superConstructor. + return superConstructor; +} diff --git a/engine262/src/runtime-semantics/SuperProperty.mjs b/engine262/src/runtime-semantics/SuperProperty.mjs new file mode 100644 index 0000000..8e2876a --- /dev/null +++ b/engine262/src/runtime-semantics/SuperProperty.mjs @@ -0,0 +1,60 @@ +import { Evaluate } from '../evaluator.mjs'; +import { SuperReference, Value } from '../value.mjs'; +import { + Assert, + GetThisEnvironment, + GetValue, + RequireObjectCoercible, + ToPropertyKey, +} from '../abstract-ops/all.mjs'; +import { StringValue } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-makesuperpropertyreference +function MakeSuperPropertyReference(actualThis, propertyKey, strict) { + // 1. Let env be GetThisEnvironment(). + const env = GetThisEnvironment(); + // 2. Assert: env.HasSuperBinding() is true. + Assert(env.HasSuperBinding() === Value.true); + // 3. Let baseValue be ? env.GetSuperBase(). + const baseValue = Q(env.GetSuperBase()); + // 4. Let bv be ? RequireObjectCoercible(baseValue). + const bv = Q(RequireObjectCoercible(baseValue)); + // 5. Return a value of type Reference that is a Super Reference whose base value component is bv, + // whose referenced name component is propertyKey, whose thisValue component is actualThis, and + // whose strict reference flag is strict. + return new SuperReference({ + BaseValue: bv, + ReferencedName: propertyKey, + thisValue: actualThis, + StrictReference: strict ? Value.true : Value.false, + }); +} + +// #sec-super-keyword-runtime-semantics-evaluation +// SuperProperty : +// `super` `[` Expression `]` +// `super` `.` IdentifierName +export function* Evaluate_SuperProperty({ Expression, IdentifierName, strict }) { + // 1. Let env be GetThisEnvironment(). + const env = GetThisEnvironment(); + // 2. Let actualThis be ? env.GetThisBinding(). + const actualThis = Q(env.GetThisBinding()); + if (Expression) { + // 3. Let propertyNameReference be the result of evaluating Expression. + const propertyNameReference = yield* Evaluate(Expression); + // 4. Let propertyNameReference be the result of evaluating Expression. + const propertyNameValue = Q(GetValue(propertyNameReference)); + // 5. Let propertyNameValue be ? GetValue(propertyNameReference). + const propertyKey = Q(ToPropertyKey(propertyNameValue)); + // 6. If the code matched by this SuperProperty is strict mode code, let strict be true; else let strict be false. + // 7. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). + return Q(MakeSuperPropertyReference(actualThis, propertyKey, strict)); + } else { + // 3. Let propertyKey be StringValue of IdentifierName. + const propertyKey = StringValue(IdentifierName); + // 4. const strict = SuperProperty.strict; + // 5. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). + return Q(MakeSuperPropertyReference(actualThis, propertyKey, strict)); + } +} diff --git a/engine262/src/runtime-semantics/SwitchStatement.mjs b/engine262/src/runtime-semantics/SwitchStatement.mjs new file mode 100644 index 0000000..d77eee2 --- /dev/null +++ b/engine262/src/runtime-semantics/SwitchStatement.mjs @@ -0,0 +1,218 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { Assert, GetValue, StrictEqualityComparison } from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { + Completion, + AbruptCompletion, + NormalCompletion, + EnsureCompletion, + UpdateEmpty, + Q, +} from '../completion.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + BlockDeclarationInstantiation, + Evaluate_StatementList, +} from './all.mjs'; + +// #sec-runtime-semantics-caseclauseisselected +function* CaseClauseIsSelected(C, input) { + // 1. Assert: C is an instance of the production CaseClause : `case` Expression `:` StatementList?. + Assert(C.type === 'CaseClause'); + // 2. Let exprRef be the result of evaluating the Expression of C. + const exprRef = yield* Evaluate(C.Expression); + // 3. Let clauseSelector be ? GetValue(exprRef). + const clauseSelector = Q(GetValue(exprRef)); + // 4. Return the result of performing Strict Equality Comparison input === clauseSelector. + return StrictEqualityComparison(input, clauseSelector); +} + +// #sec-runtime-semantics-caseblockevaluation +// CaseBlock : +// `{` `}` +// `{` CaseClauses `}` +// `{` CaseClauses? DefaultClause CaseClauses? `}` +function* CaseBlockEvaluation({ CaseClauses_a, DefaultClause, CaseClauses_b }, input) { + switch (true) { + case !CaseClauses_a && !DefaultClause && !CaseClauses_b: { + // 1. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); + } + case !!CaseClauses_a && !DefaultClause && !CaseClauses_b: { + // 1. Let V be undefined. + let V = Value.undefined; + // 2. Let A be the List of CaseClause items in CaseClauses, in source text order. + const A = CaseClauses_a; + // 3. Let found be false. + let found = Value.false; + // 4. For each CaseClause C in A, do + for (const C of A) { + // a. If found is false, then + if (found === Value.false) { + // i. Set found to ? CaseClauseIsSelected(C, input). + found = Q(yield* CaseClauseIsSelected(C, input)); + } + // b. If found is true, them + if (found === Value.true) { + // i. Let R be the result of evaluating C. + const R = EnsureCompletion(yield* Evaluate(C)); + // ii. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (R.Value !== undefined) { + V = R.Value; + } + // iii. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (R instanceof AbruptCompletion) { + return Completion(UpdateEmpty(R, V)); + } + } + } + // 5. Return NormalCompletion(V). + return NormalCompletion(V); + } + case !!DefaultClause: { + // 1. Let V be undefined. + let V = Value.undefined; + // 2. If the first CaseClauses is present, then + let A; + if (CaseClauses_a) { + // a. Let A be the List of CaseClause items in the first CaseClauses, in source text order. + A = CaseClauses_a; + } else { // 3. Else, + // a. Let A be « ». + A = []; + } + let found = Value.false; + // 4. For each CaseClause C in A, do + for (const C of A) { + // a. If found is false, then + if (found === Value.false) { + // i. Set found to ? CaseClauseIsSelected(C, input). + found = Q(yield* CaseClauseIsSelected(C, input)); + } + // b. If found is true, them + if (found === Value.true) { + // i. Let R be the result of evaluating C. + const R = EnsureCompletion(yield* Evaluate(C)); + // ii. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (R.Value !== undefined) { + V = R.Value; + } + // iii. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (R instanceof AbruptCompletion) { + return Completion(UpdateEmpty(R, V)); + } + } + } + // 6. Let foundInB be false. + let foundInB = Value.false; + // 7. If the second CaseClauses is present, then + let B; + if (CaseClauses_b) { + // a. Let B be the List of CaseClause items in the second CaseClauses, in source text order. + B = CaseClauses_b; + } else { // 8. Else, + // a. Let B be « ». + B = []; + } + // 9. If found is false, then + if (found === Value.false) { + // a. For each CaseClause C in B, do + for (const C of B) { + // a. If foundInB is false, then + if (foundInB === Value.false) { + // i. Set foundInB to ? CaseClauseIsSelected(C, input). + foundInB = Q(yield* CaseClauseIsSelected(C, input)); + } + // b. If foundInB is true, them + if (foundInB === Value.true) { + // i. Let R be the result of evaluating C. + const R = EnsureCompletion(yield* Evaluate(C)); + // ii. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (R.Value !== undefined) { + V = R.Value; + } + // iii. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (R instanceof AbruptCompletion) { + return Completion(UpdateEmpty(R, V)); + } + } + } + } + // 10. If foundInB is true, return NormalCompletion(V). + if (foundInB === Value.true) { + return NormalCompletion(V); + } + // 11. Let R be the result of evaluating DefaultClause. + const R = EnsureCompletion(yield* Evaluate(DefaultClause)); + // 12. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (R.Value !== undefined) { + V = R.Value; + } + // 13. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (R instanceof AbruptCompletion) { + return Completion(UpdateEmpty(R, V)); + } + // 14. NOTE: The following is another complete iteration of the second CaseClauses. + // 15. For each CaseClause C in B, do + for (const C of B) { + // a. Let R be the result of evaluating CaseClause C. + const innerR = EnsureCompletion(yield* Evaluate(C)); + // b. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (innerR.Value !== undefined) { + V = innerR.Value; + } + // c. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (innerR instanceof AbruptCompletion) { + return Completion(UpdateEmpty(innerR, V)); + } + } + // 16. Return NormalCompletion(V). + // + return NormalCompletion(V); + } + default: + throw new OutOfRange('CaseBlockEvaluation'); + } +} + +// #sec-switch-statement-runtime-semantics-evaluation +// SwitchStatement : +// `switch` `(` Expression `)` CaseBlock +export function* Evaluate_SwitchStatement({ Expression, CaseBlock }) { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // 2. Let switchValue be ? GetValue(exprRef). + const switchValue = Q(GetValue(exprRef)); + // 3. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let blockEnv be NewDeclarativeEnvironment(oldEnv). + const blockEnv = NewDeclarativeEnvironment(oldEnv); + // 5. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv). + BlockDeclarationInstantiation(CaseBlock, blockEnv); + // 6. Set the running execution context's LexicalEnvironment to blockEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = blockEnv; + // 7. Let R be CaseBlockEvaluation of CaseBlock with argument switchValue. + const R = yield* CaseBlockEvaluation(CaseBlock, switchValue); + // 8. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 9. return R. + return R; +} + +// #sec-switch-statement-runtime-semantics-evaluation +// CaseClause : +// `case` Expression `:` +// `case` Expression `:` StatementList +// DefaultClause : +// `case` `default` `:` +// `case` `default` `:` StatementList +export function* Evaluate_CaseClause({ StatementList }) { + if (!StatementList) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + // 1. Return the result of evaluating StatementList. + return yield* Evaluate_StatementList(StatementList); +} diff --git a/engine262/src/runtime-semantics/TaggedTemplateExpression.mjs b/engine262/src/runtime-semantics/TaggedTemplateExpression.mjs new file mode 100644 index 0000000..fec48d6 --- /dev/null +++ b/engine262/src/runtime-semantics/TaggedTemplateExpression.mjs @@ -0,0 +1,22 @@ +import { Evaluate } from '../evaluator.mjs'; +import { GetValue } from '../abstract-ops/all.mjs'; +import { IsInTailPosition } from '../static-semantics/all.mjs'; +import { Q } from '../completion.mjs'; +import { EvaluateCall } from './all.mjs'; + +// #sec-tagged-templates-runtime-semantics-evaluation +// MemberExpression : +// MemberExpression TemplateLiteral +export function* Evaluate_TaggedTemplateExpression(node) { + const { MemberExpression, TemplateLiteral } = node; + // 1. Let tagRef be the result of evaluating MemberExpression. + const tagRef = yield* Evaluate(MemberExpression); + // 1. Let tagFunc be ? GetValue(tagRef). + const tagFunc = Q(GetValue(tagRef)); + // 1. Let thisCall be this MemberExpression. + const thisCall = node; + // 1. Let tailCall be IsInTailPosition(thisCall). + const tailCall = IsInTailPosition(thisCall); + // 1. Return ? EvaluateCall(tagFunc, tagRef, TemplateLiteral, tailCall). + return Q(yield* EvaluateCall(tagFunc, tagRef, TemplateLiteral, tailCall)); +} diff --git a/engine262/src/runtime-semantics/TemplateLiteral.mjs b/engine262/src/runtime-semantics/TemplateLiteral.mjs new file mode 100644 index 0000000..f5dbc7e --- /dev/null +++ b/engine262/src/runtime-semantics/TemplateLiteral.mjs @@ -0,0 +1,33 @@ +import { Value } from '../value.mjs'; +import { Q } from '../completion.mjs'; +import { GetValue, ToString } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { TV } from '../static-semantics/all.mjs'; + +// 12.2.9.6 #sec-template-literals-runtime-semantics-evaluation +// TemplateLiteral : NoSubstitutionTemplate +// SubstitutionTemplate : TemplateHead Expression TemplateSpans +// TemplateSpans : TemplateTail +// TemplateSpans : TemplateMiddleList TemplateTail +// TemplateMiddleList : TemplateMiddle Expression +// TemplateMiddleList : TemplateMiddleList TemplateMiddle Expression +// +// (implicit) +// TemplateLiteral : SubstitutionTemplate +export function* Evaluate_TemplateLiteral({ TemplateSpanList, ExpressionList }) { + let str = ''; + for (let i = 0; i < TemplateSpanList.length - 1; i += 1) { + const Expression = ExpressionList[i]; + const head = TV(TemplateSpanList[i]); + // 2. Let subRef be the result of evaluating Expression. + const subRef = yield* Evaluate(Expression); + // 3. Let sub be ? GetValue(subRef). + const sub = Q(GetValue(subRef)); + // 4. Let middle be ? ToString(sub). + const middle = Q(ToString(sub)); + str += head; + str += middle.stringValue(); + } + const tail = TV(TemplateSpanList[TemplateSpanList.length - 1]); + return new Value(str + tail); +} diff --git a/engine262/src/runtime-semantics/This.mjs b/engine262/src/runtime-semantics/This.mjs new file mode 100644 index 0000000..54af2e0 --- /dev/null +++ b/engine262/src/runtime-semantics/This.mjs @@ -0,0 +1,8 @@ +import { ResolveThisBinding } from '../abstract-ops/all.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-this-keyword-runtime-semantics-evaluation +// PrimaryExpression : `this` +export function Evaluate_This(_PrimaryExpression) { + return Q(ResolveThisBinding()); +} diff --git a/engine262/src/runtime-semantics/ThrowStatement.mjs b/engine262/src/runtime-semantics/ThrowStatement.mjs new file mode 100644 index 0000000..27d11a6 --- /dev/null +++ b/engine262/src/runtime-semantics/ThrowStatement.mjs @@ -0,0 +1,21 @@ +import { + Evaluate, +} from '../evaluator.mjs'; +import { + GetValue, +} from '../abstract-ops/all.mjs'; +import { + Q, + ThrowCompletion, +} from '../completion.mjs'; + +// #sec-throw-statement-runtime-semantics-evaluation +// ThrowStatement : `throw` Expression `;` +export function* Evaluate_ThrowStatement({ Expression }) { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = yield* Evaluate(Expression); + // 2. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(GetValue(exprRef)); + // 3. Return ThrowCompletion(exprValue). + return ThrowCompletion(exprValue); +} diff --git a/engine262/src/runtime-semantics/TrimString.mjs b/engine262/src/runtime-semantics/TrimString.mjs new file mode 100644 index 0000000..0b9390e --- /dev/null +++ b/engine262/src/runtime-semantics/TrimString.mjs @@ -0,0 +1,19 @@ +import { Assert, RequireObjectCoercible, ToString } from '../abstract-ops/all.mjs'; +import { Value } from '../value.mjs'; +import { Q } from '../completion.mjs'; + +// #sec-trimstring +export function TrimString(string, where) { + const str = Q(RequireObjectCoercible(string)); + const S = Q(ToString(str)).stringValue(); + let T; + if (where === 'start') { + T = S.trimStart(); + } else if (where === 'end') { + T = S.trimEnd(); + } else { + Assert(where === 'start+end'); + T = S.trim(); + } + return new Value(T); +} diff --git a/engine262/src/runtime-semantics/TryStatement.mjs b/engine262/src/runtime-semantics/TryStatement.mjs new file mode 100644 index 0000000..d5ec78c --- /dev/null +++ b/engine262/src/runtime-semantics/TryStatement.mjs @@ -0,0 +1,119 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { + Completion, + AbruptCompletion, + UpdateEmpty, + EnsureCompletion, + X, +} from '../completion.mjs'; +import { BoundNames } from '../static-semantics/all.mjs'; +import { NewDeclarativeEnvironment } from '../environment.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { BindingInitialization } from './all.mjs'; + +// #sec-try-statement-runtime-semantics-evaluation +// TryStatement : +// `try` Block Catch +// `try` Block Finally +// `try` Block Catch Finally +export function Evaluate_TryStatement(TryStatement) { + switch (true) { + case !!TryStatement.Catch && !TryStatement.Finally: + return Evaluate_TryStatement_BlockCatch(TryStatement); + case !TryStatement.Catch && !!TryStatement.Finally: + return Evaluate_TryStatement_BlockFinally(TryStatement); + case !!TryStatement.Catch && !!TryStatement.Finally: + return Evaluate_TryStatement_BlockCatchFinally(TryStatement); + default: + throw new OutOfRange('Evaluate_TryStatement', TryStatement); + } +} + +// TryStatement : `try` Block Catch +function* Evaluate_TryStatement_BlockCatch({ Block, Catch }) { + // 1. Let B be the result of evaluating Block. + const B = EnsureCompletion(yield* Evaluate(Block)); + // 2. If B.[[Type]] is throw, let C be CatchClauseEvaluation of Catch with argument B.[[Value]]. + let C; + if (B.Type === 'throw') { + C = EnsureCompletion(yield* CatchClauseEvaluation(Catch, B.Value)); + } else { // 3. Else, let C be B. + C = B; + } + // 3. Return Completion(UpdateEmpty(C, undefined)). + return Completion(UpdateEmpty(C, Value.undefined)); +} + +// TryStatement : `try` Block Finally +function* Evaluate_TryStatement_BlockFinally({ Block, Finally }) { + // 1. Let B be the result of evaluating Block. + const B = EnsureCompletion(yield* Evaluate(Block)); + // 1. Let F be the result of evaluating Finally. + let F = EnsureCompletion(yield* Evaluate(Finally)); + // 1. If F.[[Type]] is normal, set F to B. + if (F.Type === 'normal') { + F = B; + } + // 1. Return Completion(UpdateEmpty(F, undefined)). + return Completion(UpdateEmpty(F, Value.undefined)); +} + +// TryStatement : `try` Block Catch Finally +function* Evaluate_TryStatement_BlockCatchFinally({ Block, Catch, Finally }) { + // 1. Let B be the result of evaluating Block. + const B = EnsureCompletion(yield* Evaluate(Block)); + // 2. If B.[[Type]] is throw, let C be CatchClauseEvaluation of Catch with argument B.[[Value]]. + let C; + if (B.Type === 'throw') { + C = EnsureCompletion(yield* CatchClauseEvaluation(Catch, B.Value)); + } else { // 3. Else, let C be B. + C = B; + } + // 4. Let F be the result of evaluating Finally. + let F = EnsureCompletion(yield* Evaluate(Finally)); + // 5. If F.[[Type]] is normal, set F to C. + if (F.Type === 'normal') { + F = C; + } + // 6. Return Completion(UpdateEmpty(F, undefined)). + return Completion(UpdateEmpty(F, Value.undefined)); +} + +// #sec-runtime-semantics-catchclauseevaluation +// Catch : +// `catch` Block +// `catch` `(` CatchParameter `)` Block +function* CatchClauseEvaluation({ CatchParameter, Block }, thrownValue) { + if (!CatchParameter) { + // 1. Return the result of evaluating Block. + return yield* Evaluate(Block); + } + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let catchEnv be NewDeclarativeEnvironment(oldEnv). + const catchEnv = NewDeclarativeEnvironment(oldEnv); + // 3. For each element argName of the BoundNames of CatchParameter, do + for (const argName of BoundNames(CatchParameter)) { + // a. Perform ! catchEnv.CreateMutableBinding(argName, false). + X(catchEnv.CreateMutableBinding(argName, Value.false)); + } + // 4. Set the running execution context's LexicalEnvironment to catchEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = catchEnv; + // 5. Let status be BindingInitialization of CatchParameter with arguments thrownValue and catchEnv. + const status = yield* BindingInitialization(CatchParameter, thrownValue, catchEnv); + // 6. If status is an abrupt completion, then + if (status instanceof AbruptCompletion) { + // a. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // b. Return Completion(status). + return Completion(status); + } + // 7. Let B be the result of evaluating Block. + const B = EnsureCompletion(yield* Evaluate(Block)); + // 8. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 9. Return Completion(B). + return Completion(B); +} diff --git a/engine262/src/runtime-semantics/UnaryExpression.mjs b/engine262/src/runtime-semantics/UnaryExpression.mjs new file mode 100644 index 0000000..0a86b75 --- /dev/null +++ b/engine262/src/runtime-semantics/UnaryExpression.mjs @@ -0,0 +1,195 @@ +import { surroundingAgent } from '../engine.mjs'; +import { + Assert, + GetBase, + GetReferencedName, + GetValue, + IsCallable, + IsPropertyReference, + IsStrictReference, + IsSuperReference, + IsUnresolvableReference, + ToBoolean, + ToNumber, + ToObject, + ToNumeric, +} from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { Q, ReturnIfAbrupt, X } from '../completion.mjs'; +import { Type, TypeNumeric, Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; + +// #sec-delete-operator-runtime-semantics-evaluation +// UnaryExpression : `delete` UnaryExpression +function* Evaluate_UnaryExpression_Delete({ UnaryExpression }) { + // 1. Let ref be the result of evaluating UnaryExpression. + const ref = yield* Evaluate(UnaryExpression); + // 2. ReturnIfAbrupt(ref). + ReturnIfAbrupt(ref); + // 3. If Type(ref) is not Reference, return true. + if (Type(ref) !== 'Reference') { + return Value.true; + } + // 4. If IsUnresolvableReference(ref) is true, then + if (IsUnresolvableReference(ref) === Value.true) { + // a. Assert: IsStrictReference(ref) is false. + Assert(IsStrictReference(ref) === Value.false); + // b. Return true. + return Value.true; + } + // 5. If IsPropertyReference(ref) is true, then + if (IsPropertyReference(ref) === Value.true) { + // a. If IsSuperReference(ref) is true, throw a ReferenceError exception. + if (IsSuperReference(ref) === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'CannotDeleteSuper'); + } + // b. Let baseObj be ! ToObject(GetBase(ref)). + const baseObj = X(ToObject(GetBase(ref))); + // c. Let deleteStatus be ? baseObj.[[Delete]](GetReferencedName(ref)). + const deleteStatus = Q(baseObj.Delete(GetReferencedName(ref))); + // d. If deleteStatus is false and IsStrictReference(ref) is true, throw a TypeError exception. + if (deleteStatus === Value.false && IsStrictReference(ref) === Value.true) { + return surroundingAgent.Throw('TypeError', 'StrictModeDelete', GetReferencedName(ref)); + } + // e. Return deleteStatus. + return deleteStatus; + } else { // 6. Else, + // a. Assert: ref is a Reference to an Environment Record binding. + // b. Let bindings be GetBase(ref). + const bindings = GetBase(ref); + // c. Return ? bindings.DeleteBinding(GetReferencedName(ref)). + return Q(bindings.DeleteBinding(GetReferencedName(ref))); + } +} + +// #sec-void-operator-runtime-semantics-evaluation +// UnaryExpression : `void` UnaryExpression +function* Evaluate_UnaryExpression_Void({ UnaryExpression }) { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Perform ? GetValue(expr). + Q(GetValue(expr)); + // 3. Return undefined. + return Value.undefined; +} + +// 12.5.5.1 #sec-typeof-operator-runtime-semantics-evaluation +// UnaryExpression : `typeof` UnaryExpression +function* Evaluate_UnaryExpression_Typeof({ UnaryExpression }) { + // 1. Let val be the result of evaluating UnaryExpression. + let val = yield* Evaluate(UnaryExpression); + // 2. If Type(val) is Reference, then + if (Type(val) === 'Reference') { + // a. If IsUnresolvableReference(val) is true, return "undefined". + if (IsUnresolvableReference(val) === Value.true) { + return new Value('undefined'); + } + } + // 3. Set val to ? GetValue(val). + val = Q(GetValue(val)); + // 4. Return a String according to Table 37. + const type = Type(val); + switch (type) { + case 'Undefined': + return new Value('undefined'); + case 'Null': + return new Value('object'); + case 'Boolean': + return new Value('boolean'); + case 'Number': + return new Value('number'); + case 'String': + return new Value('string'); + case 'BigInt': + return new Value('bigint'); + case 'Symbol': + return new Value('symbol'); + case 'Object': + if (IsCallable(val) === Value.true) { + return new Value('function'); + } + return new Value('object'); + default: + throw new OutOfRange('Evaluate_UnaryExpression_Typeof', type); + } +} + +// #sec-unary-plus-operator-runtime-semantics-evaluation +// UnaryExpression : `+` UnaryExpression +function* Evaluate_UnaryExpression_Plus({ UnaryExpression }) { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Return ? ToNumber(? GetValue(expr)). + return Q(ToNumber(Q(GetValue(expr)))); +} + +// #sec-unary-minus-operator-runtime-semantics-evaluation +// UnaryExpression : `-` UnaryExpression +function* Evaluate_UnaryExpression_Minus({ UnaryExpression }) { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(ToNumeric(Q(GetValue(expr)))); + // 3. Let T be Type(oldValue). + const T = TypeNumeric(oldValue); + // 4. Return ! T::unaryMinus(oldValue). + return X(T.unaryMinus(oldValue)); +} + +// #sec-bitwise-not-operator-runtime-semantics-evaluation +// UnaryExpression : `~` UnaryExpression +function* Evaluate_UnaryExpression_Tilde({ UnaryExpression }) { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(ToNumeric(Q(GetValue(expr)))); + // 3. Let T be Type(oldValue). + const T = TypeNumeric(oldValue); + // 4. Return ! T::bitwiseNOT(oldValue). + return X(T.bitwiseNOT(oldValue)); +} + +// #sec-logical-not-operator-runtime-semantics-evaluation +// UnaryExpression : `!` UnaryExpression +function* Evaluate_UnaryExpression_Bang({ UnaryExpression }) { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Let oldValue be ! ToBoolean(? GetValue(expr)). + const oldValue = ToBoolean(Q(GetValue(expr))); + // 3. If oldValue is true, return false. + if (oldValue === Value.true) { + return Value.false; + } + // 4. Return true. + return Value.true; +} + +// UnaryExpression : +// `delete` UnaryExpression +// `void` UnaryExpression +// `typeof` UnaryExpression +// `+` UnaryExpression +// `-` UnaryExpression +// `~` UnaryExpression +// `!` UnaryExpression +export function* Evaluate_UnaryExpression(UnaryExpression) { + switch (UnaryExpression.operator) { + case 'delete': + return yield* Evaluate_UnaryExpression_Delete(UnaryExpression); + case 'void': + return yield* Evaluate_UnaryExpression_Void(UnaryExpression); + case 'typeof': + return yield* Evaluate_UnaryExpression_Typeof(UnaryExpression); + case '+': + return yield* Evaluate_UnaryExpression_Plus(UnaryExpression); + case '-': + return yield* Evaluate_UnaryExpression_Minus(UnaryExpression); + case '~': + return yield* Evaluate_UnaryExpression_Tilde(UnaryExpression); + case '!': + return yield* Evaluate_UnaryExpression_Bang(UnaryExpression); + + default: + throw new OutOfRange('Evaluate_UnaryExpression', UnaryExpression); + } +} diff --git a/engine262/src/runtime-semantics/Unicode.mjs b/engine262/src/runtime-semantics/Unicode.mjs new file mode 100644 index 0000000..62fa7d3 --- /dev/null +++ b/engine262/src/runtime-semantics/Unicode.mjs @@ -0,0 +1,542 @@ +import { Assert } from '../abstract-ops/all.mjs'; + +// #table-nonbinary-unicode-properties +export const NonbinaryUnicodeProperties = { + __proto__: null, + General_Category: 'General_Category', + gc: 'General_Category', + Script: 'Script', + sc: 'Script', + Script_Extensions: 'Script_Extensions', + scx: 'Script_Extensions', +}; + +// #table-binary-unicode-properties +export const BinaryUnicodeProperties = { + __proto__: null, + ASCII: 'ASCII', + ASCII_Hex_Digit: 'ASCII_Hex_Digit', + AHex: 'ASCII_Hex_Digit', + Alphabetic: 'Alphabetic', + Alpha: 'Alphabetic', + Any: 'Any', + Assigned: 'Assigned', + Bidi_Control: 'Bidi_Control', + Bidi_C: 'Bidi_Control', + Bidi_Mirrored: 'Bidi_Mirrored', + Bidi_M: 'Bidi_Mirrored', + Case_Ignorable: 'Case_Ignorable', + CI: 'Case_Ignorable', + Cased: 'Cased', + Changes_When_Casefolded: 'Changes_When_Casefolded', + CWCF: 'Changes_When_Casefolded', + Changes_When_Casemapped: 'Changes_When_Casemapped', + CWCM: 'Changes_When_Casemapped', + Changes_When_Lowercased: 'Changes_When_Lowercased', + CWL: 'Changes_When_Lowercased', + Changes_When_NFKC_Casefolded: 'Changes_When_NFKC_Casefolded', + CWKCF: 'Changes_When_NFKC_Casefolded', + Changes_When_Titlecased: 'Changes_When_Titlecased', + CWT: 'Changes_When_Titlecased', + Changes_When_Uppercased: 'Changes_When_Uppercased', + CWU: 'Changes_When_Uppercased', + Dash: 'Dash', + Default_Ignorable_Code_Point: 'Default_Ignorable_Code_Point', + DI: 'Default_Ignorable_Code_Point', + Deprecated: 'Deprecated', + Dep: 'Deprecated', + Diacritic: 'Diacritic', + Dia: 'Diacritic', + Emoji: 'Emoji', + Emoji_Component: 'Emoji_Component', + EComp: 'Emoji_Component', + Emoji_Modifier: 'Emoji_Modifier', + EMod: 'Emoji_Modifier', + Emoji_Modifier_Base: 'Emoji_Modifier_Base', + EBase: 'Emoji_Modifier_Base', + Emoji_Presentation: 'Emoji_Presentation', + EPres: 'Emoji_Presentation', + Extended_Pictographic: 'Extended_Pictographic', + ExtPict: 'Extended_Pictographic', + Extender: 'Extender', + Ext: 'Extender', + Grapheme_Base: 'Grapheme_Base', + Gr_Base: 'Grapheme_Base', + Grapheme_Extend: 'Grapheme_Extend', + Gr_Ext: 'Grapheme_Extend', + Hex_Digit: 'Hex_Digit', + Hex: 'Hex_Digit', + IDS_Binary_Operator: 'IDS_Binary_Operator', + IDSB: 'IDS_Binary_Operator', + IDS_Trinary_Operator: 'IDS_Trinary_Operator', + IDST: 'IDS_Trinary_Operator', + ID_Continue: 'ID_Continue', + IDC: 'ID_Continue', + ID_Start: 'ID_Start', + IDS: 'ID_Start', + Ideographic: 'Ideographic', + Ideo: 'Ideographic', + Join_Control: 'Join_Control', + Join_C: 'Join_Control', + Logical_Order_Exception: 'Logical_Order_Exception', + LOE: 'Logical_Order_Exception', + Lowercase: 'Lowercase', + Lower: 'Lowercase', + Math: 'Math', + Noncharacter_Code_Point: 'Noncharacter_Code_Point', + NChar: 'Noncharacter_Code_Point', + Pattern_Syntax: 'Pattern_Syntax', + Pat_Syn: 'Pattern_Syntax', + Pattern_White_Space: 'Pattern_White_Space', + Pat_WS: 'Pattern_White_Space', + Quotation_Mark: 'Quotation_Mark', + QMark: 'Quotation_Mark', + Radical: 'Radical', + Regional_Indicator: 'Regional_Indicator', + RI: 'Regional_Indicator', + Sentence_Terminal: 'Sentence_Terminal', + STerm: 'Sentence_Terminal', + Soft_Dotted: 'Soft_Dotted', + SD: 'Soft_Dotted', + Terminal_Punctuation: 'Terminal_Punctuation', + Term: 'Terminal_Punctuation', + Unified_Ideograph: 'Unified_Ideograph', + UIdeo: 'Unified_Ideograph', + Uppercase: 'Uppercase', + Upper: 'Uppercase', + Variation_Selector: 'Variation_Selector', + VS: 'Variation_Selector', + White_Space: 'White_Space', + space: 'White_Space', + XID_Continue: 'XID_Continue', + XIDC: 'XID_Continue', + XID_Start: 'XID_Start', + XIDS: 'XID_Start', +}; + +// #table-unicode-general-category-values +export const UnicodeGeneralCategoryValues = { + __proto__: null, + Cased_Letter: 'Cased_Letter', + LC: 'Cased_Letter', + Close_Punctuation: 'Close_Punctuation', + Pe: 'Close_Punctuation', + Connector_Punctuation: 'Connector_Punctuation', + Pc: 'Connector_Punctuation', + Control: 'Control', + Cc: 'Control', + cntrl: 'Control', + Currency_Symbol: 'Currency_Symbol', + Sc: 'Currency_Symbol', + Dash_Punctuation: 'Dash_Punctuation', + Pd: 'Dash_Punctuation', + Decimal_Number: 'Decimal_Number', + Nd: 'Decimal_Number', + digit: 'Decimal_Number', + Enclosing_Mark: 'Enclosing_Mark', + Me: 'Enclosing_Mark', + Final_Punctuation: 'Final_Punctuation', + Pf: 'Final_Punctuation', + Format: 'Format', + Cf: 'Format', + Initial_Punctuation: 'Initial_Punctuation', + Pi: 'Initial_Punctuation', + Letter: 'Letter', + L: 'Letter', + Letter_Number: 'Letter_Number', + Nl: 'Letter_Number', + Line_Separator: 'Line_Separator', + Zl: 'Line_Separator', + Lowercase_Letter: 'Lowercase_Letter', + Ll: 'Lowercase_Letter', + Mark: 'Mark', + M: 'Mark', + Combining_Mark: 'Mark', + Math_Symbol: 'Math_Symbol', + Sm: 'Math_Symbol', + Modifier_Letter: 'Modifier_Letter', + Lm: 'Modifier_Letter', + Modifier_Symbol: 'Modifier_Symbol', + Sk: 'Modifier_Symbol', + Nonspacing_Mark: 'Nonspacing_Mark', + Mn: 'Nonspacing_Mark', + Number: 'Number', + N: 'Number', + Open_Punctuation: 'Open_Punctuation', + Ps: 'Open_Punctuation', + Other: 'Other', + C: 'Other', + Other_Letter: 'Other_Letter', + Lo: 'Other_Letter', + Other_Number: 'Other_Number', + No: 'Other_Number', + Other_Punctuation: 'Other_Punctuation', + Po: 'Other_Punctuation', + Other_Symbol: 'Other_Symbol', + So: 'Other_Symbol', + Paragraph_Separator: 'Paragraph_Separator', + Zp: 'Paragraph_Separator', + Private_Use: 'Private_Use', + Co: 'Private_Use', + Punctuation: 'Punctuation', + P: 'Punctuation', + punct: 'Punctuation', + Separator: 'Separator', + Z: 'Separator', + Space_Separator: 'Space_Separator', + Zs: 'Space_Separator', + Spacing_Mark: 'Spacing_Mark', + Mc: 'Spacing_Mark', + Surrogate: 'Surrogate', + Cs: 'Surrogate', + Symbol: 'Symbol', + S: 'Symbol', + Titlecase_Letter: 'Titlecase_Letter', + Lt: 'Titlecase_Letter', + Unassigned: 'Unassigned', + Cn: 'Unassigned', + Uppercase_Letter: 'Uppercase_Letter', + Lu: 'Uppercase_Letter', +}; + +// #table-unicode-script-values +export const UnicodeScriptValues = { + __proto__: null, + Adlam: 'Adlam', + Adlm: 'Adlam', + Ahom: 'Ahom', + Anatolian_Hieroglyphs: 'Anatolian_Hieroglyphs', + Hluw: 'Anatolian_Hieroglyphs', + Arabic: 'Arabic', + Arab: 'Arabic', + Armenian: 'Armenian', + Armn: 'Armenian', + Avestan: 'Avestan', + Avst: 'Avestan', + Balinese: 'Balinese', + Bali: 'Balinese', + Bamum: 'Bamum', + Bamu: 'Bamum', + Bassa_Vah: 'Bassa_Vah', + Bass: 'Bassa_Vah', + Batak: 'Batak', + Batk: 'Batak', + Bengali: 'Bengali', + Beng: 'Bengali', + Bhaiksuki: 'Bhaiksuki', + Bhks: 'Bhaiksuki', + Bopomofo: 'Bopomofo', + Bopo: 'Bopomofo', + Brahmi: 'Brahmi', + Brah: 'Brahmi', + Braille: 'Braille', + Brai: 'Braille', + Buginese: 'Buginese', + Bugi: 'Buginese', + Buhid: 'Buhid', + Buhd: 'Buhid', + Canadian_Aboriginal: 'Canadian_Aboriginal', + Cans: 'Canadian_Aboriginal', + Carian: 'Carian', + Cari: 'Carian', + Caucasian_Albanian: 'Caucasian_Albanian', + Aghb: 'Caucasian_Albanian', + Chakma: 'Chakma', + Cakm: 'Chakma', + Cham: 'Cham', + Chorasmian: 'Chorasmian', + Chrs: 'Chorasmian', + Cherokee: 'Cherokee', + Cher: 'Cherokee', + Common: 'Common', + Zyyy: 'Common', + Coptic: 'Coptic', + Copt: 'Coptic', + Qaac: 'Coptic', + Cuneiform: 'Cuneiform', + Xsux: 'Cuneiform', + Cypriot: 'Cypriot', + Cprt: 'Cypriot', + Cyrillic: 'Cyrillic', + Cyrl: 'Cyrillic', + Deseret: 'Deseret', + Dsrt: 'Deseret', + Devanagari: 'Devanagari', + Deva: 'Devanagari', + Dives_Akuru: 'Dives_Akuru', + Diak: 'Dives_Akuru', + Dogra: 'Dogra', + Dogr: 'Dogra', + Duployan: 'Duployan', + Dupl: 'Duployan', + Egyptian_Hieroglyphs: 'Egyptian_Hieroglyphs', + Egyp: 'Egyptian_Hieroglyphs', + Elbasan: 'Elbasan', + Elba: 'Elbasan', + Elymaic: 'Elymaic', + Elym: 'Elymaic', + Ethiopic: 'Ethiopic', + Ethi: 'Ethiopic', + Georgian: 'Georgian', + Geor: 'Georgian', + Glagolitic: 'Glagolitic', + Glag: 'Glagolitic', + Gothic: 'Gothic', + Goth: 'Gothic', + Grantha: 'Grantha', + Gran: 'Grantha', + Greek: 'Greek', + Grek: 'Greek', + Gujarati: 'Gujarati', + Gujr: 'Gujarati', + Gunjala_Gondi: 'Gunjala_Gondi', + Gong: 'Gunjala_Gondi', + Gurmukhi: 'Gurmukhi', + Guru: 'Gurmukhi', + Han: 'Han', + Hani: 'Han', + Hangul: 'Hangul', + Hang: 'Hangul', + Hanifi_Rohingya: 'Hanifi_Rohingya', + Rohg: 'Hanifi_Rohingya', + Hanunoo: 'Hanunoo', + Hano: 'Hanunoo', + Hatran: 'Hatran', + Hatr: 'Hatran', + Hebrew: 'Hebrew', + Hebr: 'Hebrew', + Hiragana: 'Hiragana', + Hira: 'Hiragana', + Imperial_Aramaic: 'Imperial_Aramaic', + Armi: 'Imperial_Aramaic', + Inherited: 'Inherited', + Zinh: 'Inherited', + Qaai: 'Inherited', + Inscriptional_Pahlavi: 'Inscriptional_Pahlavi', + Phli: 'Inscriptional_Pahlavi', + Inscriptional_Parthian: 'Inscriptional_Parthian', + Prti: 'Inscriptional_Parthian', + Javanese: 'Javanese', + Java: 'Javanese', + Kaithi: 'Kaithi', + Kthi: 'Kaithi', + Kannada: 'Kannada', + Knda: 'Kannada', + Katakana: 'Katakana', + Kana: 'Katakana', + Kayah_Li: 'Kayah_Li', + Kali: 'Kayah_Li', + Kharoshthi: 'Kharoshthi', + Khar: 'Kharoshthi', + Khitan_Small_Script: 'Khitan_Small_Script', + Kits: 'Khitan_Small_Script', + Khmer: 'Khmer', + Khmr: 'Khmer', + Khojki: 'Khojki', + Khoj: 'Khojki', + Khudawadi: 'Khudawadi', + Sind: 'Khudawadi', + Lao: 'Lao', + Laoo: 'Lao', + Latin: 'Latin', + Latn: 'Latin', + Lepcha: 'Lepcha', + Lepc: 'Lepcha', + Limbu: 'Limbu', + Limb: 'Limbu', + Linear_A: 'Linear_A', + Lina: 'Linear_A', + Linear_B: 'Linear_B', + Linb: 'Linear_B', + Lisu: 'Lisu', + Lycian: 'Lycian', + Lyci: 'Lycian', + Lydian: 'Lydian', + Lydi: 'Lydian', + Mahajani: 'Mahajani', + Mahj: 'Mahajani', + Makasar: 'Makasar', + Maka: 'Makasar', + Malayalam: 'Malayalam', + Mlym: 'Malayalam', + Mandaic: 'Mandaic', + Mand: 'Mandaic', + Manichaean: 'Manichaean', + Mani: 'Manichaean', + Marchen: 'Marchen', + Marc: 'Marchen', + Medefaidrin: 'Medefaidrin', + Medf: 'Medefaidrin', + Masaram_Gondi: 'Masaram_Gondi', + Gonm: 'Masaram_Gondi', + Meetei_Mayek: 'Meetei_Mayek', + Mtei: 'Meetei_Mayek', + Mende_Kikakui: 'Mende_Kikakui', + Mend: 'Mende_Kikakui', + Meroitic_Cursive: 'Meroitic_Cursive', + Merc: 'Meroitic_Cursive', + Meroitic_Hieroglyphs: 'Meroitic_Hieroglyphs', + Mero: 'Meroitic_Hieroglyphs', + Miao: 'Miao', + Plrd: 'Miao', + Modi: 'Modi', + Mongolian: 'Mongolian', + Mong: 'Mongolian', + Mro: 'Mro', + Mroo: 'Mro', + Multani: 'Multani', + Mult: 'Multani', + Myanmar: 'Myanmar', + Mymr: 'Myanmar', + Nabataean: 'Nabataean', + Nbat: 'Nabataean', + Nandinagari: 'Nandinagari', + Nand: 'Nandinagari', + New_Tai_Lue: 'New_Tai_Lue', + Talu: 'New_Tai_Lue', + Newa: 'Newa', + Nko: 'Nko', + Nkoo: 'Nko', + Nushu: 'Nushu', + Nshu: 'Nushu', + Nyiakeng_Puachue_Hmong: 'Nyiakeng_Puachue_Hmong', + Hmnp: 'Nyiakeng_Puachue_Hmong', + Ogham: 'Ogham', + Ogam: 'Ogham', + Ol_Chiki: 'Ol_Chiki', + Olck: 'Ol_Chiki', + Old_Hungarian: 'Old_Hungarian', + Hung: 'Old_Hungarian', + Old_Italic: 'Old_Italic', + Ital: 'Old_Italic', + Old_North_Arabian: 'Old_North_Arabian', + Narb: 'Old_North_Arabian', + Old_Permic: 'Old_Permic', + Perm: 'Old_Permic', + Old_Persian: 'Old_Persian', + Xpeo: 'Old_Persian', + Old_Sogdian: 'Old_Sogdian', + Sogo: 'Old_Sogdian', + Old_South_Arabian: 'Old_South_Arabian', + Sarb: 'Old_South_Arabian', + Old_Turkic: 'Old_Turkic', + Orkh: 'Old_Turkic', + Oriya: 'Oriya', + Orya: 'Oriya', + Osage: 'Osage', + Osge: 'Osage', + Osmanya: 'Osmanya', + Osma: 'Osmanya', + Pahawh_Hmong: 'Pahawh_Hmong', + Hmng: 'Pahawh_Hmong', + Palmyrene: 'Palmyrene', + Palm: 'Palmyrene', + Pau_Cin_Hau: 'Pau_Cin_Hau', + Pauc: 'Pau_Cin_Hau', + Phags_Pa: 'Phags_Pa', + Phag: 'Phags_Pa', + Phoenician: 'Phoenician', + Phnx: 'Phoenician', + Psalter_Pahlavi: 'Psalter_Pahlavi', + Phlp: 'Psalter_Pahlavi', + Rejang: 'Rejang', + Rjng: 'Rejang', + Runic: 'Runic', + Runr: 'Runic', + Samaritan: 'Samaritan', + Samr: 'Samaritan', + Saurashtra: 'Saurashtra', + Saur: 'Saurashtra', + Sharada: 'Sharada', + Shrd: 'Sharada', + Shavian: 'Shavian', + Shaw: 'Shavian', + Siddham: 'Siddham', + Sidd: 'Siddham', + SignWriting: 'SignWriting', + Sgnw: 'SignWriting', + Sinhala: 'Sinhala', + Sinh: 'Sinhala', + Sogdian: 'Sogdian', + Sogd: 'Sogdian', + Sora_Sompeng: 'Sora_Sompeng', + Sora: 'Sora_Sompeng', + Soyombo: 'Soyombo', + Soyo: 'Soyombo', + Sundanese: 'Sundanese', + Sund: 'Sundanese', + Syloti_Nagri: 'Syloti_Nagri', + Sylo: 'Syloti_Nagri', + Syriac: 'Syriac', + Syrc: 'Syriac', + Tagalog: 'Tagalog', + Tglg: 'Tagalog', + Tagbanwa: 'Tagbanwa', + Tagb: 'Tagbanwa', + Tai_Le: 'Tai_Le', + Tale: 'Tai_Le', + Tai_Tham: 'Tai_Tham', + Lana: 'Tai_Tham', + Tai_Viet: 'Tai_Viet', + Tavt: 'Tai_Viet', + Takri: 'Takri', + Takr: 'Takri', + Tamil: 'Tamil', + Taml: 'Tamil', + Tangut: 'Tangut', + Tang: 'Tangut', + Telugu: 'Telugu', + Telu: 'Telugu', + Thaana: 'Thaana', + Thaa: 'Thaana', + Thai: 'Thai', + Tibetan: 'Tibetan', + Tibt: 'Tibetan', + Tifinagh: 'Tifinagh', + Tfng: 'Tifinagh', + Tirhuta: 'Tirhuta', + Tirh: 'Tirhuta', + Ugaritic: 'Ugaritic', + Ugar: 'Ugaritic', + Vai: 'Vai', + Vaii: 'Vai', + Wancho: 'Wancho', + Wcho: 'Wancho', + Warang_Citi: 'Warang_Citi', + Wara: 'Warang_Citi', + Yezidi: 'Yezidi', + Yezi: 'Yezidi', + Yi: 'Yi', + Yiii: 'Yi', + Zanabazar_Square: 'Zanabazar_Square', + Zanb: 'Zanabazar_Square', +}; + +// #sec-runtime-semantics-unicodematchproperty-p +export function UnicodeMatchProperty(p) { + // 1. Assert: p is a List of Unicode code points that is identical to a List of Unicode code points that is a Unicode property name or property alias listed in the “Property name and aliases” column of Table 55 or Table 56. + Assert(p in NonbinaryUnicodeProperties || p in BinaryUnicodeProperties); + // 2. Let c be the canonical property name of p as given in the “Canonical property name” column of the corresponding row. + const c = NonbinaryUnicodeProperties[p] || BinaryUnicodeProperties[p]; + // 3. Return the List of Unicode code points of c. + return c; +} + +// #sec-runtime-semantics-unicodematchpropertyvalue-p-v +export function UnicodeMatchPropertyValue(p, v) { + // 1. Assert: p is a List of Unicode code points that is identical to a List of Unicode code points that is a canonical, unaliased Unicode property name listed in the “Canonical property name” column of Table 55. + Assert(p in NonbinaryUnicodeProperties); + // 2. Assert: v is a List of Unicode code points that is identical to a List of Unicode code points that is a property value or property value alias for Unicode property p listed in the “Property value and aliases” column of Table 57 or Table 58. + // Assert(v in UnicodeGeneralCategoryValues || v in UnicodeScriptValues); + // 3. Let value be the canonical property value of v as given in the “Canonical property value” column of the corresponding row. + const value = UnicodeGeneralCategoryValues[v] || UnicodeScriptValues[v]; + // 4. Return the List of Unicode code points of value. + return value; +} + +export function getUnicodePropertyValueSet(_property, _value) { + // FIXME: figure out bundling unicode properties + // const path = value ? `${property}/${value}` : `Binary_Property/${property}`; + // return new Set(require(`unicode-13.0.0/${path}/symbols.js`)); + return new Set(); +} diff --git a/engine262/src/runtime-semantics/UpdateExpression.mjs b/engine262/src/runtime-semantics/UpdateExpression.mjs new file mode 100644 index 0000000..f38a43e --- /dev/null +++ b/engine262/src/runtime-semantics/UpdateExpression.mjs @@ -0,0 +1,77 @@ +import { Evaluate } from '../evaluator.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { + GetValue, + PutValue, + ToNumeric, +} from '../abstract-ops/all.mjs'; +import { TypeNumeric } from '../value.mjs'; +import { Q, X } from '../completion.mjs'; + +// UpdateExpression : +// LeftHandSideExpression `++` +// LeftHandSideExpression `--` +// `++` UnaryExpression +// `--` UnaryExpression +export function* Evaluate_UpdateExpression({ LeftHandSideExpression, operator, UnaryExpression }) { + switch (true) { + // UpdateExpression : LeftHandSideExpression `++` + case operator === '++' && !!LeftHandSideExpression: { + // 1. Let lhs be the result of evaluating LeftHandSideExpression. + const lhs = yield* Evaluate(LeftHandSideExpression); + // 2. Let oldValue be ? ToNumeric(? GetValue(lhs)). + const oldValue = Q(ToNumeric(Q(GetValue(lhs)))); + // 3. Let newValue be ! Type(oldvalue)::add(oldValue, Type(oldValue)::unit). + const newValue = X(TypeNumeric(oldValue).add(oldValue, TypeNumeric(oldValue).unit)); + // 4. Perform ? PutValue(lhs, newValue). + Q(PutValue(lhs, newValue)); + // 5. Return oldValue. + return oldValue; + } + + // UpdateExpression : LeftHandSideExpression `--` + case operator === '--' && !!LeftHandSideExpression: { + // 1. Let lhs be the result of evaluating LeftHandSideExpression. + const lhs = yield* Evaluate(LeftHandSideExpression); + // 2. Let oldValue be ? ToNumeric(? GetValue(lhs)). + const oldValue = Q(ToNumeric(Q(GetValue(lhs)))); + // 3. Let newValue be ! Type(oldvalue)::subtract(oldValue, Type(oldValue)::unit). + const newValue = X(TypeNumeric(oldValue).subtract(oldValue, TypeNumeric(oldValue).unit)); + // 4. Perform ? PutValue(lhs, newValue). + Q(PutValue(lhs, newValue)); + // 5. Return oldValue. + return oldValue; + } + + // UpdateExpression : `++` UnaryExpression + case operator === '++' && !!UnaryExpression: { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(ToNumeric(Q(GetValue(expr)))); + // 3. Let newValue be ! Type(oldvalue)::add(oldValue, Type(oldValue)::unit). + const newValue = X(TypeNumeric(oldValue).add(oldValue, TypeNumeric(oldValue).unit)); + // 4. Perform ? PutValue(expr, newValue). + Q(PutValue(expr, newValue)); + // 5. Return newValue. + return newValue; + } + + // UpdateExpression : `--` UnaryExpression + case operator === '--' && !!UnaryExpression: { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = yield* Evaluate(UnaryExpression); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(ToNumeric(Q(GetValue(expr)))); + // 3. Let newValue be ! Type(oldvalue)::subtract(oldValue, Type(oldValue)::unit). + const newValue = X(TypeNumeric(oldValue).subtract(oldValue, TypeNumeric(oldValue).unit)); + // 4. Perform ? PutValue(expr, newValue). + Q(PutValue(expr, newValue)); + // 5. Return newValue. + return newValue; + } + + default: + throw new OutOfRange('Evaluate_UpdateExpression', operator); + } +} diff --git a/engine262/src/runtime-semantics/VariableStatement.mjs b/engine262/src/runtime-semantics/VariableStatement.mjs new file mode 100644 index 0000000..a7982a9 --- /dev/null +++ b/engine262/src/runtime-semantics/VariableStatement.mjs @@ -0,0 +1,69 @@ +import { + GetValue, + PutValue, + ResolveBinding, +} from '../abstract-ops/all.mjs'; +import { NormalCompletion, Q, ReturnIfAbrupt } from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { StringValue, IsAnonymousFunctionDefinition } from '../static-semantics/all.mjs'; +import { Value } from '../value.mjs'; +import { NamedEvaluation, BindingInitialization } from './all.mjs'; + +// 13.3.2.4 #sec-variable-statement-runtime-semantics-evaluation +// VariableDeclaration : +// BindingIdentifier +// BindingIdentifier Initializer +// BindingPattern Initializer +function* Evaluate_VariableDeclaration({ BindingIdentifier, Initializer, BindingPattern }) { + if (BindingIdentifier) { + if (!Initializer) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(BindingIdentifier); + // 2. Let lhs be ? ResolveBinding(bindingId). + const lhs = Q(ResolveBinding(bindingId, undefined, BindingIdentifier.strict)); + // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + let value; + if (IsAnonymousFunctionDefinition(Initializer)) { + // a. Let value be NamedEvaluation of Initializer with argument bindingId. + value = yield* NamedEvaluation(Initializer, bindingId); + } else { // 4. Else, + // a. Let rhs be the result of evaluating Initializer. + const rhs = yield* Evaluate(Initializer); + // b. Let value be ? GetValue(rhs). + value = Q(GetValue(rhs)); + } + // 5. Return ? PutValue(lhs, value). + return Q(PutValue(lhs, value)); + } + // 1. Let rhs be the result of evaluating Initializer. + const rhs = yield* Evaluate(Initializer); + // 2. Let rval be ? GetValue(rhs). + const rval = Q(GetValue(rhs)); + // 3. Return the result of performing BindingInitialization for BindingPattern passing rval and undefined as arguments. + return yield* BindingInitialization(BindingPattern, rval, Value.undefined); +} + +// 13.3.2.4 #sec-variable-statement-runtime-semantics-evaluation +// VariableDeclarationList : VariableDeclarationList `,` VariableDeclaration +// +// (implicit) +// VariableDeclarationList : VariableDeclaration +export function* Evaluate_VariableDeclarationList(VariableDeclarationList) { + let next; + for (const VariableDeclaration of VariableDeclarationList) { + next = yield* Evaluate_VariableDeclaration(VariableDeclaration); + ReturnIfAbrupt(next); + } + return next; +} + +// 13.3.2.4 #sec-variable-statement-runtime-semantics-evaluation +// VariableStatement : `var` VariableDeclarationList `;` +export function* Evaluate_VariableStatement({ VariableDeclarationList }) { + const next = yield* Evaluate_VariableDeclarationList(VariableDeclarationList); + ReturnIfAbrupt(next); + return NormalCompletion(undefined); +} diff --git a/engine262/src/runtime-semantics/WithStatement.mjs b/engine262/src/runtime-semantics/WithStatement.mjs new file mode 100644 index 0000000..8919562 --- /dev/null +++ b/engine262/src/runtime-semantics/WithStatement.mjs @@ -0,0 +1,34 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Value } from '../value.mjs'; +import { ToObject, GetValue } from '../abstract-ops/all.mjs'; +import { Evaluate } from '../evaluator.mjs'; +import { NewObjectEnvironment } from '../environment.mjs'; +import { + UpdateEmpty, + Completion, + EnsureCompletion, + Q, +} from '../completion.mjs'; + +// #sec-with-statement-runtime-semantics-evaluation +// WithStatement : `with` `(` Expression `)` Statement +export function* Evaluate_WithStatement({ Expression, Statement }) { + // 1. Let val be the result of evaluating Expression. + const val = yield* Evaluate(Expression); + // 2. Let obj be ? ToObject(? GetValue(val)). + const obj = Q(ToObject(Q(GetValue(val)))); + // 3. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let newEnv be NewObjectEnvironment(obj, oldEnv). + const newEnv = NewObjectEnvironment(obj, oldEnv); + // 5. Set the withEnvironment flag of newEnv's EnvironmentRecord to true. + newEnv.withEnvironment = true; + // 6. Set the running execution context's LexicalEnvironment to newEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = newEnv; + // 7. Let C be the result of evaluating Statement. + const C = EnsureCompletion(yield* Evaluate(Statement)); + // 8. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 9. Return Completion(UpdateEmpty(C, undefined)). + return Completion(UpdateEmpty(C, Value.undefined)); +} diff --git a/engine262/src/runtime-semantics/YieldExpression.mjs b/engine262/src/runtime-semantics/YieldExpression.mjs new file mode 100644 index 0000000..c6beb4d --- /dev/null +++ b/engine262/src/runtime-semantics/YieldExpression.mjs @@ -0,0 +1,178 @@ +import { surroundingAgent } from '../engine.mjs'; +import { Type, Value } from '../value.mjs'; +import { + Assert, + Call, + CreateIterResultObject, + GeneratorYield, + GetGeneratorKind, + GetIterator, + GetMethod, + GetValue, + IteratorClose, + IteratorComplete, + IteratorValue, + AsyncGeneratorYield, + AsyncIteratorClose, +} from '../abstract-ops/all.mjs'; +import { + Await, + Completion, + NormalCompletion, + EnsureCompletion, + Q, X, +} from '../completion.mjs'; +import { Evaluate } from '../evaluator.mjs'; + +// #sec-generator-function-definitions-runtime-semantics-evaluation +// YieldExpression : +// `yield` +// `yield` AssignmentExpression +// `yield` `*` AssignmentExpression +export function* Evaluate_YieldExpression({ hasStar, AssignmentExpression }) { + // 1. Let generatorKind be ! GetGeneratorKind(). + const generatorKind = X(GetGeneratorKind()); + if (hasStar) { + // 2. Let exprRef be the result of evaluating AssignmentExpression. + const exprRef = yield* Evaluate(AssignmentExpression); + // 3. Let value be ? GetValue(exprRef). + const value = Q(GetValue(exprRef)); + // 4. Let iteratorRecord be ? GetIterator(value, generatorKind). + const iteratorRecord = Q(GetIterator(value, generatorKind)); + // 5. Let iterator be iteratorRecord.[[Iterator]]. + const iterator = iteratorRecord.Iterator; + // 6. Let received be NormalCompletion(undefined). + let received = NormalCompletion(Value.undefined); + // 7. Repeat, + while (true) { + // a. If received.[[Type]] is normal, then + if (received.Type === 'normal') { + // i. Let innerResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « received.[[Value]] »). + let innerResult = Q(Call(iteratorRecord.NextMethod, iteratorRecord.Iterator, [received.Value])); + // ii. If generatorKind is async, then set innerResult to ? Await(innerResult). + if (generatorKind === 'async') { + innerResult = Q(yield* Await(innerResult)); + } + // iii. If Type(innerResult) is not Object, throw a TypeError exception. + if (Type(innerResult) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult); + } + // iv. Let done be ? IteratorComplete(innerResult). + const done = Q(IteratorComplete(innerResult)); + // v. If done is true, then + if (done === Value.true) { + // 1. Return ? IteratorValue(innerResult). + return Q(IteratorValue(innerResult)); + } + // vi. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + if (generatorKind === 'async') { + received = yield* AsyncGeneratorYield(Q(IteratorValue(innerResult))); + } else { // vii. Else, set received to GeneratorYield(innerResult). + received = yield* GeneratorYield(innerResult); + } + } else if (received.Type === 'throw') { // b. Else if received.[[Type]] is throw, then + // i. Let throw be ? GetMethod(iterator, "throw"). + const thr = Q(GetMethod(iterator, new Value('throw'))); + // ii. If throw is not undefined, then + if (thr !== Value.undefined) { + // 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »). + let innerResult = Q(Call(thr, iterator, [received.Value])); + // 2. If generatorKind is async, then set innerResult to ? Await(innerResult). + if (generatorKind === 'async') { + innerResult = Q(yield* Await(innerResult)); + } + // 3. NOTE: Exceptions from the inner iterator throw method are propagated. Normal completions from an inner throw method are processed similarly to an inner next. + // 4. If Type(innerResult) is not Object, throw a TypeError exception. + if (Type(innerResult) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult); + } + // 5. Let done be ? IteratorComplete(innerResult). + const done = Q(IteratorComplete(innerResult)); + // 6. If done is true, then + if (done === Value.true) { + // a. Return ? IteratorValue(innerResult). + return Q(IteratorValue(innerResult)); + } + // 7. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + if (generatorKind === 'async') { + received = yield* AsyncGeneratorYield(Q(IteratorValue(innerResult))); + } else { // 8. Else, set received to GeneratorYield(innerResult). + received = yield* GeneratorYield(innerResult); + } + } else { // iii. Else, + // 1. NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop. But first we need to give iterator a chance to clean up. + // 2. Let closeCompletion be Completion { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }. + const closeCompletion = NormalCompletion(undefined); + // 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion). + // 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion). + if (generatorKind === 'async') { + Q(yield* AsyncIteratorClose(iteratorRecord, closeCompletion)); + } else { + Q(IteratorClose(iteratorRecord, closeCompletion)); + } + // 5. NOTE: The next step throws a TypeError to indicate that there was a yield* protocol violation: iterator does not have a throw method. + // 6. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'IteratorThrowMissing'); + } + } else { // c. Else, + // i. Assert: received.[[Type]] is return. + Assert(received.Type === 'return'); + // ii. Let return be ? GetMethod(iterator, "return"). + const ret = Q(GetMethod(iterator, new Value('return'))); + // iii. If return is undefined, then + if (ret === Value.undefined) { + // 1. If generatorKind is async, then set received.[[Value]] to ? Await(received.[[Value]]). + if (generatorKind === 'async') { + received.Value = Q(yield* Await(received.Value)); + } + // 2. Return Completion(received). + return Completion(received); + } + // iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »). + let innerReturnResult = Q(Call(ret, iterator, [received.Value])); + // v. If generatorKind is async, then set innerReturnResult to ? Await(innerReturnResult). + if (generatorKind === 'async') { + innerReturnResult = Q(yield* Await(innerReturnResult)); + } + // vi. If Type(innerReturnResult) is not Object, throw a TypeError exception. + if (Type(innerReturnResult) !== 'Object') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerReturnResult); + } + // vii. Let done be ? IteratorComplete(innerReturnResult). + const done = Q(IteratorComplete(innerReturnResult)); + // viii. If done is true, then + if (done === Value.true) { + // 1. Let value be ? IteratorValue(innerReturnResult). + const innerValue = Q(IteratorValue(innerReturnResult)); + // 2. Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: innerValue, Target: undefined }); + } + // ix. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + if (generatorKind === 'async') { + received = yield* AsyncGeneratorYield(Q(IteratorValue(innerReturnResult))); + } else { // ixx. Else, set received to GeneratorYield(innerResult). + received = yield* GeneratorYield(innerReturnResult); + } + } + received = EnsureCompletion(received); + } + } + if (AssignmentExpression) { + // 2. Let exprRef be the result of evaluating AssignmentExpression. + const exprRef = yield* Evaluate(AssignmentExpression); + // 3. Let value be ? GetValue(exprRef). + const value = Q(GetValue(exprRef)); + // 4. If generatorKind is async, then return ? AsyncGeneratorYield(value). + if (generatorKind === 'async') { + return Q(yield* AsyncGeneratorYield(value)); + } + // 5. Otherwise, return ? GeneratorYield(CreateIterResultObject(value, false)). + return Q(yield* GeneratorYield(CreateIterResultObject(value, Value.false))); + } + // 2. If generatorKind is async, then return ? AsyncGeneratorYield(undefined). + if (generatorKind === 'async') { + return Q(yield* AsyncGeneratorYield(Value.undefined)); + } + // 3. Otherwise, return ? GeneratorYield(CreateIterResultObject(undefined, false)). + return Q(yield* GeneratorYield(CreateIterResultObject(Value.undefined, Value.false))); +} diff --git a/engine262/src/runtime-semantics/all.mjs b/engine262/src/runtime-semantics/all.mjs new file mode 100644 index 0000000..daccea9 --- /dev/null +++ b/engine262/src/runtime-semantics/all.mjs @@ -0,0 +1,99 @@ +export * from './IdentifierReference.mjs'; +export * from './This.mjs'; +export * from './Literal.mjs'; +export * from './ClassExpression.mjs'; +export * from './ClassDefinitionEvaluation.mjs'; +export * from './DefineMethod.mjs'; +export * from './PropertyName.mjs'; +export * from './AdditiveExpression.mjs'; +export * from './AssignmentExpression.mjs'; +export * from './BitwiseOperators.mjs'; +export * from './CoalesceExpression.mjs'; +export * from './EmptyStatement.mjs'; +export * from './ExponentiationExpression.mjs'; +export * from './IfStatement.mjs'; +export * from './ImportCall.mjs'; +export * from './MultiplicativeExpression.mjs'; +export * from './ThrowStatement.mjs'; +export * from './UpdateExpression.mjs'; +export * from './GlobalDeclarationInstantiation.mjs'; +export * from './InstantiateFunctionObject.mjs'; +export * from './Script.mjs'; +export * from './ScriptBody.mjs'; +export * from './StatementList.mjs'; +export * from './ExpressionStatement.mjs'; +export * from './VariableStatement.mjs'; +export * from './FunctionDeclaration.mjs'; +export * from './CallExpression.mjs'; +export * from './EvaluateCall.mjs'; +export * from './ArgumentListEvaluation.mjs'; +export * from './EvaluateBody.mjs'; +export * from './FunctionDeclarationInstantiation.mjs'; +export * from './FunctionStatementList.mjs'; +export * from './IteratorBindingInitialization.mjs'; +export * from './ReturnStatement.mjs'; +export * from './ParenthesizedExpression.mjs'; +export * from './MemberExpression.mjs'; +export * from './EvaluatePropertyAccess.mjs'; +export * from './LexicalDeclaration.mjs'; +export * from './ObjectLiteral.mjs'; +export * from './PropertyDefinitionEvaluation.mjs'; +export * from './FunctionExpression.mjs'; +export * from './NamedEvaluation.mjs'; +export * from './TryStatement.mjs'; +export * from './Block.mjs'; +export * from './ArrayLiteral.mjs'; +export * from './UnaryExpression.mjs'; +export * from './EqualityExpression.mjs'; +export * from './LogicalANDExpression.mjs'; +export * from './LogicalORExpression.mjs'; +export * from './NewExpression.mjs'; +export * from './ShiftExpression.mjs'; +export * from './SuperCall.mjs'; +export * from './SuperProperty.mjs'; +export * from './BindingInitialization.mjs'; +export * from './AsyncFunctionExpression.mjs'; +export * from './RelationalExpression.mjs'; +export * from './BreakableStatement.mjs'; +export * from './LabelledEvaluation.mjs'; +export * from './TemplateLiteral.mjs'; +export * from './SwitchStatement.mjs'; +export * from './CreateDynamicFunction.mjs'; +export * from './GeneratorExpression.mjs'; +export * from './ArrowFunction.mjs'; +export * from './AsyncArrowFunction.mjs'; +export * from './BreakStatement.mjs'; +export * from './AsyncGeneratorExpression.mjs'; +export * from './HoistableDeclaration.mjs'; +export * from './CommaOperator.mjs'; +export * from './YieldExpression.mjs'; +export * from './StringIndexOf.mjs'; +export * from './NumberToBigInt.mjs'; +export * from './ConditionalExpression.mjs'; +export * from './RegularExpressionLiteral.mjs'; +export * from './RegExp.mjs'; +export * from './StringPad.mjs'; +export * from './TrimString.mjs'; +export * from './NewTarget.mjs'; +export * from './AwaitExpression.mjs'; +export * from './ClassDeclaration.mjs'; +export * from './WithStatement.mjs'; +export * from './Module.mjs'; +export * from './ModuleBody.mjs'; +export * from './ImportDeclaration.mjs'; +export * from './ExportDeclaration.mjs'; +export * from './OptionalExpression.mjs'; +export * from './TaggedTemplateExpression.mjs'; +export * from './GetSubstitution.mjs'; +export * from './ContinueStatement.mjs'; +export * from './LabelledStatement.mjs'; +export * from './MV.mjs'; +export * from './ApplyStringOrNumericBinaryOperator.mjs'; +export * from './EvaluateStringOrNumericBinaryExpression.mjs'; +export * from './ImportMeta.mjs'; +export * from './DebuggerStatement.mjs'; +export * from './PropertyBindingInitialization.mjs'; +export * from './KeyedBindingInitialization.mjs'; +export * from './DestructuringAssignmentEvaluation.mjs'; +export * from './RestBindingInitialization.mjs'; +export * from './Unicode.mjs'; diff --git a/engine262/src/static-semantics/BodyText.mjs b/engine262/src/static-semantics/BodyText.mjs new file mode 100644 index 0000000..caa0c86 --- /dev/null +++ b/engine262/src/static-semantics/BodyText.mjs @@ -0,0 +1,5 @@ +// #sec-static-semantics-bodytext +// RegularExpressionLiteral :: `/` RegularExpressionBody `/` RegularExpressionFlags +export function BodyText(RegularExpressionLiteral) { + return RegularExpressionLiteral.RegularExpressionBody; +} diff --git a/engine262/src/static-semantics/BoundNames.mjs b/engine262/src/static-semantics/BoundNames.mjs new file mode 100644 index 0000000..651060c --- /dev/null +++ b/engine262/src/static-semantics/BoundNames.mjs @@ -0,0 +1,99 @@ +import { OutOfRange } from '../helpers.mjs'; +import { StringValue } from './all.mjs'; + +export function BoundNames(node) { + if (Array.isArray(node)) { + const names = []; + for (const item of node) { + names.push(...BoundNames(item)); + } + return names; + } + switch (node.type) { + case 'BindingIdentifier': + return [StringValue(node)]; + case 'LexicalDeclaration': + return BoundNames(node.BindingList); + case 'LexicalBinding': + if (node.BindingIdentifier) { + return BoundNames(node.BindingIdentifier); + } + return BoundNames(node.BindingPattern); + case 'VariableStatement': + return BoundNames(node.VariableDeclarationList); + case 'VariableDeclaration': + if (node.BindingIdentifier) { + return BoundNames(node.BindingIdentifier); + } + return BoundNames(node.BindingPattern); + case 'ForDeclaration': + return BoundNames(node.ForBinding); + case 'ForBinding': + if (node.BindingIdentifier) { + return BoundNames(node.BindingIdentifier); + } + return BoundNames(node.BindingPattern); + case 'FunctionDeclaration': + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + case 'ClassDeclaration': + if (node.BindingIdentifier) { + return BoundNames(node.BindingIdentifier); + } + return ['default']; + case 'ImportSpecifier': + return BoundNames(node.ImportedBinding); + case 'ExportDeclaration': + if (node.FromClause || node.NamedExports) { + return []; + } + if (node.VariableStatement) { + return BoundNames(node.VariableStatement); + } + if (node.Declaration) { + return BoundNames(node.Declaration); + } + if (node.HoistableDeclaration) { + const declarationNames = BoundNames(node.HoistableDeclaration); + return declarationNames; + } + if (node.ClassDeclaration) { + const declarationNames = BoundNames(node.ClassDeclaration); + return declarationNames; + } + if (node.AssignmentExpression) { + return ['default']; + } + throw new OutOfRange('BoundNames', node); + case 'SingleNameBinding': + return BoundNames(node.BindingIdentifier); + case 'BindingRestElement': + if (node.BindingIdentifier) { + return BoundNames(node.BindingIdentifier); + } + return BoundNames(node.BindingPattern); + case 'BindingRestProperty': + return BoundNames(node.BindingIdentifier); + case 'BindingElement': + return BoundNames(node.BindingPattern); + case 'BindingProperty': + return BoundNames(node.BindingElement); + case 'ObjectBindingPattern': { + const names = BoundNames(node.BindingPropertyList); + if (node.BindingRestProperty) { + names.push(...BoundNames(node.BindingRestProperty)); + } + return names; + } + case 'ArrayBindingPattern': { + const names = BoundNames(node.BindingElementList); + if (node.BindingRestElement) { + names.push(...BoundNames(node.BindingRestElement)); + } + return names; + } + default: + return []; + } +} diff --git a/engine262/src/static-semantics/CharacterValue.mjs b/engine262/src/static-semantics/CharacterValue.mjs new file mode 100644 index 0000000..0bc6bac --- /dev/null +++ b/engine262/src/static-semantics/CharacterValue.mjs @@ -0,0 +1,91 @@ +import { OutOfRange } from '../helpers.mjs'; +import { UTF16SurrogatePairToCodePoint } from './all.mjs'; + +export function CharacterValue(node) { + switch (node.type) { + case 'CharacterEscape': + switch (true) { + case !!node.ControlEscape: + switch (node.ControlEscape) { + case 't': + return 0x0009; + case 'n': + return 0x000A; + case 'v': + return 0x000B; + case 'f': + return 0x000C; + case 'r': + return 0x000D; + default: + throw new OutOfRange('Evaluate_CharacterEscape', node); + } + case !!node.ControlLetter: { + // 1. Let ch be the code point matched by ControlLetter. + const ch = node.ControlLetter; + // 2. Let i be ch's code point value. + const i = ch.codePointAt(0); + // 3. Return the remainder of dividing i by 32. + return i % 32; + } + case !!node.HexEscapeSequence: + // 1. Return the numeric value of the code unit that is the SV of HexEscapeSequence. + return Number.parseInt(`${node.HexEscapeSequence.HexDigit_a}${node.HexEscapeSequence.HexDigit_b}`, 16); + case !!node.RegExpUnicodeEscapeSequence: + return CharacterValue(node.RegExpUnicodeEscapeSequence); + case node.subtype === '0': + // 1. Return the code point value of U+0000 (NULL). + return 0x0000; + case !!node.IdentityEscape: { + // 1. Let ch be the code point matched by IdentityEscape. + const ch = node.IdentityEscape.codePointAt(0); + // 2. Return the code point value of ch. + return ch; + } + default: + throw new OutOfRange('Evaluate_CharacterEscape', node); + } + case 'RegExpUnicodeEscapeSequence': + switch (true) { + case 'Hex4Digits' in node: + return node.Hex4Digits; + case 'CodePoint' in node: + return node.CodePoint; + case 'HexTrailSurrogate' in node: + return UTF16SurrogatePairToCodePoint(node.HexLeadSurrogate, node.HexTrailSurrogate); + case 'HexLeadSurrogate' in node: + return node.HexLeadSurrogate; + default: + throw new OutOfRange('Evaluate_CharacterEscape', node); + } + case 'ClassAtom': + switch (true) { + case node.value === '-': + // 1. Return the code point value of U+002D (HYPHEN-MINUS). + return 0x002D; + case !!node.SourceCharacter: { + // 1. Let ch be the code point matched by SourceCharacter. + const ch = node.SourceCharacter.codePointAt(0); + // 2. Return ch. + return ch; + } + default: + throw new OutOfRange('CharacterValue', node); + } + case 'ClassEscape': + switch (true) { + case node.value === 'b': + // 1. Return the code point value of U+0008 (BACKSPACE). + return 0x0008; + case node.value === '-': + // 1. Return the code point value of U+002D (HYPHEN-MINUS). + return 0x002D; + case !!node.CharacterEscape: + return CharacterValue(node.CharacterEscape); + default: + throw new OutOfRange('CharacterValue', node); + } + default: + throw new OutOfRange('CharacterValue', node); + } +} diff --git a/engine262/src/static-semantics/CodePointAt.mjs b/engine262/src/static-semantics/CodePointAt.mjs new file mode 100644 index 0000000..6ab9361 --- /dev/null +++ b/engine262/src/static-semantics/CodePointAt.mjs @@ -0,0 +1,53 @@ +import { Assert } from '../abstract-ops/all.mjs'; +import { X } from '../completion.mjs'; +import { isLeadingSurrogate, isTrailingSurrogate } from '../parser/Lexer.mjs'; +import { UTF16SurrogatePairToCodePoint } from './all.mjs'; + +// #sec-codepointat +export function CodePointAt(string, position) { + // 1 .Let size be the length of string. + const size = string.length; + // 2. Assert: position ≥ 0 and position < size. + Assert(position >= 0 && position < size); + // 3. Let first be the code unit at index position within string. + const first = string.charCodeAt(position); + // 4. Let cp be the code point whose numeric value is that of first. + let cp = first; + // 5. If first is not a leading surrogate or trailing surrogate, then + if (!isLeadingSurrogate(first) && !isTrailingSurrogate(first)) { + // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }. + return { + CodePoint: cp, + CodeUnitCount: 1, + IsUnpairedSurrogate: false, + }; + } + // 6. If first is a trailing surrogate or position + 1 = size, then + if (isTrailingSurrogate(first) || position + 1 === size) { + // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. + return { + CodePoint: cp, + CodeUnitCount: 1, + IsUnpairedSurrogate: true, + }; + } + // 7. Let second be the code unit at index position + 1 within string. + const second = string.charCodeAt(position + 1); + // 8. If seconds is not a trailing surrogate, then + if (!isTrailingSurrogate(second)) { + // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. + return { + CodePoint: cp, + CodeUnitCount: 1, + IsUnpairedSurrogate: true, + }; + } + // 9. Set cp to ! UTF16SurrogatePairToCodePoint(first, second). + cp = X(UTF16SurrogatePairToCodePoint(first, second)); + // 10. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }. + return { + CodePoint: cp, + CodeUnitCount: 2, + IsUnpairedSurrogate: false, + }; +} diff --git a/engine262/src/static-semantics/CodePointToUTF16CodeUnits.mjs b/engine262/src/static-semantics/CodePointToUTF16CodeUnits.mjs new file mode 100644 index 0000000..0fa48aa --- /dev/null +++ b/engine262/src/static-semantics/CodePointToUTF16CodeUnits.mjs @@ -0,0 +1,17 @@ +import { Assert } from '../abstract-ops/all.mjs'; + +// #sec-codepointtoutf16codeunits +export function CodePointToUTF16CodeUnits(cp) { + // 1. Assert: 0 ≤ cp ≤ 0x10FFFF. + Assert(cp >= 0 && cp <= 0x10FFFF); + // 2. If cp ≤ 0xFFFF, return cp. + if (cp <= 0xFFFF) { + return [cp]; + } + // 3. Let cu1 be floor((cp - 0x10000) / 0x400) + 0xD800. + const cu1 = Math.floor((cp - 0x10000) / 0x400) + 0xD800; + // 4. Let cu2 be ((cp - 0x10000) modulo 0x400) + 0xDC00. + const cu2 = ((cp - 0x10000) % 0x400) + 0xDC00; + // 5. Return the code unit sequence consisting of cu1 followed by cu2. + return [cu1, cu2]; +} diff --git a/engine262/src/static-semantics/CodePointsToString.mjs b/engine262/src/static-semantics/CodePointsToString.mjs new file mode 100644 index 0000000..5016848 --- /dev/null +++ b/engine262/src/static-semantics/CodePointsToString.mjs @@ -0,0 +1,15 @@ +import { X } from '../completion.mjs'; +import { CodePointToUTF16CodeUnits } from './all.mjs'; + +// #sec-codepointstostring +export function CodePointsToString(text) { + // 1. Let result be the empty String. + let result = ''; + // 2. For each code point cp in text, do + for (const cp of text) { + // a. Set result to the string-concatenation of result and ! CodePointToUTF16CodeUnits(cp). + result += X(CodePointToUTF16CodeUnits(cp)).map((c) => String.fromCodePoint(c)).join(''); + } + // 3. Return result. + return result; +} diff --git a/engine262/src/static-semantics/ConstructorMethod.mjs b/engine262/src/static-semantics/ConstructorMethod.mjs new file mode 100644 index 0000000..01507b2 --- /dev/null +++ b/engine262/src/static-semantics/ConstructorMethod.mjs @@ -0,0 +1,9 @@ +import { PropName } from './all.mjs'; + +// #sec-static-semantics-constructormethod +// ClassElementList : +// ClassElement +// ClassElementList ClassElement +export function ConstructorMethod(ClassElementList) { + return ClassElementList.find((ClassElement) => ClassElement.static === false && PropName(ClassElement) === 'constructor'); +} diff --git a/engine262/src/static-semantics/ContainsExpression.mjs b/engine262/src/static-semantics/ContainsExpression.mjs new file mode 100644 index 0000000..f3bd726 --- /dev/null +++ b/engine262/src/static-semantics/ContainsExpression.mjs @@ -0,0 +1,56 @@ +import { OutOfRange } from '../helpers.mjs'; + +export function ContainsExpression(node) { + if (Array.isArray(node)) { + for (const n of node) { + if (ContainsExpression(n)) { + return true; + } + } + return false; + } + switch (node.type) { + case 'SingleNameBinding': + return !!node.Initializer; + case 'BindingElement': + if (ContainsExpression(node.BindingPattern)) { + return true; + } + return !!node.Initializer; + case 'ObjectBindingPattern': + if (ContainsExpression(node.BindingPropertyList)) { + return true; + } + if (node.BindingRestProperty) { + return ContainsExpression(node.BindingRestProperty); + } + return false; + case 'BindingProperty': + if (node.PropertyName && node.PropertyName.ComputedPropertyName) { + return true; + } + return ContainsExpression(node.BindingElement); + case 'BindingRestProperty': + if (node.BindingIdentifier) { + return false; + } + return ContainsExpression(node.BindingPattern); + case 'ArrayBindingPattern': + if (ContainsExpression(node.BindingElementList)) { + return true; + } + if (node.BindingRestElement) { + return ContainsExpression(node.BindingRestElement); + } + return false; + case 'BindingRestElement': + if (node.BindingIdentifier) { + return false; + } + return ContainsExpression(node.BindingPattern); + case 'Elision': + return false; + default: + throw new OutOfRange('ContainsExpression', node); + } +} diff --git a/engine262/src/static-semantics/DeclarationPart.mjs b/engine262/src/static-semantics/DeclarationPart.mjs new file mode 100644 index 0000000..cbded7a --- /dev/null +++ b/engine262/src/static-semantics/DeclarationPart.mjs @@ -0,0 +1,3 @@ +export function DeclarationPart(node) { + return node; +} diff --git a/engine262/src/static-semantics/ExpectedArgumentCount.mjs b/engine262/src/static-semantics/ExpectedArgumentCount.mjs new file mode 100644 index 0000000..8ac9811 --- /dev/null +++ b/engine262/src/static-semantics/ExpectedArgumentCount.mjs @@ -0,0 +1,25 @@ +import { HasInitializer } from './all.mjs'; + +export function ExpectedArgumentCount(FormalParameterList) { + if (FormalParameterList.length === 0) { + return 0; + } + + let count = 0; + for (const FormalParameter of FormalParameterList.slice(0, -1)) { + const BindingElement = FormalParameter; + if (HasInitializer(BindingElement)) { + return count; + } + count += 1; + } + + const last = FormalParameterList[FormalParameterList.length - 1]; + if (last.type === 'BindingRestElement') { + return count; + } + if (HasInitializer(last)) { + return count; + } + return count + 1; +} diff --git a/engine262/src/static-semantics/ExportEntries.mjs b/engine262/src/static-semantics/ExportEntries.mjs new file mode 100644 index 0000000..6d1050b --- /dev/null +++ b/engine262/src/static-semantics/ExportEntries.mjs @@ -0,0 +1,119 @@ +import { Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { BoundNames, ModuleRequests, ExportEntriesForModule } from './all.mjs'; + +export function ExportEntries(node) { + if (Array.isArray(node)) { + const entries = []; + node.forEach((n) => { + entries.push(...ExportEntries(n)); + }); + return entries; + } + switch (node.type) { + case 'Module': + if (!node.ModuleBody) { + return []; + } + return ExportEntries(node.ModuleBody); + case 'ModuleBody': + return ExportEntries(node.ModuleItemList); + case 'ExportDeclaration': + switch (true) { + case !!node.ExportFromClause && !!node.FromClause: { + // `export` ExportFromClause FromClause `;` + // 1. Let module be the sole element of ModuleRequests of FromClause. + const module = ModuleRequests(node.FromClause)[0]; + // 2. Return ExportEntriesForModule(ExportFromClause, module). + return ExportEntriesForModule(node.ExportFromClause, module); + } + case !!node.NamedExports: { + // `export` NamedExports `;` + // 1. Return ExportEntriesForModule(NamedExports, null). + return ExportEntriesForModule(node.NamedExports, Value.null); + } + case !!node.VariableStatement: { + // `export` VariableStatement + // 1. Let entries be a new empty List. + const entries = []; + // 2. Let names be the BoundNames of VariableStatement. + const names = BoundNames(node.VariableStatement); + // 3. For each name in names, do + for (const name of names) { + // a. Append the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: name, [[ExportName]]: name } to entries. + entries.push({ + ModuleRequest: Value.null, + ImportName: Value.null, + LocalName: name, + ExportName: name, + }); + } + // 4. Return entries. + return entries; + } + case !!node.Declaration: { + // `export` Declaration + // 1. Let entries be a new empty List. + const entries = []; + // 2. Let names be the BoundNames of Declaration. + const names = BoundNames(node.Declaration); + // 3. For each name in names, do + for (const name of names) { + // a. Append the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: name, [[ExportName]]: name } to entries. + entries.push({ + ModuleRequest: Value.null, + ImportName: Value.null, + LocalName: name, + ExportName: name, + }); + } + // 4. Return entries. + return entries; + } + case node.default && !!node.HoistableDeclaration: { + // `export` `default` HoistableDeclaration + // 1. Let names be BoundNames of HoistableDeclaration. + const names = BoundNames(node.HoistableDeclaration); + // 2. Let localName be the sole element of names. + const localName = names[0]; + // 3. Return a new List containing the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default" }. + return [{ + ModuleRequest: Value.null, + ImportName: Value.null, + LocalName: localName, + ExportName: new Value('default'), + }]; + } + case node.default && !!node.ClassDeclaration: { + // `export` `default` ClassDeclaration + // 1. Let names be BoundNames of ClassDeclaration. + const names = BoundNames(node.ClassDeclaration); + // 2. Let localName be the sole element of names. + const localName = names[0]; + // 3. Return a new List containing the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default" }. + return [{ + ModuleRequest: Value.null, + ImportName: Value.null, + LocalName: localName, + ExportName: new Value('default'), + }]; + } + case node.default && !!node.AssignmentExpression: { + // `export` `default` AssignmentExpression `;` + // 1. Let entry be the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: ~default~, [[ExportName]]: "default" }. + const entry = { + ModuleRequest: Value.null, + ImportName: Value.null, + LocalName: 'default', + ExportName: new Value('default'), + }; + // 2. Return a new List containing entry. + return [entry]; + } + default: + throw new OutOfRange('ExportEntries', node); + } + default: + return []; + } +} diff --git a/engine262/src/static-semantics/ExportEntriesForModule.mjs b/engine262/src/static-semantics/ExportEntriesForModule.mjs new file mode 100644 index 0000000..fad9d6f --- /dev/null +++ b/engine262/src/static-semantics/ExportEntriesForModule.mjs @@ -0,0 +1,131 @@ +import { Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { StringValue } from './all.mjs'; + +export function ExportEntriesForModule(node, module) { + if (Array.isArray(node)) { + const specs = []; + node.forEach((n) => { + specs.push(...ExportEntriesForModule(n, module)); + }); + return specs; + } + switch (node.type) { + case 'ExportFromClause': + if (node.IdentifierName) { + // 1. Let exportName be the StringValue of IdentifierName. + const exportName = StringValue(node.IdentifierName); + // 2. Let entry be the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: ~star~, [[LocalName]]: null, [[ExportName]]: exportName }. + const entry = { + ModuleRequest: module, + ImportName: 'star', + LocalName: Value.null, + ExportName: exportName, + }; + // 3. Return a new List containing entry. + return [entry]; + } else if (node.ModuleExportName) { + // 1. Let exportName be the StringValue of ModuleExportName. + const exportName = StringValue(node.ModuleExportName); + // 2. Let entry be the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: ~star~, [[LocalName]]: null, [[ExportName]]: exportName }. + const entry = { + ModuleRequest: module, + ImportName: 'star', + LocalName: Value.null, + ExportName: exportName, + }; + // 3. Return a new List containing entry. + return [entry]; + } else { + // 1. Let entry be the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: ~star~, [[LocalName]]: null, [[ExportName]]: null }. + const entry = { + ModuleRequest: module, + ImportName: 'star', + LocalName: Value.null, + ExportName: Value.null, + }; + // 2. Return a new List containing entry. + return [entry]; + } + case 'ExportSpecifier': + switch (true) { + case !!node.IdentifierName && !!node.ModuleExportName: { + // 1. Let sourceName be the StringValue of IdentifierName. + const sourceName = StringValue(node.IdentifierName); + // 2. Let exportName be the StringValue of ModuleExportName. + const exportName = StringValue(node.ModuleExportName); + let localName; + let importName; + // 3. If module is null, then + if (module === Value.null) { + localName = sourceName; + importName = Value.null; + } else { // 4. Else, + localName = Value.null; + importName = sourceName; + } + // 5. Return a new List containing the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName, [[ExportName]]: exportName }. + return [{ + ModuleRequest: module, + ImportName: importName, + LocalName: localName, + ExportName: exportName, + }]; + } + case !!node.IdentifierName: { + // 1. Let sourceName be the StringValue of IdentifierName. + const sourceName = StringValue(node.IdentifierName); + let localName; + let importName; + // 2. If module is null, then + if (module === Value.null) { + // a. Let localName be sourceName. + localName = sourceName; + // b. Let importName be null. + importName = Value.null; + } else { // 3. Else, + // a. Let localName be null. + localName = Value.null; + // b. Let importName be sourceName. + importName = sourceName; + } + // 4. Return a new List containing the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName, [[ExportName]]: sourceName }. + return [{ + ModuleRequest: module, + ImportName: importName, + LocalName: localName, + ExportName: sourceName, + }]; + } + case !!node.IdentifierName_a && !!node.IdentifierName_b: { + // 1. Let sourceName be the StringValue of the first IdentifierName. + const sourceName = StringValue(node.IdentifierName_a); + // 2. Let exportName be the StringValue of the second IdentifierName. + const exportName = StringValue(node.IdentifierName_b); + let localName; + let importName; + // 3. If module is null, then + if (module === Value.null) { + localName = sourceName; + importName = Value.null; + } else { // 4. Else, + localName = Value.null; + importName = sourceName; + } + // 5. Return a new List containing the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName, [[ExportName]]: exportName }. + return [{ + ModuleRequest: module, + ImportName: importName, + LocalName: localName, + ExportName: exportName, + }]; + } + default: + throw new OutOfRange('ExportEntriesForModule', node); + } + case 'NamedExports': + return ExportEntriesForModule(node.ExportsList, module); + default: + throw new OutOfRange('ExportEntriesForModule', node); + } +} diff --git a/engine262/src/static-semantics/FlagText.mjs b/engine262/src/static-semantics/FlagText.mjs new file mode 100644 index 0000000..2207e3b --- /dev/null +++ b/engine262/src/static-semantics/FlagText.mjs @@ -0,0 +1,5 @@ +// #sec-static-semantics-flagtext +// RegularExpressionLiteral :: `/` RegularExpressionBody `/` RegularExpressionFlags +export function FlagText(RegularExpressionLiteral) { + return RegularExpressionLiteral.RegularExpressionFlags; +} diff --git a/engine262/src/static-semantics/HasInitializer.mjs b/engine262/src/static-semantics/HasInitializer.mjs new file mode 100644 index 0000000..0b2a271 --- /dev/null +++ b/engine262/src/static-semantics/HasInitializer.mjs @@ -0,0 +1,3 @@ +export function HasInitializer(node) { + return !!node.Initializer; +} diff --git a/engine262/src/static-semantics/HasName.mjs b/engine262/src/static-semantics/HasName.mjs new file mode 100644 index 0000000..01a169b --- /dev/null +++ b/engine262/src/static-semantics/HasName.mjs @@ -0,0 +1,6 @@ +export function HasName(node) { + if (node.type === 'ParenthesizedExpression') { + return HasName(node.Expression); + } + return !!node.BindingIdentifier; +} diff --git a/engine262/src/static-semantics/ImportEntries.mjs b/engine262/src/static-semantics/ImportEntries.mjs new file mode 100644 index 0000000..26436e8 --- /dev/null +++ b/engine262/src/static-semantics/ImportEntries.mjs @@ -0,0 +1,28 @@ +import { ImportEntriesForModule, ModuleRequests } from './all.mjs'; + +export function ImportEntries(node) { + switch (node.type) { + case 'Module': + if (node.ModuleBody) { + return ImportEntries(node.ModuleBody); + } + return []; + case 'ModuleBody': { + const entries = []; + for (const item of node.ModuleItemList) { + entries.push(...ImportEntries(item)); + } + return entries; + } + case 'ImportDeclaration': + if (node.FromClause) { + // 1. Let module be the sole element of ModuleRequests of FromClause. + const module = ModuleRequests(node.FromClause)[0]; + // 2. Return ImportEntriesForModule of ImportClause with argument module. + return ImportEntriesForModule(node.ImportClause, module); + } + return []; + default: + return []; + } +} diff --git a/engine262/src/static-semantics/ImportEntriesForModule.mjs b/engine262/src/static-semantics/ImportEntriesForModule.mjs new file mode 100644 index 0000000..263536c --- /dev/null +++ b/engine262/src/static-semantics/ImportEntriesForModule.mjs @@ -0,0 +1,107 @@ +import { Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; +import { BoundNames, StringValue } from './all.mjs'; + +export function ImportEntriesForModule(node, module) { + switch (node.type) { + case 'ImportClause': + switch (true) { + case !!node.ImportedDefaultBinding && !!node.NameSpaceImport: { + // 1. Let entries be ImportEntriesForModule of ImportedDefaultBinding with argument module. + const entries = ImportEntriesForModule(node.ImportedDefaultBinding, module); + // 2. Append to entries the elements of the ImportEntriesForModule of NameSpaceImport with argument module. + entries.push(...ImportEntriesForModule(node.NameSpaceImport, module)); + // 3. Return entries. + return entries; + } + case !!node.ImportedDefaultBinding && !!node.NamedImports: { + // 1. Let entries be ImportEntriesForModule of ImportedDefaultBinding with argument module. + const entries = ImportEntriesForModule(node.ImportedDefaultBinding, module); + // 2. Append to entries the elements of the ImportEntriesForModule of NamedImports with argument module. + entries.push(...ImportEntriesForModule(node.NamedImports, module)); + // 3. Return entries. + return entries; + } + case !!node.ImportedDefaultBinding: + return ImportEntriesForModule(node.ImportedDefaultBinding, module); + case !!node.NameSpaceImport: + return ImportEntriesForModule(node.NameSpaceImport, module); + case !!node.NamedImports: + return ImportEntriesForModule(node.NamedImports, module); + default: + throw new OutOfRange('ImportEntriesForModule', node); + } + case 'ImportedDefaultBinding': { + // 1. Let localName be the sole element of BoundNames of ImportedBinding. + const localName = BoundNames(node.ImportedBinding)[0]; + // 2. Let defaultEntry be the ImportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: "default", [[LocalName]]: localName }. + const defaultEntry = { + ModuleRequest: module, + ImportName: new Value('default'), + LocalName: localName, + }; + // 3. Return a new List containing defaultEntry. + return [defaultEntry]; + } + case 'NameSpaceImport': { + // 1. Let localName be the StringValue of ImportedBinding. + const localName = StringValue(node.ImportedBinding); + // 2. Let entry be the ImportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: ~star~, [[LocalName]]: localName }. + const entry = { + ModuleRequest: module, + ImportName: 'star', + LocalName: localName, + }; + // 3. Return a new List containing entry. + return [entry]; + } + case 'NamedImports': { + const specs = []; + node.ImportsList.forEach((n) => { + specs.push(...ImportEntriesForModule(n, module)); + }); + return specs; + } + case 'ImportSpecifier': + if (node.IdentifierName) { + // 1. Let importName be the StringValue of IdentifierName. + const importName = StringValue(node.IdentifierName); + // 2. Let localName be the StringValue of ImportedBinding. + const localName = StringValue(node.ImportedBinding); + // 3. Let entry be the ImportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName }. + const entry = { + ModuleRequest: module, + ImportName: importName, + LocalName: localName, + }; + // 4. Return a new List containing entry. + return [entry]; + } else if (node.ModuleExportName) { + // 1. Let importName be the StringValue of ModuleExportName. + const importName = StringValue(node.ModuleExportName); + // 2. Let localName be the StringValue of ImportedBinding. + const localName = StringValue(node.ImportedBinding); + // 3. Let entry be the ImportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName }. + const entry = { + ModuleRequest: module, + ImportName: importName, + LocalName: localName, + }; + // 4. Return a new List containing entry. + return [entry]; + } else { + // 1. Let localName be the sole element of BoundNames of ImportedBinding. + const localName = BoundNames(node.ImportedBinding)[0]; + // 2. Let entry be the ImportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: localName, [[LocalName]]: localName }. + const entry = { + ModuleRequest: module, + ImportName: localName, + LocalName: localName, + }; + // 3. Return a new List containing entry. + return [entry]; + } + default: + throw new OutOfRange('ImportEntriesForModule', node); + } +} diff --git a/engine262/src/static-semantics/ImportedLocalNames.mjs b/engine262/src/static-semantics/ImportedLocalNames.mjs new file mode 100644 index 0000000..45b6204 --- /dev/null +++ b/engine262/src/static-semantics/ImportedLocalNames.mjs @@ -0,0 +1,12 @@ +// #sec-importedlocalnames +export function ImportedLocalNames(importEntries) { + // 1. Let localNames be a new empty List. + const localNames = []; + // 2. For each ImportEntry Record i in importEntries, do + for (const i of importEntries) { + // a. Append i.[[LocalName]] to localNames. + localNames.push(i.LocalName); + } + // 3. Return localNames. + return localNames; +} diff --git a/engine262/src/static-semantics/IsAnonymousFunctionDefinition.mjs b/engine262/src/static-semantics/IsAnonymousFunctionDefinition.mjs new file mode 100644 index 0000000..ee750e2 --- /dev/null +++ b/engine262/src/static-semantics/IsAnonymousFunctionDefinition.mjs @@ -0,0 +1,17 @@ +import { IsFunctionDefinition, HasName } from './all.mjs'; + +// #sec-isanonymousfunctiondefinition +export function IsAnonymousFunctionDefinition(expr) { + // 1. If IsFunctionDefinition of expr is false, return false. + if (!IsFunctionDefinition(expr)) { + return false; + } + // 1. Let hasName be HasName of expr. + const hasName = HasName(expr); + // 1. If hasName is true, return false. + if (hasName) { + return false; + } + // 1. Return true. + return true; +} diff --git a/engine262/src/static-semantics/IsConstantDeclaration.mjs b/engine262/src/static-semantics/IsConstantDeclaration.mjs new file mode 100644 index 0000000..f11c822 --- /dev/null +++ b/engine262/src/static-semantics/IsConstantDeclaration.mjs @@ -0,0 +1,3 @@ +export function IsConstantDeclaration(node) { + return node === 'const' || node.LetOrConst === 'const'; +} diff --git a/engine262/src/static-semantics/IsDestructuring.mjs b/engine262/src/static-semantics/IsDestructuring.mjs new file mode 100644 index 0000000..d2e0aaf --- /dev/null +++ b/engine262/src/static-semantics/IsDestructuring.mjs @@ -0,0 +1,18 @@ +export function IsDestructuring(node) { + switch (node.type) { + case 'ObjectBindingPattern': + case 'ArrayBindingPattern': + case 'ObjectLiteral': + case 'ArrayLiteral': + return true; + case 'ForDeclaration': + return IsDestructuring(node.ForBinding); + case 'ForBinding': + if (node.BindingIdentifier) { + return false; + } + return true; + default: + return false; + } +} diff --git a/engine262/src/static-semantics/IsFunctionDefinition.mjs b/engine262/src/static-semantics/IsFunctionDefinition.mjs new file mode 100644 index 0000000..929f2ea --- /dev/null +++ b/engine262/src/static-semantics/IsFunctionDefinition.mjs @@ -0,0 +1,12 @@ +export function IsFunctionDefinition(node) { + if (node.type === 'ParenthesizedExpression') { + return IsFunctionDefinition(node.Expression); + } + return node.type === 'FunctionExpression' + || node.type === 'GeneratorExpression' + || node.type === 'AsyncGeneratorExpression' + || node.type === 'AsyncFunctionExpression' + || node.type === 'ClassExpression' + || node.type === 'ArrowFunction' + || node.type === 'AsyncArrowFunction'; +} diff --git a/engine262/src/static-semantics/IsIdentifierRef.mjs b/engine262/src/static-semantics/IsIdentifierRef.mjs new file mode 100644 index 0000000..d49e68f --- /dev/null +++ b/engine262/src/static-semantics/IsIdentifierRef.mjs @@ -0,0 +1,3 @@ +export function IsIdentifierRef(node) { + return node.type === 'IdentifierReference'; +} diff --git a/engine262/src/static-semantics/IsInTailPosition.mjs b/engine262/src/static-semantics/IsInTailPosition.mjs new file mode 100644 index 0000000..9115336 --- /dev/null +++ b/engine262/src/static-semantics/IsInTailPosition.mjs @@ -0,0 +1,3 @@ +export function IsInTailPosition(_node) { + return false; +} diff --git a/engine262/src/static-semantics/IsSimpleParameterList.mjs b/engine262/src/static-semantics/IsSimpleParameterList.mjs new file mode 100644 index 0000000..139b13e --- /dev/null +++ b/engine262/src/static-semantics/IsSimpleParameterList.mjs @@ -0,0 +1,22 @@ +import { OutOfRange } from '../helpers.mjs'; + +export function IsSimpleParameterList(node) { + if (Array.isArray(node)) { + for (const n of node) { + if (!IsSimpleParameterList(n)) { + return false; + } + } + return true; + } + switch (node.type) { + case 'SingleNameBinding': + return node.Initializer === null; + case 'BindingElement': + return false; + case 'BindingRestElement': + return false; + default: + throw new OutOfRange('IsSimpleParameterList', node); + } +} diff --git a/engine262/src/static-semantics/IsStatic.mjs b/engine262/src/static-semantics/IsStatic.mjs new file mode 100644 index 0000000..cd15e6b --- /dev/null +++ b/engine262/src/static-semantics/IsStatic.mjs @@ -0,0 +1,8 @@ +// #sec-static-semantics-isstatic +// ClassElement : +// MethodDefinition +// `static` MethodDefinition +// `;` +export function IsStatic(ClassElement) { + return ClassElement.static; +} diff --git a/engine262/src/static-semantics/IsStrict.mjs b/engine262/src/static-semantics/IsStrict.mjs new file mode 100644 index 0000000..76be7bd --- /dev/null +++ b/engine262/src/static-semantics/IsStrict.mjs @@ -0,0 +1,5 @@ +// #sec-static-semantics-isstrict +export function IsStrict({ ScriptBody }) { + // 1. If ScriptBody is present and the Directive Prologue of ScriptBody contains a Use Strict Directive, return true; otherwise, return false. + return ScriptBody.strict; +} diff --git a/engine262/src/static-semantics/IsStringValidUnicode.mjs b/engine262/src/static-semantics/IsStringValidUnicode.mjs new file mode 100644 index 0000000..a54237d --- /dev/null +++ b/engine262/src/static-semantics/IsStringValidUnicode.mjs @@ -0,0 +1,22 @@ +import { X } from '../completion.mjs'; +import { CodePointAt } from './all.mjs'; + +export function IsStringValidUnicode(string) { + string = string.stringValue(); + // 1. Let _strLen_ be the number of code units in string. + const strLen = string.length; + // 2. Let k be 0. + let k = 0; + // 3. Repeat, while k does not equal strLen, + while (k !== strLen) { + // a. Let cp be ! CodePointAt(string, k). + const cp = X(CodePointAt(string, k)); + // b. If cp.[[IsUnpairedSurrogate]] is true, return false. + if (cp.IsUnpairedSurrogate) { + return false; + } + // c. Set k to k + cp.[[CodeUnitCount]]. + k += cp.CodeUnitCount; + } + return true; +} diff --git a/engine262/src/static-semantics/LexicallyDeclaredNames.mjs b/engine262/src/static-semantics/LexicallyDeclaredNames.mjs new file mode 100644 index 0000000..be86e35 --- /dev/null +++ b/engine262/src/static-semantics/LexicallyDeclaredNames.mjs @@ -0,0 +1,22 @@ +import { + TopLevelLexicallyDeclaredNames, +} from './all.mjs'; + +export function LexicallyDeclaredNames(node) { + switch (node.type) { + case 'Script': + if (node.ScriptBody) { + return LexicallyDeclaredNames(node.ScriptBody); + } + return []; + case 'ScriptBody': + return TopLevelLexicallyDeclaredNames(node.StatementList); + case 'FunctionBody': + case 'GeneratorBody': + case 'AsyncFunctionBody': + case 'AsyncGeneratorBody': + return TopLevelLexicallyDeclaredNames(node.FunctionStatementList); + default: + return []; + } +} diff --git a/engine262/src/static-semantics/LexicallyScopedDeclarations.mjs b/engine262/src/static-semantics/LexicallyScopedDeclarations.mjs new file mode 100644 index 0000000..b684353 --- /dev/null +++ b/engine262/src/static-semantics/LexicallyScopedDeclarations.mjs @@ -0,0 +1,78 @@ +import { TopLevelLexicallyScopedDeclarations, DeclarationPart } from './all.mjs'; + +export function LexicallyScopedDeclarations(node) { + if (Array.isArray(node)) { + const declarations = []; + for (const item of node) { + declarations.push(...LexicallyScopedDeclarations(item)); + } + return declarations; + } + switch (node.type) { + case 'LabelledStatement': + return LexicallyScopedDeclarations(node.LabelledItem); + case 'Script': + if (node.ScriptBody) { + return LexicallyScopedDeclarations(node.ScriptBody); + } + return []; + case 'ScriptBody': + return TopLevelLexicallyScopedDeclarations(node.StatementList); + case 'Module': + if (node.ModuleBody) { + return LexicallyScopedDeclarations(node.ModuleBody); + } + return []; + case 'ModuleBody': + return LexicallyScopedDeclarations(node.ModuleItemList); + case 'FunctionBody': + case 'GeneratorBody': + case 'AsyncFunctionBody': + case 'AsyncGeneratorBody': + return TopLevelLexicallyScopedDeclarations(node.FunctionStatementList); + case 'ImportDeclaration': + return []; + case 'ClassDeclaration': + case 'LexicalDeclaration': + case 'FunctionDeclaration': + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + return [DeclarationPart(node)]; + case 'CaseBlock': { + const names = []; + if (node.CaseClauses_a) { + names.push(...LexicallyScopedDeclarations(node.CaseClauses_a)); + } + if (node.DefaultClause) { + names.push(...LexicallyScopedDeclarations(node.DefaultClause)); + } + if (node.CaseClauses_b) { + names.push(...LexicallyScopedDeclarations(node.CaseClauses_b)); + } + return names; + } + case 'CaseClause': + case 'DefaultClause': + if (node.StatementList) { + return LexicallyScopedDeclarations(node.StatementList); + } + return []; + case 'ExportDeclaration': + if (node.Declaration) { + return [DeclarationPart(node.Declaration)]; + } + if (node.HoistableDeclaration) { + return [DeclarationPart(node.HoistableDeclaration)]; + } + if (node.ClassDeclaration) { + return [node.ClassDeclaration]; + } + if (node.AssignmentExpression) { + return [node]; + } + return []; + default: + return []; + } +} diff --git a/engine262/src/static-semantics/ModuleRequests.mjs b/engine262/src/static-semantics/ModuleRequests.mjs new file mode 100644 index 0000000..abe262e --- /dev/null +++ b/engine262/src/static-semantics/ModuleRequests.mjs @@ -0,0 +1,32 @@ +import { StringValue } from './all.mjs'; + +export function ModuleRequests(node) { + switch (node.type) { + case 'Module': + if (node.ModuleBody) { + return ModuleRequests(node.ModuleBody); + } + return []; + case 'ModuleBody': { + const moduleNames = []; + for (const item of node.ModuleItemList) { + moduleNames.push(...ModuleRequests(item)); + } + return moduleNames; + } + case 'ImportDeclaration': + if (node.FromClause) { + return ModuleRequests(node.FromClause); + } + return [StringValue(node.ModuleSpecifier)]; + case 'ExportDeclaration': + if (node.FromClause) { + return ModuleRequests(node.FromClause); + } + return []; + case 'StringLiteral': + return [StringValue(node)]; + default: + return []; + } +} diff --git a/engine262/src/static-semantics/NonConstructorMethodDefinitions.mjs b/engine262/src/static-semantics/NonConstructorMethodDefinitions.mjs new file mode 100644 index 0000000..a0981f3 --- /dev/null +++ b/engine262/src/static-semantics/NonConstructorMethodDefinitions.mjs @@ -0,0 +1,14 @@ +import { PropName } from './all.mjs'; + +// #sec-static-semantics-nonconstructormethoddefinitions +// ClassElementList : +// ClassElement +// ClassElementList ClassElement +export function NonConstructorMethodDefinitions(ClassElementList) { + return ClassElementList.filter((ClassElement) => { + if (ClassElement.static === false && PropName(ClassElement) === 'constructor') { + return false; + } + return true; + }); +} diff --git a/engine262/src/static-semantics/NumericValue.mjs b/engine262/src/static-semantics/NumericValue.mjs new file mode 100644 index 0000000..253a4d0 --- /dev/null +++ b/engine262/src/static-semantics/NumericValue.mjs @@ -0,0 +1,6 @@ +// #sec-numericvalue +import { Value } from '../value.mjs'; + +export function NumericValue(node) { + return new Value(node.value); +} diff --git a/engine262/src/static-semantics/PropName.mjs b/engine262/src/static-semantics/PropName.mjs new file mode 100644 index 0000000..7c2e1e2 --- /dev/null +++ b/engine262/src/static-semantics/PropName.mjs @@ -0,0 +1,20 @@ +export function PropName(node) { + switch (node.type) { + case 'IdentifierName': + return node.name; + case 'StringLiteral': + return node.value; + case 'MethodDefinition': + case 'GeneratorMethod': + case 'AsyncGeneratorMethod': + case 'AsyncMethod': + return PropName(node.PropertyName); + case 'ClassElement': + if (node.MethodDefinition) { + return PropName(node.MethodDefinition); + } + return undefined; + default: + return undefined; + } +} diff --git a/engine262/src/static-semantics/StringToCodePoints.mjs b/engine262/src/static-semantics/StringToCodePoints.mjs new file mode 100644 index 0000000..b6e0b92 --- /dev/null +++ b/engine262/src/static-semantics/StringToCodePoints.mjs @@ -0,0 +1,23 @@ +import { X } from '../completion.mjs'; +import { CodePointAt } from './all.mjs'; + +// #sec-stringtocodepoints +export function StringToCodePoints(string) { + // 1. Let codePoints be a new empty List. + const codePoints = []; + // 2. Let size be the length of string. + const size = string.length; + // 3. Let position be 0. + let position = 0; + // 4. Repeat, while position < size, + while (position < size) { + // a. Let cp be ! CodePointAt(string, position). + const cp = X(CodePointAt(string, position)); + // b. Append cp.[[CodePoint]] to codePoints. + codePoints.push(cp.CodePoint); + // c. Set position to position + cp.[[CodeUnitCount]]. + position += cp.CodeUnitCount; + } + // 5. Return codePoints. + return codePoints; +} diff --git a/engine262/src/static-semantics/StringValue.mjs b/engine262/src/static-semantics/StringValue.mjs new file mode 100644 index 0000000..e413e8c --- /dev/null +++ b/engine262/src/static-semantics/StringValue.mjs @@ -0,0 +1,17 @@ +import { Value } from '../value.mjs'; +import { OutOfRange } from '../helpers.mjs'; + +export function StringValue(node) { + switch (node.type) { + case 'Identifier': + case 'IdentifierName': + case 'BindingIdentifier': + case 'IdentifierReference': + case 'LabelIdentifier': + return new Value(node.name); + case 'StringLiteral': + return new Value(node.value); + default: + throw new OutOfRange('StringValue', node); + } +} diff --git a/engine262/src/static-semantics/TemplateStrings.mjs b/engine262/src/static-semantics/TemplateStrings.mjs new file mode 100644 index 0000000..443f1ba --- /dev/null +++ b/engine262/src/static-semantics/TemplateStrings.mjs @@ -0,0 +1,104 @@ +import { Value } from '../value.mjs'; +import { isHexDigit, isDecimalDigit, isLineTerminator } from '../parser/Lexer.mjs'; + +export function TV(s) { + let buffer = ''; + for (let i = 0; i < s.length; i += 1) { + if (s[i] === '\\') { + i += 1; + switch (s[i]) { + case '\\': + buffer += '\\'; + break; + case '`': + buffer += '`'; + break; + case '\'': + buffer += '\''; + break; + case '"': + buffer += '"'; + break; + case 'b': + buffer += '\b'; + break; + case 'f': + buffer += '\f'; + break; + case 'n': + buffer += '\n'; + break; + case 'r': + buffer += '\r'; + break; + case 't': + buffer += '\t'; + break; + case 'v': + buffer += '\v'; + break; + case 'x': + i += 1; + if (isHexDigit(s[i]) && isHexDigit(s[i + 1])) { + const n = Number.parseInt(s.slice(i, i + 2), 16); + i += 2; + buffer += String.fromCharCode(n); + } else { + return undefined; + } + break; + case 'u': + i += 1; + if (s[i] === '{') { + i += 1; + const start = i; + do { + i += 1; + } while (isHexDigit(s[i])); + if (s[i] !== '}') { + return undefined; + } + const n = Number.parseInt(s.slice(start, i), 16); + if (n > 0x10FFFF) { + return undefined; + } + buffer += String.fromCodePoint(n); + } else if (isHexDigit(s[i]) && isHexDigit(s[i + 1]) + && isHexDigit(s[i + 2]) && isHexDigit(s[i + 3])) { + const n = Number.parseInt(s.slice(i, i + 4), 16); + i += 3; + buffer += String.fromCodePoint(n); + } else { + return undefined; + } + break; + case '0': + if (isDecimalDigit(s[i + 1])) { + return undefined; + } + return '\u{0000}'; + default: + if (isLineTerminator(s)) { + return ''; + } + return undefined; + } + } else { + buffer += s[i]; + } + } + return buffer; +} + +export function TemplateStrings(node, raw) { + if (raw) { + return node.TemplateSpanList.map(Value); + } + return node.TemplateSpanList.map((v) => { + const tv = TV(v); + if (tv === undefined) { + return Value.undefined; + } + return new Value(tv); + }); +} diff --git a/engine262/src/static-semantics/TopLevelLexicallyDeclaredNames.mjs b/engine262/src/static-semantics/TopLevelLexicallyDeclaredNames.mjs new file mode 100644 index 0000000..f992ef6 --- /dev/null +++ b/engine262/src/static-semantics/TopLevelLexicallyDeclaredNames.mjs @@ -0,0 +1,18 @@ +import { BoundNames } from './all.mjs'; + +export function TopLevelLexicallyDeclaredNames(node) { + if (Array.isArray(node)) { + const names = []; + for (const StatementListItem of node) { + names.push(...TopLevelLexicallyDeclaredNames(StatementListItem)); + } + return names; + } + switch (node.type) { + case 'ClassDeclaration': + case 'LexicalDeclaration': + return BoundNames(node); + default: + return []; + } +} diff --git a/engine262/src/static-semantics/TopLevelLexicallyScopedDeclarations.mjs b/engine262/src/static-semantics/TopLevelLexicallyScopedDeclarations.mjs new file mode 100644 index 0000000..66ece70 --- /dev/null +++ b/engine262/src/static-semantics/TopLevelLexicallyScopedDeclarations.mjs @@ -0,0 +1,16 @@ +export function TopLevelLexicallyScopedDeclarations(node) { + if (Array.isArray(node)) { + const declarations = []; + for (const item of node) { + declarations.push(...TopLevelLexicallyScopedDeclarations(item)); + } + return declarations; + } + switch (node.type) { + case 'ClassDeclaration': + case 'LexicalDeclaration': + return [node]; + default: + return []; + } +} diff --git a/engine262/src/static-semantics/TopLevelVarDeclaredNames.mjs b/engine262/src/static-semantics/TopLevelVarDeclaredNames.mjs new file mode 100644 index 0000000..5b5df24 --- /dev/null +++ b/engine262/src/static-semantics/TopLevelVarDeclaredNames.mjs @@ -0,0 +1,23 @@ +import { BoundNames, VarDeclaredNames } from './all.mjs'; + +export function TopLevelVarDeclaredNames(node) { + if (Array.isArray(node)) { + const names = []; + for (const item of node) { + names.push(...TopLevelVarDeclaredNames(item)); + } + return names; + } + switch (node.type) { + case 'ClassDeclaration': + case 'LexicalDeclaration': + return []; + case 'FunctionDeclaration': + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + return BoundNames(node); + default: + return VarDeclaredNames(node); + } +} diff --git a/engine262/src/static-semantics/TopLevelVarScopedDeclarations.mjs b/engine262/src/static-semantics/TopLevelVarScopedDeclarations.mjs new file mode 100644 index 0000000..fe0c57e --- /dev/null +++ b/engine262/src/static-semantics/TopLevelVarScopedDeclarations.mjs @@ -0,0 +1,23 @@ +import { DeclarationPart, VarScopedDeclarations } from './all.mjs'; + +export function TopLevelVarScopedDeclarations(node) { + if (Array.isArray(node)) { + const declarations = []; + for (const item of node) { + declarations.push(...TopLevelVarScopedDeclarations(item)); + } + return declarations; + } + switch (node.type) { + case 'ClassDeclaration': + case 'LexicalDeclaration': + return []; + case 'FunctionDeclaration': + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + return [DeclarationPart(node)]; + default: + return VarScopedDeclarations(node); + } +} diff --git a/engine262/src/static-semantics/UTF16SurrogatePairToCodePoint.mjs b/engine262/src/static-semantics/UTF16SurrogatePairToCodePoint.mjs new file mode 100644 index 0000000..411329b --- /dev/null +++ b/engine262/src/static-semantics/UTF16SurrogatePairToCodePoint.mjs @@ -0,0 +1,12 @@ +import { Assert } from '../abstract-ops/all.mjs'; +import { isLeadingSurrogate, isTrailingSurrogate } from '../parser/Lexer.mjs'; + +// #sec-utf16decodesurrogatepair +export function UTF16SurrogatePairToCodePoint(lead, trail) { + // 1. Assert: lead is a leading surrogate and trail is a trailing surrogate. + Assert(isLeadingSurrogate(lead) && isTrailingSurrogate(trail)); + // 2. Let cp be (lead - 0xD800) × 0x400 + (trail - 0xDC00) + 0x10000. + const cp = (lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000; + // 3. Return the code point cp. + return cp; +} diff --git a/engine262/src/static-semantics/VarDeclaredNames.mjs b/engine262/src/static-semantics/VarDeclaredNames.mjs new file mode 100644 index 0000000..0152023 --- /dev/null +++ b/engine262/src/static-semantics/VarDeclaredNames.mjs @@ -0,0 +1,104 @@ +import { BoundNames, TopLevelVarDeclaredNames } from './all.mjs'; + +export function VarDeclaredNames(node) { + if (Array.isArray(node)) { + const names = []; + for (const item of node) { + names.push(...VarDeclaredNames(item)); + } + return names; + } + switch (node.type) { + case 'VariableStatement': + return BoundNames(node.VariableDeclarationList); + case 'VariableDeclaration': + return BoundNames(node); + case 'IfStatement': { + const names = VarDeclaredNames(node.Statement_a); + if (node.Statement_b) { + names.push(...VarDeclaredNames(node.Statement_b)); + } + return names; + } + case 'Block': + return VarDeclaredNames(node.StatementList); + case 'WhileStatement': + return VarDeclaredNames(node.Statement); + case 'DoWhileStatement': + return VarDeclaredNames(node.Statement); + case 'ForStatement': { + const names = []; + if (node.VariableDeclarationList) { + names.push(...VarDeclaredNames(node.VariableDeclarationList)); + } + names.push(...VarDeclaredNames(node.Statement)); + return names; + } + case 'ForInStatement': + case 'ForOfStatement': + case 'ForAwaitStatement': { + const names = []; + if (node.ForBinding) { + names.push(...BoundNames(node.ForBinding)); + } + names.push(...VarDeclaredNames(node.Statement)); + return names; + } + case 'WithStatement': + return VarDeclaredNames(node.Statement); + case 'SwitchStatement': + return VarDeclaredNames(node.CaseBlock); + case 'CaseBlock': { + const names = []; + if (node.CaseClauses_a) { + names.push(...VarDeclaredNames(node.CaseClauses_a)); + } + if (node.DefaultClause) { + names.push(...VarDeclaredNames(node.DefaultClause)); + } + if (node.CaseClauses_b) { + names.push(...VarDeclaredNames(node.CaseClauses_b)); + } + return names; + } + case 'CaseClause': + case 'DefaultClause': + if (node.StatementList) { + return VarDeclaredNames(node.StatementList); + } + return []; + case 'LabelledStatement': + return VarDeclaredNames(node.LabelledItem); + case 'TryStatement': { + const names = VarDeclaredNames(node.Block); + if (node.Catch) { + names.push(...VarDeclaredNames(node.Catch)); + } + if (node.Finally) { + names.push(...VarDeclaredNames(node.Finally)); + } + return names; + } + case 'Catch': + return VarDeclaredNames(node.Block); + case 'Script': + if (node.ScriptBody) { + return VarDeclaredNames(node.ScriptBody); + } + return []; + case 'ScriptBody': + return TopLevelVarDeclaredNames(node.StatementList); + case 'FunctionBody': + case 'GeneratorBody': + case 'AsyncFunctionBody': + case 'AsyncGeneratorBody': + return TopLevelVarDeclaredNames(node.FunctionStatementList); + case 'ExportDeclaration': + if (node.VariableStatement) { + return BoundNames(node); + } + return []; + default: + return []; + } +} diff --git a/engine262/src/static-semantics/VarScopedDeclarations.mjs b/engine262/src/static-semantics/VarScopedDeclarations.mjs new file mode 100644 index 0000000..f6c2e67 --- /dev/null +++ b/engine262/src/static-semantics/VarScopedDeclarations.mjs @@ -0,0 +1,111 @@ +import { TopLevelVarScopedDeclarations } from './all.mjs'; + +export function VarScopedDeclarations(node) { + if (Array.isArray(node)) { + const declarations = []; + for (const item of node) { + declarations.push(...VarScopedDeclarations(item)); + } + return declarations; + } + switch (node.type) { + case 'VariableStatement': + return VarScopedDeclarations(node.VariableDeclarationList); + case 'VariableDeclaration': + return [node]; + case 'Block': + return VarScopedDeclarations(node.StatementList); + case 'IfStatement': { + const declarations = VarScopedDeclarations(node.Statement_a); + if (node.Statement_b) { + declarations.push(...VarScopedDeclarations(node.Statement_b)); + } + return declarations; + } + case 'WhileStatement': + return VarScopedDeclarations(node.Statement); + case 'DoWhileStatement': + return VarScopedDeclarations(node.Statement); + case 'ForStatement': { + const names = []; + if (node.VariableDeclarationList) { + names.push(...VarScopedDeclarations(node.VariableDeclarationList)); + } + names.push(...VarScopedDeclarations(node.Statement)); + return names; + } + case 'ForInStatement': + case 'ForOfStatement': + case 'ForAwaitStatement': { + const declarations = []; + if (node.ForBinding) { + declarations.push(node.ForBinding); + } + declarations.push(...VarScopedDeclarations(node.Statement)); + return declarations; + } + case 'WithStatement': + return VarScopedDeclarations(node.Statement); + case 'SwitchStatement': + return VarScopedDeclarations(node.CaseBlock); + case 'CaseBlock': { + const names = []; + if (node.CaseClauses_a) { + names.push(...VarScopedDeclarations(node.CaseClauses_a)); + } + if (node.DefaultClause) { + names.push(...VarScopedDeclarations(node.DefaultClause)); + } + if (node.CaseClauses_b) { + names.push(...VarScopedDeclarations(node.CaseClauses_b)); + } + return names; + } + case 'CaseClause': + case 'DefaultClause': + if (node.StatementList) { + return VarScopedDeclarations(node.StatementList); + } + return []; + case 'LabelledStatement': + return VarScopedDeclarations(node.LabelledItem); + case 'TryStatement': { + const declarations = VarScopedDeclarations(node.Block); + if (node.Catch) { + declarations.push(...VarScopedDeclarations(node.Catch)); + } + if (node.Finally) { + declarations.push(...VarScopedDeclarations(node.Finally)); + } + return declarations; + } + case 'Catch': + return VarScopedDeclarations(node.Block); + case 'ExportDeclaration': + if (node.VariableStatement) { + return VarScopedDeclarations(node.VariableStatement); + } + return []; + case 'Script': + if (node.ScriptBody) { + return VarScopedDeclarations(node.ScriptBody); + } + return []; + case 'ScriptBody': + return TopLevelVarScopedDeclarations(node.StatementList); + case 'Module': + if (node.ModuleBody) { + return VarScopedDeclarations(node.ModuleBody); + } + return []; + case 'ModuleBody': + return VarScopedDeclarations(node.ModuleItemList); + case 'FunctionBody': + case 'GeneratorBody': + case 'AsyncFunctionBody': + case 'AsyncGeneratorBody': + return TopLevelVarScopedDeclarations(node.FunctionStatementList); + default: + return []; + } +} diff --git a/engine262/src/static-semantics/all.mjs b/engine262/src/static-semantics/all.mjs new file mode 100644 index 0000000..f05ec28 --- /dev/null +++ b/engine262/src/static-semantics/all.mjs @@ -0,0 +1,44 @@ +export * from './StringValue.mjs'; +export * from './IsStatic.mjs'; +export * from './NonConstructorMethodDefinitions.mjs'; +export * from './ConstructorMethod.mjs'; +export * from './PropName.mjs'; +export * from './NumericValue.mjs'; +export * from './IsAnonymousFunctionDefinition.mjs'; +export * from './IsFunctionDefinition.mjs'; +export * from './HasName.mjs'; +export * from './IsIdentifierRef.mjs'; +export * from './LexicallyDeclaredNames.mjs'; +export * from './TopLevelLexicallyDeclaredNames.mjs'; +export * from './BoundNames.mjs'; +export * from './VarDeclaredNames.mjs'; +export * from './TopLevelVarDeclaredNames.mjs'; +export * from './VarScopedDeclarations.mjs'; +export * from './TopLevelVarScopedDeclarations.mjs'; +export * from './DeclarationPart.mjs'; +export * from './LexicallyScopedDeclarations.mjs'; +export * from './TopLevelLexicallyScopedDeclarations.mjs'; +export * from './IsConstantDeclaration.mjs'; +export * from './IsInTailPosition.mjs'; +export * from './ExpectedArgumentCount.mjs'; +export * from './HasInitializer.mjs'; +export * from './IsSimpleParameterList.mjs'; +export * from './ContainsExpression.mjs'; +export * from './IsStrict.mjs'; +export * from './BodyText.mjs'; +export * from './FlagText.mjs'; +export * from './ModuleRequests.mjs'; +export * from './ImportEntries.mjs'; +export * from './ExportEntries.mjs'; +export * from './ImportedLocalNames.mjs'; +export * from './IsDestructuring.mjs'; +export * from './TemplateStrings.mjs'; +export * from './ImportEntriesForModule.mjs'; +export * from './ExportEntriesForModule.mjs'; +export * from './CharacterValue.mjs'; +export * from './UTF16SurrogatePairToCodePoint.mjs'; +export * from './CodePointAt.mjs'; +export * from './CodePointToUTF16CodeUnits.mjs'; +export * from './StringToCodePoints.mjs'; +export * from './CodePointsToString.mjs'; +export * from './IsStringValidUnicode.mjs'; diff --git a/engine262/src/value.mjs b/engine262/src/value.mjs new file mode 100644 index 0000000..caf9507 --- /dev/null +++ b/engine262/src/value.mjs @@ -0,0 +1,855 @@ +import { surroundingAgent } from './engine.mjs'; +import { + Assert, + CreateBuiltinFunction, + OrdinaryDefineOwnProperty, + OrdinaryDelete, + OrdinaryGet, + OrdinaryGetOwnProperty, + OrdinaryGetPrototypeOf, + OrdinaryHasProperty, + OrdinaryIsExtensible, + OrdinaryOwnPropertyKeys, + OrdinaryPreventExtensions, + OrdinarySet, + OrdinarySetPrototypeOf, + ToInt32, + ToUint32, +} from './abstract-ops/all.mjs'; +import { EnvironmentRecord } from './environment.mjs'; +import { Completion, X } from './completion.mjs'; +import { ValueMap, OutOfRange } from './helpers.mjs'; + +// #sec-ecmascript-language-types +export function Value(value) { + if (new.target !== undefined && new.target !== Value) { + return undefined; + } + + switch (typeof value) { + case 'string': + return new StringValue(value); + case 'number': + return new NumberValue(value); + case 'bigint': + return new BigIntValue(value); + case 'function': + return CreateBuiltinFunction(value, []); + default: + throw new OutOfRange('new Value', value); + } +} + +export class PrimitiveValue extends Value {} + +// #sec-ecmascript-language-types-undefined-type +export class UndefinedValue extends PrimitiveValue {} + +// #sec-ecmascript-language-types-null-type +export class NullValue extends PrimitiveValue {} + +// #sec-ecmascript-language-types-boolean-type +export class BooleanValue extends PrimitiveValue { + constructor(v) { + super(); + this.boolean = v; + } + + booleanValue() { + return this.boolean; + } + + [Symbol.for('nodejs.util.inspect.custom')]() { + return `Boolean { ${this.boolean} }`; + } +} + +Object.defineProperties(Value, { + undefined: { value: new UndefinedValue(), configurable: false, writable: false }, + null: { value: new NullValue(), configurable: false, writable: false }, + true: { value: new BooleanValue(true), configurable: false, writable: false }, + false: { value: new BooleanValue(false), configurable: false, writable: false }, +}); + +// #sec-ecmascript-language-types-string-type +class StringValue extends PrimitiveValue { + constructor(string) { + super(); + this.string = string; + } + + stringValue() { + return this.string; + } +} +// rename for static semantics StringValue() conflict +export { StringValue as JSStringValue }; + +// #sec-ecmascript-language-types-symbol-type +export class SymbolValue extends PrimitiveValue { + constructor(Description) { + super(); + this.Description = Description; + } +} + +export const wellKnownSymbols = Object.create(null); +for (const name of [ + 'asyncIterator', + 'hasInstance', + 'isConcatSpreadable', + 'iterator', + 'match', + 'matchAll', + 'replace', + 'search', + 'species', + 'split', + 'toPrimitive', + 'toStringTag', + 'unscopables', +]) { + const sym = new SymbolValue(new StringValue(`Symbol.${name}`)); + wellKnownSymbols[name] = sym; +} +Object.freeze(wellKnownSymbols); + +// #sec-ecmascript-language-types-number-type +export class NumberValue extends PrimitiveValue { + constructor(number) { + super(); + this.number = number; + } + + numberValue() { + return this.number; + } + + isNaN() { + return Number.isNaN(this.number); + } + + isInfinity() { + return !Number.isFinite(this.number) && !this.isNaN(); + } + + isFinite() { + return Number.isFinite(this.number); + } + + // #sec-numeric-types-number-unaryMinus + static unaryMinus(x) { + if (x.isNaN()) { + return new Value(NaN); + } + return new Value(-x.numberValue()); + } + + // #sec-numeric-types-number-bitwiseNOT + static bitwiseNOT(x) { + // 1. Let oldValue be ! ToInt32(x). + const oldValue = X(ToInt32(x)); + // 2. Return the result of applying bitwise complement to oldValue. The result is a signed 32-bit integer. + return new Value(~oldValue.numberValue()); // eslint-disable-line no-bitwise + } + + // #sec-numeric-types-number-exponentiate + static exponentiate(base, exponent) { + return new Value(base.numberValue() ** exponent.numberValue()); + } + + // #sec-numeric-types-number-multiply + static multiply(x, y) { + return new Value(x.numberValue() * y.numberValue()); + } + + // #sec-numeric-types-number-divide + static divide(x, y) { + return new Value(x.numberValue() / y.numberValue()); + } + + // #sec-numeric-types-number-remainder + static remainder(n, d) { + return new Value(n.numberValue() % d.numberValue()); + } + + // #sec-numeric-types-number-add + static add(x, y) { + return new Value(x.numberValue() + y.numberValue()); + } + + // #sec-numeric-types-number-subtract + static subtract(x, y) { + // The result of - operator is x + (-y). + return NumberValue.add(x, new Value(-y.numberValue())); + } + + // #sec-numeric-types-number-leftShift + static leftShift(x, y) { + // 1. Let lnum be ! ToInt32(x). + const lnum = X(ToInt32(x)); + // 2. Let rnum be ! ToUint32(y). + const rnum = X(ToUint32(y)); + // 3. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F. + const shiftCount = rnum.numberValue() & 0x1F; // eslint-disable-line no-bitwise + // 4. Return the result of left shifting lnum by shiftCount bits. The result is a signed 32-bit integer. + return new Value(lnum.numberValue() << shiftCount); // eslint-disable-line no-bitwise + } + + // #sec-numeric-types-number-signedRightShift + static signedRightShift(x, y) { + // 1. Let lnum be ! ToInt32(x). + const lnum = X(ToInt32(x)); + // 2. Let rnum be ! ToUint32(y). + const rnum = X(ToUint32(y)); + // 3. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F. + const shiftCount = rnum.numberValue() & 0x1F; // eslint-disable-line no-bitwise + // 4. Return the result of performing a sign-extending right shift of lnum by shiftCount bits. + // The most significant bit is propagated. The result is a signed 32-bit integer. + return new Value(lnum.numberValue() >> shiftCount); // eslint-disable-line no-bitwise + } + + // #sec-numeric-types-number-unsignedRightShift + static unsignedRightShift(x, y) { + // 1. Let lnum be ! ToInt32(x). + const lnum = X(ToInt32(x)); + // 2. Let rnum be ! ToUint32(y). + const rnum = X(ToUint32(y)); + // 3. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F. + const shiftCount = rnum.numberValue() & 0x1F; // eslint-disable-line no-bitwise + // 4. Return the result of performing a zero-filling right shift of lnum by shiftCount bits. + // Vacated bits are filled with zero. The result is an unsigned 32-bit integer. + return new Value(lnum.numberValue() >>> shiftCount); // eslint-disable-line no-bitwise + } + + // #sec-numeric-types-number-lessThan + static lessThan(x, y) { + if (x.isNaN()) { + return Value.undefined; + } + if (y.isNaN()) { + return Value.undefined; + } + // If nx and ny are the same Number value, return false. + // If nx is +0 and ny is -0, return false. + // If nx is -0 and ny is +0, return false. + if (x.numberValue() === y.numberValue()) { + return Value.false; + } + if (x.numberValue() === +Infinity) { + return Value.false; + } + if (y.numberValue() === +Infinity) { + return Value.true; + } + if (y.numberValue() === -Infinity) { + return Value.false; + } + if (x.numberValue() === -Infinity) { + return Value.true; + } + return x.numberValue() < y.numberValue() ? Value.true : Value.false; + } + + // #sec-numeric-types-number-equal + static equal(x, y) { + if (x.isNaN()) { + return Value.false; + } + if (y.isNaN()) { + return Value.false; + } + const xVal = x.numberValue(); + const yVal = y.numberValue(); + if (xVal === yVal) { + return Value.true; + } + if (Object.is(xVal, 0) && Object.is(yVal, -0)) { + return Value.true; + } + if (Object.is(xVal, -0) && Object.is(yVal, 0)) { + return Value.true; + } + return Value.false; + } + + // #sec-numeric-types-number-sameValue + static sameValue(x, y) { + if (x.isNaN() && y.isNaN()) { + return Value.true; + } + const xVal = x.numberValue(); + const yVal = y.numberValue(); + if (Object.is(xVal, 0) && Object.is(yVal, -0)) { + return Value.false; + } + if (Object.is(xVal, -0) && Object.is(yVal, 0)) { + return Value.false; + } + if (xVal === yVal) { + return Value.true; + } + return Value.false; + } + + // #sec-numeric-types-number-sameValueZero + static sameValueZero(x, y) { + if (x.isNaN() && y.isNaN()) { + return Value.true; + } + const xVal = x.numberValue(); + const yVal = y.numberValue(); + if (Object.is(xVal, 0) && Object.is(yVal, -0)) { + return Value.true; + } + if (Object.is(xVal, -0) && Object.is(yVal, 0)) { + return Value.true; + } + if (xVal === yVal) { + return Value.true; + } + return Value.false; + } + + // #sec-numeric-types-number-bitwiseAND + static bitwiseAND(x, y) { + return NumberBitwiseOp('&', x, y); + } + + // #sec-numeric-types-number-bitwiseXOR + static bitwiseXOR(x, y) { + return NumberBitwiseOp('^', x, y); + } + + // #sec-numeric-types-number-bitwiseOR + static bitwiseOR(x, y) { + return NumberBitwiseOp('|', x, y); + } + + // #sec-numeric-types-number-tostring + static toString(x) { + if (x.isNaN()) { + return new Value('NaN'); + } + const xVal = x.numberValue(); + if (xVal === 0) { + return new Value('0'); + } + if (xVal < 0) { + const str = X(NumberValue.toString(new Value(-xVal))).stringValue(); + return new Value(`-${str}`); + } + if (x.isInfinity()) { + return new Value('Infinity'); + } + // TODO: implement properly + return new Value(`${xVal}`); + } +} + +NumberValue.unit = new NumberValue(1); + +// #sec-numberbitwiseop +function NumberBitwiseOp(op, x, y) { + // 1. Let lnum be ! ToInt32(x). + const lnum = X(ToInt32(x)); + // 2. Let rnum be ! ToUint32(y). + const rnum = X(ToUint32(y)); + // 3. Return the result of applying the bitwise operator op to lnum and rnum. The result is a signed 32-bit integer. + switch (op) { + case '&': + return new Value(lnum.numberValue() & rnum.numberValue()); // eslint-disable-line no-bitwise + case '|': + return new Value(lnum.numberValue() | rnum.numberValue()); // eslint-disable-line no-bitwise + case '^': + return new Value(lnum.numberValue() ^ rnum.numberValue()); // eslint-disable-line no-bitwise + default: + throw new OutOfRange('NumberBitwiseOp', op); + } +} + +// #sec-ecmascript-language-types-bigint-type +export class BigIntValue extends PrimitiveValue { + constructor(bigint) { + super(); + this.bigint = bigint; + } + + bigintValue() { + return this.bigint; + } + + isNaN() { + return false; + } + + isFinite() { + return true; + } + + // #sec-numeric-types-bigint-unaryMinus + static unaryMinus(x) { + if (x.bigintValue() === 0n) { + return new Value(0n); + } + return new Value(-x.bigintValue()); + } + + // #sec-numeric-types-bigint-bitwiseNOT + static bitwiseNOT(x) { + return new Value(-x.bigintValue() - 1n); + } + + // #sec-numeric-types-bigint-exponentiate + static exponentiate(base, exponent) { + // 1. If exponent < 0n, throw a RangeError exception. + if (exponent.bigintValue() < 0n) { + return surroundingAgent.Throw('RangeError', 'BigIntNegativeExponent'); + } + // 2. If base is 0n and exponent is 0n, return 1n. + if (base.bigintValue() === 0n && exponent.bigintValue() === 0n) { + return new Value(1n); + } + // 3. Return the BigInt value that represents the mathematical value of base raised to the power exponent. + return new Value(base.bigintValue() ** exponent.bigintValue()); + } + + // #sec-numeric-types-bigint-multiply + static multiply(x, y) { + return new Value(x.bigintValue() * y.bigintValue()); + } + + // #sec-numeric-types-bigint-divide + static divide(x, y) { + // 1. If y is 0n, throw a RangeError exception. + if (y.bigintValue() === 0n) { + return surroundingAgent.Throw('RangeError', 'BigIntDivideByZero'); + } + // 2. Let quotient be the mathematical value of x divided by y. + const quotient = x.bigintValue() / y.bigintValue(); + // 3. Return the BigInt value that represents quotient rounded towards 0 to the next integral value. + return new Value(quotient); + } + + // #sec-numeric-types-bigint-remainder + static remainder(n, d) { + // 1. If d is 0n, throw a RangeError exception. + if (d.bigintValue() === 0n) { + return surroundingAgent.Throw('RangeError', 'BigIntDivideByZero'); + } + // 2. If n is 0n, return 0n. + if (n.bigintValue() === 0n) { + return new Value(0n); + } + // 3. Let r be the BigInt defined by the mathematical relation r = n - (d × q) + // where q is a BigInt that is negative only if n/d is negative and positive + // only if n/d is positive, and whose magnitude is as large as possible without + // exceeding the magnitude of the true mathematical quotient of n and d. + const r = new Value(n.bigintValue() % d.bigintValue()); + // 4. Return r. + return r; + } + + // #sec-numeric-types-bigint-add + static add(x, y) { + return new Value(x.bigintValue() + y.bigintValue()); + } + + // #sec-numeric-types-bigint-subtract + static subtract(x, y) { + return new Value(x.bigintValue() - y.bigintValue()); + } + + // #sec-numeric-types-bigint-leftShift + static leftShift(x, y) { + return new Value(x.bigintValue() << y.bigintValue()); // eslint-disable-line no-bitwise + } + + // #sec-numeric-types-bigint-signedRightShift + static signedRightShift(x, y) { + // 1. Return BigInt::leftShift(x, -y). + return BigIntValue.leftShift(x, new Value(-y.bigintValue())); + } + + // #sec-numeric-types-bigint-unsignedRightShift + static unsignedRightShift(_x, _y) { + return surroundingAgent.Throw('TypeError', 'BigIntUnsignedRightShift'); + } + + // #sec-numeric-types-bigint-lessThan + static lessThan(x, y) { + return x.bigintValue() < y.bigintValue() ? Value.true : Value.false; + } + + // #sec-numeric-types-bigint-equal + static equal(x, y) { + // Return true if x and y have the same mathematical integer value and false otherwise. + return x.bigintValue() === y.bigintValue() ? Value.true : Value.false; + } + + // #sec-numeric-types-bigint-sameValue + static sameValue(x, y) { + // 1. Return BigInt::equal(x, y). + return BigIntValue.equal(x, y); + } + + // #sec-numeric-types-bigint-sameValueZero + static sameValueZero(x, y) { + // 1. Return BigInt::equal(x, y). + return BigIntValue.equal(x, y); + } + + // #sec-numeric-types-bigint-bitwiseAND + static bitwiseAND(x, y) { + // 1. Return BigIntBitwiseOp("&", x, y). + return BigIntBitwiseOp('&', x.bigintValue(), y.bigintValue()); + } + + // #sec-numeric-types-bigint-bitwiseXOR + static bitwiseXOR(x, y) { + // 1. Return BigIntBitwiseOp("^", x, y). + return BigIntBitwiseOp('^', x.bigintValue(), y.bigintValue()); + } + + // #sec-numeric-types-bigint-bitwiseOR + static bitwiseOR(x, y) { + // 1. Return BigIntBitwiseOp("|", x, y); + return BigIntBitwiseOp('|', x.bigintValue(), y.bigintValue()); + } + + // #sec-numeric-types-bigint-tostring + static toString(x) { + // 1. If x is less than zero, return the string-concatenation of the String "-" and ! BigInt::toString(-x). + if (x.bigintValue() < 0n) { + const str = X(BigIntValue.toString(new Value(-x.bigintValue()))).stringValue(); + return new Value(`-${str}`); + } + // 2. Return the String value consisting of the code units of the digits of the decimal representation of x. + return new Value(`${x.bigintValue()}`); + } +} + +BigIntValue.unit = new BigIntValue(1n); + +/* +// #sec-binaryand +function BinaryAnd(x, y) { + // 1. Assert: x is 0 or 1. + Assert(x === 0n || x === 1n); + // 2. Assert: y is 0 or 1. + Assert(x === 0n || x === 1n); + // 3. If x is 1 and y is 1, return 1. + if (x === 1n && y === 1n) { + return 1n; + } else { + // 4. Else, return 0. + return 0n; + } +} + +// #sec-binaryor +function BinaryOr(x, y) { + // 1. Assert: x is 0 or 1. + Assert(x === 0n || x === 1n); + // 2. Assert: y is 0 or 1. + Assert(x === 0n || x === 1n); + // 3. If x is 1 or y is 1, return 1. + if (x === 1n || y === 1n) { + return 1n; + } else { + // 4. Else, return 0. + return 0n; + } +} + +// #sec-binaryxor +function BinaryXor(x, y) { + // 1. Assert: x is 0 or 1. + Assert(x === 0n || x === 1n); + // 2. Assert: y is 0 or 1. + Assert(x === 0n || x === 1n); + // 3. If x is 1 and y is 0, return 1. + if (x === 1n && y === 0n) { + return 1n; + } else if (x === 0n && y === 1n) { + // Else if x is 0 and y is 1, return 1. + return 1n; + } else { + // 4. Else, return 0. + return 0n; + } +} +*/ + +// #sec-bigintbitwiseop +function BigIntBitwiseOp(op, x, y) { + // TODO: figure out why this doesn't work, probably the modulo. + /* + // 1. Assert: op is "&", "|", or "^". + Assert(['&', '|', '^'].includes(op)); + // 2. Let result be 0n. + let result = 0n; + // 3. Let shift be 0. + let shift = 0n; + // 4. Repeat, until (x = 0 or x = -1) and (y = 0 or y = -1), + while (!((x === 0n || x === -1n) && (y === 0n || y === -1n))) { + // a. Let xDigit be x modulo 2. + const xDigit = x % 2n; + // b. Let yDigit be y modulo 2. + const yDigit = y % 2n; + // c. If op is "&", set result to result + 2^shift × BinaryAnd(xDigit, yDigit). + if (op === '&') { + result += (2n ** shift) * BinaryAnd(xDigit, yDigit); + } else if (op === '|') { + // d. Else if op is "|", set result to result + 2shift × BinaryOr(xDigit, yDigit). + result += (2n ** shift) * BinaryXor(xDigit, yDigit); + } else { + // i. Assert: op is "^". + Assert(op === '^'); + // ii. Set result to result + 2^shift × BinaryXor(xDigit, yDigit). + result += (2n ** shift) * BinaryXor(xDigit, yDigit); + } + // f. Set shift to shift + 1. + shift += 1n; + // g. Set x to (x - xDigit) / 2. + x = (x - xDigit) / 2n; + // h. Set y to (y - yDigit) / 2. + y = (y - yDigit) / 2n; + } + let tmp; + // 5. If op is "&", let tmp be BinaryAnd(x modulo 2, y modulo 2). + if (op === '&') { + tmp = BinaryAnd(x % 2n, y % 2n); + } else if (op === '|') { + // 6. Else if op is "|", let tmp be BinaryOr(x modulo 2, y modulo 2). + tmp = BinaryOr(x % 2n, y % 2n); + } else { + // a. Assert: op is "^". + Assert(op === '^'); + // b. Let tmp be BinaryXor(x modulo 2, y modulo 2). + tmp = BinaryXor(x % 2n, y % 2n); + } + // 8. If tmp ≠ 0, then + if (tmp !== 0n) { + // a. Set result to result - 2^shift. NOTE: This extends the sign. + result -= 2n ** shift; + } + // 9. Return result. + return new Value(result); + */ + switch (op) { + case '&': + return new Value(x & y); // eslint-disable-line no-bitwise + case '|': + return new Value(x | y); // eslint-disable-line no-bitwise + case '^': + return new Value(x ^ y); // eslint-disable-line no-bitwise + default: + throw new OutOfRange('BigIntBitwiseOp', op); + } +} + +// #sec-object-type +export class ObjectValue extends Value { + constructor(internalSlotsList) { + super(); + + this.properties = new ValueMap(); + this.internalSlotsList = internalSlotsList; + } + + GetPrototypeOf() { + return OrdinaryGetPrototypeOf(this); + } + + SetPrototypeOf(V) { + return OrdinarySetPrototypeOf(this, V); + } + + IsExtensible() { + return OrdinaryIsExtensible(this); + } + + PreventExtensions() { + return OrdinaryPreventExtensions(this); + } + + GetOwnProperty(P) { + return OrdinaryGetOwnProperty(this, P); + } + + DefineOwnProperty(P, Desc) { + return OrdinaryDefineOwnProperty(this, P, Desc); + } + + HasProperty(P) { + return OrdinaryHasProperty(this, P); + } + + Get(P, Receiver) { + return OrdinaryGet(this, P, Receiver); + } + + Set(P, V, Receiver) { + return OrdinarySet(this, P, V, Receiver); + } + + Delete(P) { + return OrdinaryDelete(this, P); + } + + OwnPropertyKeys() { + return OrdinaryOwnPropertyKeys(this); + } + + // NON-SPEC + mark(m) { + m(this.properties); + this.internalSlotsList.forEach((s) => { + m(this[s]); + }); + } +} + +export class Reference { + constructor({ BaseValue, ReferencedName, StrictReference }) { + this.BaseValue = BaseValue; + this.ReferencedName = ReferencedName; + Assert(Type(StrictReference) === 'Boolean'); + this.StrictReference = StrictReference; + } + + // NON-SPEC + mark(m) { + m(this.BaseValue); + m(this.ReferencedName); + } +} + +export class SuperReference extends Reference { + constructor({ + BaseValue, + ReferencedName, + thisValue, + StrictReference, + }) { + super({ BaseValue, ReferencedName, StrictReference }); + this.thisValue = thisValue; + } + + // NON-SPEC + mark(m) { + super.mark(m); + m(this.thisValue); + } +} + +export function Descriptor(O) { + if (new.target === Descriptor) { + this.Value = O.Value; + this.Get = O.Get; + this.Set = O.Set; + this.Writable = O.Writable; + this.Enumerable = O.Enumerable; + this.Configurable = O.Configurable; + } else { + return new Descriptor(O); + } +} + +Descriptor.prototype.everyFieldIsAbsent = function everyFieldIsAbsent() { + return this.Value === undefined + && this.Get === undefined + && this.Set === undefined + && this.Writable === undefined + && this.Enumerable === undefined + && this.Configurable === undefined; +}; + +// NON-SPEC +Descriptor.prototype.mark = function mark(m) { + m(this.Value); + m(this.Get); + m(this.Set); +}; + +export class DataBlock extends Uint8Array { + constructor(sizeOrBuffer, ...restArgs) { + if (sizeOrBuffer instanceof ArrayBuffer) { + // fine. + super(sizeOrBuffer, ...restArgs); + } else { + Assert(typeof sizeOrBuffer === 'number'); + super(sizeOrBuffer); + } + } +} + +export function Type(val) { + if (val instanceof UndefinedValue) { + return 'Undefined'; + } + + if (val instanceof NullValue) { + return 'Null'; + } + + if (val instanceof BooleanValue) { + return 'Boolean'; + } + + if (val instanceof StringValue) { + return 'String'; + } + + if (val instanceof NumberValue) { + return 'Number'; + } + + if (val instanceof BigIntValue) { + return 'BigInt'; + } + + if (val instanceof SymbolValue) { + return 'Symbol'; + } + + if (val instanceof ObjectValue) { + return 'Object'; + } + + if (val instanceof Reference) { + return 'Reference'; + } + + if (val instanceof Completion) { + return 'Completion'; + } + + if (val instanceof EnvironmentRecord) { + return 'EnvironmentRecord'; + } + + if (val instanceof Descriptor) { + return 'Descriptor'; + } + + if (val instanceof DataBlock) { + return 'Data Block'; + } + + throw new OutOfRange('Type', val); +} + +// Used for Type(x)::y for numerics +export function TypeNumeric(val) { + if (val instanceof NumberValue) { + return NumberValue; + } + + if (val instanceof BigIntValue) { + return BigIntValue; + } + + throw new OutOfRange('TypeNumeric', val); +} diff --git a/engine262/test/base.js b/engine262/test/base.js new file mode 100644 index 0000000..63a1ce4 --- /dev/null +++ b/engine262/test/base.js @@ -0,0 +1,110 @@ +'use strict'; + +const readline = require('readline'); +const os = require('os'); + +process.on('unhandledRejection', (reason) => { + require('fs').writeSync(0, `\n${require('util').inspect(reason)}\n`); + process.exit(1); +}); + +const CI = !!process.env.CONTINUOUS_INTEGRATION; + +const ANSI = CI ? { + reset: '', + red: '', + green: '', + yellow: '', + blue: '', +} : { + reset: '\u001b[0m', + red: '\u001b[31m', + green: '\u001b[32m', + yellow: '\u001b[33m', + blue: '\u001b[34m', +}; + +const CPU_COUNT = os.cpus().length; + +let skipped = 0; +let passed = 0; +let failed = 0; +let total = 0; + +const start = Date.now(); + +const handledPerSecLast5 = []; +const pad = (n, l, c = '0') => n.toString().padStart(l, c); +const average = (array) => (array.reduce((a, b) => a + b, 0) / array.length) || 0; + +const printStatusLine = () => { + const elapsed = Math.floor((Date.now() - start) / 1000); + const min = Math.floor(elapsed / 60); + const sec = elapsed % 60; + + const time = `${pad(min, 2)}:${pad(sec, 2)}`; + const found = `${ANSI.blue}:${pad(total, 5, ' ')}${ANSI.reset}`; + const p = `${ANSI.green}+${pad(passed, 5, ' ')}${ANSI.reset}`; + const f = `${ANSI.red}-${pad(failed, 5, ' ')}${ANSI.reset}`; + const s = `${ANSI.yellow}»${pad(skipped, 5, ' ')}${ANSI.reset}`; + const testsPerSec = average(handledPerSecLast5); + + const line = `[${time}|${found}|${p}|${f}|${s}] (${testsPerSec.toFixed(2)}/s)`; + + if (!CI) { + readline.clearLine(process.stdout, 0); + readline.cursorTo(process.stdout, 0); + } + process.stdout.write(`${line}${CI ? '\n' : ''}`); +}; + +let handledPerSecCounter = 0; + +setInterval(() => { + handledPerSecLast5.unshift(handledPerSecCounter); + handledPerSecCounter = 0; + if (handledPerSecLast5.length > 5) { + handledPerSecLast5.length = 5; + } +}, 1000).unref(); + +module.exports = { + total() { + total += 1; + }, + pass() { + passed += 1; + handledPerSecCounter += 1; + }, + fail(name, error) { + failed += 1; + handledPerSecCounter += 1; + process.exitCode = 1; + process.stderr.write(`\nFAILURE! ${name}\n${error}\n`); + }, + skip() { + skipped += 1; + handledPerSecCounter += 1; + }, + CPU_COUNT, + CI, +}; + +process.stdout.write(` +####################### + engine262 Test Runner + Detected ${CPU_COUNT} CPUs + ${CI ? 'Running' : 'Not running'} on CI +####################### + +`); + +printStatusLine(); +setInterval(() => { + printStatusLine(); +}, CI ? 5000 : 500).unref(); + +process.on('exit', () => { + printStatusLine(); + process.stdout.write('\n'); +}); diff --git a/engine262/test/eslint-plugin-engine262/index.js b/engine262/test/eslint-plugin-engine262/index.js new file mode 100644 index 0000000..5e115c8 --- /dev/null +++ b/engine262/test/eslint-plugin-engine262/index.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + rules: { + 'no-use-in-def': require('./no-use-in-def'), + 'valid-feature': require('./valid-feature'), + 'valid-throw': require('./valid-throw'), + }, +}; diff --git a/engine262/test/eslint-plugin-engine262/no-use-in-def.js b/engine262/test/eslint-plugin-engine262/no-use-in-def.js new file mode 100644 index 0000000..4233b32 --- /dev/null +++ b/engine262/test/eslint-plugin-engine262/no-use-in-def.js @@ -0,0 +1,68 @@ +'use strict'; + +// https://github.com/eslint/eslint/blob/master/lib/rules/no-use-before-define.js + +const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; +const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; + +function isInRange(node, location) { + return node && node.range[0] <= location && location <= node.range[1]; +} + +function isUsedInDef(reference) { + const variable = reference.resolved; + if (!variable || variable.scope !== reference.from) { + return false; + } + + let node = variable.identifiers[0].parent; + const location = reference.identifier.range[1]; + + while (node) { + if (node.type === 'VariableDeclarator') { + if (isInRange(node.init, location)) { + return true; + } + if (FOR_IN_OF_TYPE.test(node.parent.parent.type) && isInRange(node.parent.parent.right, location)) { + return true; + } + break; + } + + if (node.type === 'AssignmentPattern' && isInRange(node.right, location)) { + return true; + } + + if (SENTINEL_TYPE.test(node.type)) { + break; + } + + node = node.parent; + } + + return false; +} + +module.exports = { + create(context) { + function findVariablesInScope(scope) { + scope.references.forEach((reference) => { + if (isUsedInDef(reference)) { + context.report({ + node: reference.identifier, + message: '{{name}} was used in its own definition', + data: reference.identifier, + }); + } + }); + + scope.childScopes.forEach((s) => findVariablesInScope(s)); + } + + return { + Program() { + findVariablesInScope(context.getScope()); + }, + }; + }, +}; diff --git a/engine262/test/eslint-plugin-engine262/valid-feature.js b/engine262/test/eslint-plugin-engine262/valid-feature.js new file mode 100644 index 0000000..9f84159 --- /dev/null +++ b/engine262/test/eslint-plugin-engine262/valid-feature.js @@ -0,0 +1,32 @@ +'use strict'; + +let features; +try { + features = require('../..').FEATURES.map((f) => f.flag); +} catch {} + +function isFeatureCall(node) { + return node.callee.type === 'MemberExpression' + && node.callee.computed === false + && node.callee.property.name === 'feature'; +} + +module.exports = { + create(context) { + return { + CallExpression(node) { + if (!isFeatureCall(node)) { + return; + } + if (node.arguments.length !== 1) { + context.report(node, 'Invalid arguments passed to feature()'); + return; + } + const featureName = node.arguments[0].value; + if (!features.includes(featureName)) { + context.report(node.arguments[0], `'${featureName}' is not a valid feature. Check src/engine.mjs.`); + } + }, + }; + }, +}; diff --git a/engine262/test/eslint-plugin-engine262/valid-throw.js b/engine262/test/eslint-plugin-engine262/valid-throw.js new file mode 100644 index 0000000..f2e7034 --- /dev/null +++ b/engine262/test/eslint-plugin-engine262/valid-throw.js @@ -0,0 +1,63 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const acorn = require('acorn'); + +function isThrowCall(node) { + return node.callee.type === 'MemberExpression' + && node.callee.computed === false + && node.callee.object.type === 'Identifier' + && node.callee.object.name === 'surroundingAgent' + && node.callee.property.type === 'Identifier' + && node.callee.property.name === 'Throw'; +} + +const templates = {}; + +{ + const source = fs.readFileSync(path.join(__dirname, '../../src/messages.mjs'), 'utf8'); + const ast = acorn.parse(source, { ecmaVersion: 2020, sourceType: 'module' }); + + ast.body.forEach((n) => { + if (n.type !== 'ExportNamedDeclaration') { + return; + } + const [v] = n.declaration.declarations; + const name = v.id.name; + const length = v.init.params.length; + templates[name] = length; + }); +} + +module.exports = { + create(context) { + return { + CallExpression(node) { + if (!isThrowCall(node)) { + return; + } + if (node.arguments.length === 1 && node.arguments[0].type !== 'Literal') { + return; + } + const [type, template, ...templateArgs] = node.arguments; + if (!type || type.type !== 'Literal') { + context.report(node, 'Throw must use a valid error constructor'); + return; + } + if (!template || template.type !== 'Literal') { + context.report(node, 'Throw must use a valid message template'); + return; + } + const tfn = templates[template.value]; + if (tfn === undefined) { + context.report(template, `'${template.value}' is not a valid message template`); + return; + } + if (tfn !== templateArgs.length) { + context.report(node, `Template expects ${tfn} args`); + } + }, + }; + }, +}; diff --git a/engine262/test/json/json.js b/engine262/test/json/json.js new file mode 100644 index 0000000..dda5fe8 --- /dev/null +++ b/engine262/test/json/json.js @@ -0,0 +1,64 @@ +'use strict'; + +/* eslint-disable no-await-in-loop */ + +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); +const { + pass, fail, skip, total, +} = require('../base'); +const { + Agent, + setSurroundingAgent, + ManagedRealm, + AbruptCompletion, + inspect, +} = require('../..'); + +const BASE_DIR = path.resolve(__dirname, 'JSONTestSuite'); + +const agent = new Agent(); +setSurroundingAgent(agent); + +function test(filename) { + const realm = new ManagedRealm(); + + const source = fs.readFileSync(filename, 'utf8'); + + let result; + try { + result = realm.evaluateScript(`'use strict'; +const source = ${JSON.stringify(source)}; +JSON.parse(source); +`); + } catch { + // ... + } + + const testName = path.basename(filename); + + if (!result || result instanceof AbruptCompletion) { + if (testName.startsWith('n_')) { + pass(); + } else if (testName.startsWith('i_')) { + skip(); + } else { + fail(testName, inspect(result)); + } + } else { + if (testName.startsWith('n_')) { + fail(testName, 'JSON parsed but should have failed!'); + } else { + pass(); + } + } +} + +const tests = glob.sync(`${path.resolve(BASE_DIR, 'test_parsing')}/**/*.json`) + .concat(glob.sync(`${path.resolve(BASE_DIR, 'test_transform')}/**/*.json`)); + +tests.forEach((t) => { + total(); + test(t); +}); diff --git a/engine262/test/stepped.js b/engine262/test/stepped.js new file mode 100644 index 0000000..46a2f73 --- /dev/null +++ b/engine262/test/stepped.js @@ -0,0 +1,66 @@ +'use strict'; + +require('@snek/source-map-support/register'); +const { + isMainThread, parentPort, workerData, Worker, +} = require('worker_threads'); +const fs = require('fs'); +// eslint-disable-next-line import/no-extraneous-dependencies +const { codeFrameColumns } = require('@babel/code-frame'); + +if (isMainThread) { + const shared = new SharedArrayBuffer(4); + const shared32 = new Int32Array(shared); + const source = fs.readFileSync(process.argv[2], 'utf8'); + const worker = new Worker(__filename, { + workerData: { shared, source }, + }); + process.stdin.on('data', () => { + const old = Atomics.compareExchange(shared32, 0, 0, 1); + if (old === 0) { + Atomics.notify(shared32, 0, 1); + } + }); + worker.on('message', (data) => { + const node = JSON.parse(data); + const frame = codeFrameColumns(source, node.location, { + highlightCode: true, + message: node.type, + }); + process.stdout.write(`${frame}\n\n\n`); + }); + worker.on('exit', () => { + process.exit(0); + }); +} else { + const { + Agent, + setSurroundingAgent, + ManagedRealm, + AbruptCompletion, + inspect, + } = require('..'); + + const shared32 = new Int32Array(workerData.shared); + setSurroundingAgent(new Agent({ + onNodeEvaluation(node) { + if (node.type === 'ExpressionStatement') { + return; + } + parentPort.postMessage(JSON.stringify(node)); + Atomics.wait(shared32, 0, 0); + Atomics.store(shared32, 0, 0); + }, + })); + + const realm = new ManagedRealm(); + + realm.scope(() => { + const completion = realm.evaluateScript(workerData.source); + if (completion instanceof AbruptCompletion) { + process.stdout.write(`${inspect(completion, realm)}\n`); + } + }); + + process.exit(0); +} diff --git a/engine262/test/supplemental.js b/engine262/test/supplemental.js new file mode 100644 index 0000000..62489f7 --- /dev/null +++ b/engine262/test/supplemental.js @@ -0,0 +1,267 @@ +'use strict'; + +require('@snek/source-map-support/register'); +const assert = require('assert'); +const { + Agent, + setSurroundingAgent, + ManagedRealm, + Value, + FEATURES, + Get, + CreateArrayFromList, + CreateDataProperty, +} = require('..'); +const test262realm = require('../bin/test262_realm'); +const { total, pass, fail } = require('./base'); + +// Features that cannot be tested by test262 should go here. + +[ + () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript('debugger;'); + assert.strictEqual(result.Value, Value.undefined); + }, + () => { + const agent = new Agent({ + onDebugger() { + return new Value(42); + }, + }); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript('debugger;'); + assert.strictEqual(result.Value.numberValue(), 42); + }, + () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(`\ +function x() { throw new Error('owo'); } +function y() { x(); } +try { + y(); +} catch (e) { + e.stack; +} +`); + assert.strictEqual(result.Value.stringValue(), `\ +Error: owo + at x (:1:32) + at y (:2:16) + at :4:3`); + }, + () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(`\ +async function x() { await 1; throw new Error('owo'); } +async function y() { await x(); } +y().catch((e) => e.stack); +`); + assert.strictEqual(result.Value.PromiseResult.stringValue(), `\ +Error: owo + at async x (:1:47) + at async y (:2:28)`); + }, + () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(`\ +function x() { Reflect.get(); } +try { + x(); +} catch (e) { + e.stack; +} +`); + assert.strictEqual(result.Value.stringValue(), `\ +TypeError: undefined is not an object + at get (native) + at x (:1:16) + at :3:3`); + }, + () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(`\ +function Y() { throw new Error('owo'); } +function x() { new Y(); } +try { + x(); +} catch (e) { + e.stack; +} +`); + assert.strictEqual(result.Value.stringValue(), `\ +Error: owo + at new Y (:1:32) + at x (:2:20) + at :4:3`); + }, + () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(`\ +let e; +new Promise(() => { + e = new Error('owo'); +}); +e.stack; +`); + assert.strictEqual(result.Value.stringValue(), `\ +Error: owo + at (:3:17) + at new Promise (native) + at :2:13`); + }, + () => { + const agent = new Agent({ + features: ['WeakRefs'], + }); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(` + const w = new WeakRef({}); + Promise.resolve() + .then(() => { + if (typeof w.deref() !== 'object') { + throw new Error(); + } + }) + .then(() => { + if (typeof w.deref() !== 'undefined') { + throw new Error(); + } + }) + .then(() => 'pass'); + `); + assert.strictEqual(result.Value.PromiseResult.stringValue(), 'pass'); + }, + () => { + const agent = new Agent({ + features: ['WeakRefs'], + }); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + realm.scope(() => { + const module = realm.createSourceTextModule('test.mjs', ` + const w = new WeakRef({}); + globalThis.result = Promise.resolve() + .then(() => { + if (typeof w.deref() !== 'object') { + throw new Error('should be object'); + } + }) + .then(() => { + if (typeof w.deref() !== 'undefined') { + throw new Error('should be undefined'); + } + }) + .then(() => 'pass'); + `); + module.Link(); + module.Evaluate(); + const result = Get(realm.GlobalObject, new Value('result')); + assert.strictEqual(result.Value.PromiseResult.stringValue(), 'pass'); + }); + }, + () => { + const agent = new Agent({ + features: FEATURES.map((f) => f.name), + }); + setSurroundingAgent(agent); + const { realm } = test262realm.createRealm(); + realm.scope(() => { + CreateDataProperty( + realm.GlobalObject, + new Value('fail'), + new Value(([path]) => { + throw new Error(`${path.stringValue()} did not have a section`); + }), + ); + const targets = []; + Object.entries(realm.Intrinsics) + .forEach(([k, v]) => { + targets.push(CreateArrayFromList([new Value(k), v])); + }); + CreateDataProperty( + realm.GlobalObject, + new Value('targets'), + CreateArrayFromList(targets), + ); + }); + const result = realm.evaluateScript(` +'use strict'; + +{ + const targets = globalThis.targets; + delete globalThis.targets; + const fail = globalThis.fail; + delete globalThis.fail; + + const topQueue = new Set(); + const scanned = new Set(); + const scan = (ns, path) => { + if (scanned.has(ns)) { + return; + } + scanned.add(ns); + if (typeof ns === 'function') { + if ($262.spec(ns) === undefined) { + fail(path); + } + } + if (typeof ns !== 'function' && (typeof ns !== 'object' || ns === null)) { + return; + } + + const descriptors = Object.getOwnPropertyDescriptors(ns); + Reflect.ownKeys(descriptors) + .forEach((name) => { + const desc = descriptors[name]; + const p = typeof name === 'symbol' + ? path + '[Symbol(' + name.description + ')]' + : path + '.' + name; + if ('value' in desc) { + if (!topQueue.has(desc.value)) { + scan(desc.value, p); + } + } else { + if (!topQueue.has(desc.get)) { + scan(desc.get, p); + } + if (!topQueue.has(desc.set)) { + scan(desc.set, p); + } + } + }); + }; + + targets.forEach((t) => { + topQueue.add(t[1]); + }); + targets.forEach((t) => { + scan(t[1], t[0]); + }); +} + `); + assert.strictEqual(result.Value, Value.undefined); + }, +].forEach((test) => { + total(); + try { + test(); + pass(); + } catch (e) { + fail('', e.stack || e); + } +}); diff --git a/engine262/test/test262/features b/engine262/test/test262/features new file mode 100644 index 0000000..aeb9358 --- /dev/null +++ b/engine262/test/test262/features @@ -0,0 +1,35 @@ +# https://github.com/tc39/test262/blob/master/features.txt + +# Start with `-` to skip feature +# Map feature to engine262 feature using `feature = engine262featurename` + +-Atomics +-Atomics.waitAsync + +-caller + +-SharedArrayBuffer + +-tail-call-optimization + +-class-fields-public +-class-fields-private +-class-methods-private +-class-static-fields-public +-class-static-fields-private +-class-static-methods-private + +# https://github.com/tc39/proposal-top-level-await +top-level-await = top-level-await + +# https://github.com/tc39/proposal-hashbang +hashbang = hashbang + +# https://github.com/tc39/proposal-numeric-separator +numeric-separator-literal = numeric-separators + +# https://github.com/tc39/proposal-regexp-match-indices +regexp-match-indices = regexp-match-indices + +# https://github.com/tc39/proposal-cleanup-some +cleanupSome = cleanup-some diff --git a/engine262/test/test262/skiplist b/engine262/test/test262/skiplist new file mode 100644 index 0000000..e0a0883 --- /dev/null +++ b/engine262/test/test262/skiplist @@ -0,0 +1,59 @@ +##################### +### Skipped Tests ### +##################### + +# Comments start with `#`. +# Paths are relative to the `test262/test` directory. +# Paths may contain globs. + +# Spec bug +built-ins/Date/prototype/setMonth/this-value-invalid-date.js + +# Spec bug +built-ins/Function/prototype/bind/instance-length-tointeger.js + +# https://github.com/tc39/test262/issues/427 +language/expressions/prefix-increment/S11.4.4_A5_T4.js +language/expressions/prefix-increment/S11.4.4_A5_T5.js +language/expressions/postfix-increment/S11.3.1_A5_T4.js +language/expressions/postfix-increment/S11.3.1_A5_T5.js +language/expressions/prefix-decrement/S11.4.5_A5_T4.js +language/expressions/prefix-decrement/S11.4.5_A5_T5.js +language/expressions/postfix-decrement/S11.3.2_A5_T4.js +language/expressions/postfix-decrement/S11.3.2_A5_T5.js +language/expressions/compound-assignment/S11.13.2_A5.1_T4.js +language/expressions/compound-assignment/S11.13.2_A5.4_T4.js +language/expressions/compound-assignment/S11.13.2_A5.7_T4.js +language/expressions/compound-assignment/S11.13.2_A5.10_T4.js +language/expressions/compound-assignment/S11.13.2_A5.11_T4.js +language/expressions/compound-assignment/S11.13.2_A5.2_T4.js +language/expressions/compound-assignment/S11.13.2_A5.3_T4.js +language/expressions/compound-assignment/S11.13.2_A5.5_T4.js +language/expressions/compound-assignment/S11.13.2_A5.6_T4.js +language/expressions/compound-assignment/S11.13.2_A5.9_T4.js +language/expressions/compound-assignment/S11.13.2_A5.8_T4.js +language/expressions/compound-assignment/S11.13.2_A5.1_T5.js +language/expressions/compound-assignment/S11.13.2_A5.2_T5.js +language/expressions/compound-assignment/S11.13.2_A5.3_T5.js +language/expressions/compound-assignment/S11.13.2_A5.4_T5.js +language/expressions/compound-assignment/S11.13.2_A5.5_T5.js +language/expressions/compound-assignment/S11.13.2_A5.6_T5.js +language/expressions/compound-assignment/S11.13.2_A5.7_T5.js +language/expressions/compound-assignment/S11.13.2_A5.8_T5.js +language/expressions/compound-assignment/S11.13.2_A5.9_T5.js +language/expressions/compound-assignment/S11.13.2_A5.10_T5.js +language/expressions/compound-assignment/S11.13.2_A5.11_T5.js +language/expressions/assignment/S11.13.1_A5_T5.js +language/expressions/assignment/S11.13.1_A5_T4.js + +# https://github.com/tc39/ecma262/issues/1426 +built-ins/String/prototype/replace/S15.5.4.11_A3_T*.js + +# We need our own date parser +built-ins/Date/parse/without-utc-offset.js + +# TODO: RegExp +built-ins/RegExp/property-escapes/**/*.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-plus-quantifier.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-plus-quantifier.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-plus-quantifier.js diff --git a/engine262/test/test262/slowlist b/engine262/test/test262/slowlist new file mode 100644 index 0000000..f0e589a --- /dev/null +++ b/engine262/test/test262/slowlist @@ -0,0 +1,26 @@ +################## +### Slow Tests ### +################## + +# Comments start with `#`. +# Paths are relative to the `test262/test` directory. +# Paths may contain globs. + +built-ins/RegExp/character-class-escape-non-whitespace.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-flags-u.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-plus-quantifier-flags-u.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-flags-u.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-plus-quantifier-flags-u.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-flags-u.js +built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-plus-quantifier-flags-u.js +language/literals/regexp/S7.8.5_A1.1_T2.js +language/literals/regexp/S7.8.5_A1.4_T2.js +language/literals/regexp/S7.8.5_A2.1_T2.js +language/literals/regexp/S7.8.5_A2.4_T2.js + +language/comments/S7.4_A5.js +language/comments/S7.4_A6.js + +built-ins/{encode,decode}URI?(Component)/**/*.js + +built-ins/Function/prototype/toString/built-in-function-object.js diff --git a/engine262/test/test262/test262.js b/engine262/test/test262/test262.js new file mode 100644 index 0000000..4b7b6aa --- /dev/null +++ b/engine262/test/test262/test262.js @@ -0,0 +1,324 @@ +'use strict'; + +try { + require('@snek/source-map-support/register'); +} catch {} + +const path = require('path'); +const fs = require('fs'); +const util = require('util'); +const glob = require('glob'); + +const readList = (name) => { + const source = fs.readFileSync(path.resolve(__dirname, name), 'utf8'); + return source.split('\n').filter((l) => l && !l.startsWith('#')); +}; +const readListPaths = (name) => readList(name) + .flatMap((t) => glob.sync(path.resolve(__dirname, 'test262', 'test', t))) + .map((f) => path.relative(path.resolve(__dirname, 'test262'), f)); + +const disabledFeatures = []; +const featureMap = {}; +readList('features') + .forEach((f) => { + if (f.startsWith('-')) { + disabledFeatures.push(f.slice(1)); + } + if (f.includes('=')) { + const [k, v] = f.split('='); + featureMap[k.trim()] = v.trim(); + } + }); + +if (!process.send) { + // supervisor + + const childProcess = require('child_process'); + const TestStream = require('test262-stream'); + + const { + pass, + fail, + skip, + total, + CPU_COUNT, + } = require('../base'); + + const override = process.argv.find((e, i) => i > 1 && !e.startsWith('-')); + const NUM_WORKERS = process.env.NUM_WORKERS + ? Number.parseInt(process.env.NUM_WORKERS, 10) + : Math.round(CPU_COUNT * 0.75); + const RUN_SLOW_TESTS = process.argv.includes('--run-slow-tests'); + + const createWorker = () => { + const c = childProcess.fork(__filename); + c.on('message', (message) => { + const { description, status, error } = message; + switch (status) { + case 'PASS': + pass(); + break; + case 'FAIL': + fail(description, error); + break; + case 'SKIP': + skip(); + break; + default: + throw new RangeError(JSON.stringify(message)); + } + }); + c.on('exit', (code) => { + if (code !== 0) { + process.exit(1); + } + }); + return c; + }; + + const workers = Array.from({ length: NUM_WORKERS }, () => createWorker()); + let longRunningWorker; + if (RUN_SLOW_TESTS) { + longRunningWorker = createWorker(); + } + + const slowlist = readListPaths('slowlist'); + const skiplist = readListPaths('skiplist'); + + const stream = new TestStream(path.resolve(__dirname, 'test262'), { + paths: [override || 'test'], + omitRuntime: true, + }); + + let workerIndex = 0; + stream.on('data', (test) => { + if (test.attrs.flags.module && test.scenario !== 'default') { + // test262-stream duplicates module tests, deduplicate here + return; + } + + if (/annexB|intl402/.test(test.file)) { + return; + } + + total(); + + if ((test.attrs.features && test.attrs.features.some((feature) => disabledFeatures.includes(feature))) + || skiplist.includes(test.file)) { + skip(); + return; + } + + if (slowlist.includes(test.file)) { + if (RUN_SLOW_TESTS) { + longRunningWorker.send(test); + } else { + skip(); + } + } else { + workers[workerIndex].send(test); + workerIndex += 1; + if (workerIndex >= workers.length) { + workerIndex = 0; + } + } + }); + + stream.on('end', () => { + workers.forEach((w) => { + w.send('DONE'); + }); + if (RUN_SLOW_TESTS) { + longRunningWorker.send('DONE'); + } + }); +} else { + // worker + + const { + Agent, + setSurroundingAgent, + inspect, + + Value, + + IsCallable, + IsDataDescriptor, + Type, + + AbruptCompletion, + Throw, + } = require('../..'); + const { createRealm } = require('../../bin/test262_realm'); + + const isError = (type, value) => { + if (Type(value) !== 'Object') { + return false; + } + const proto = value.Prototype; + if (!proto || Type(proto) !== 'Object') { + return false; + } + const ctorDesc = proto.properties.get(new Value('constructor')); + if (!ctorDesc || !IsDataDescriptor(ctorDesc)) { + return false; + } + const ctor = ctorDesc.Value; + if (Type(ctor) !== 'Object' || IsCallable(ctor) !== Value.true) { + return false; + } + const namePropDesc = ctor.properties.get(new Value('name')); + if (!namePropDesc || !IsDataDescriptor(namePropDesc)) { + return false; + } + const nameProp = namePropDesc.Value; + return Type(nameProp) === 'String' && nameProp.stringValue() === type; + }; + + const includeCache = {}; + + const run = (test) => { + const features = []; + if (test.attrs.features) { + test.attrs.features.forEach((f) => { + if (featureMap[f]) { + features.push(featureMap[f]); + } + }); + } + const agent = new Agent({ + features, + }); + setSurroundingAgent(agent); + + const { + realm, trackedPromises, + resolverCache, setPrintHandle, + } = createRealm({ file: test.file }); + const r = realm.scope(() => { + test.attrs.includes.unshift('assert.js', 'sta.js'); + if (test.attrs.flags.async) { + test.attrs.includes.unshift('doneprintHandle.js'); + } + + for (const include of test.attrs.includes) { + if (includeCache[include] === undefined) { + const p = path.resolve(__dirname, `./test262/harness/${include}`); + includeCache[include] = { + source: fs.readFileSync(p, 'utf8'), + specifier: p, + }; + } + const entry = includeCache[include]; + const completion = realm.evaluateScript(entry.source, { specifier: entry.specifier }); + if (completion instanceof AbruptCompletion) { + return { status: 'FAIL', error: inspect(completion) }; + } + } + + { + const completion = realm.evaluateScript(`\ +var Test262Error = class Test262Error extends Error {}; + +function $DONE(error) { + if (error) { + if (typeof error === 'object' && error !== null && 'stack' in error) { + __consolePrintHandle__('Test262:AsyncTestFailure:' + error.stack); + } else { + __consolePrintHandle__('Test262:AsyncTestFailure:Test262Error: ' + error); + } + } else { + __consolePrintHandle__('Test262:AsyncTestComplete'); + } +}`); + if (completion instanceof AbruptCompletion) { + return { status: 'FAIL', error: inspect(completion) }; + } + } + + let asyncResult; + if (test.attrs.flags.async) { + setPrintHandle((m) => { + if (m.stringValue && m.stringValue() === 'Test262:AsyncTestComplete') { + asyncResult = { status: 'PASS' }; + } else { + asyncResult = { status: 'FAIL', error: m.stringValue ? m.stringValue() : inspect(m) }; + } + setPrintHandle(undefined); + }); + } + + const specifier = path.resolve(__dirname, 'test262', test.file); + + let completion; + if (test.attrs.flags.module) { + completion = realm.createSourceTextModule(specifier, test.contents); + if (!(completion instanceof AbruptCompletion)) { + const module = completion; + resolverCache.set(specifier, module); + completion = module.Link(); + if (!(completion instanceof AbruptCompletion)) { + completion = module.Evaluate(); + } + if (!(completion instanceof AbruptCompletion)) { + if (completion.PromiseState === 'rejected') { + completion = Throw(completion.PromiseResult); + } + } + } + } else { + completion = realm.evaluateScript(test.contents, { specifier }); + } + + if (completion instanceof AbruptCompletion) { + if (test.attrs.negative && isError(test.attrs.negative.type, completion.Value)) { + return { status: 'PASS' }; + } else { + return { status: 'FAIL', error: inspect(completion) }; + } + } + + if (test.attrs.flags.async) { + if (!asyncResult) { + throw new Error('missing async result'); + } + return asyncResult; + } + + if (trackedPromises.length > 0) { + return { status: 'FAIL', error: inspect(trackedPromises[0]) }; + } + + if (test.attrs.negative) { + return { status: 'FAIL', error: `Expected ${test.attrs.negative.type} during ${test.attrs.negative.phase}` }; + } else { + return { status: 'PASS' }; + } + }); + + return r; + }; + + let p = Promise.resolve(); + const handleSendError = (e) => { + if (e) { + process.exit(1); + } + }; + process.on('message', (test) => { + if (test === 'DONE') { + p.then(() => process.exit(0)); + p = undefined; + } else { + const description = `${test.file}\n${test.attrs.description}`; + p = p + .then(() => run(test)) + .then((r) => { + process.send({ description, ...r }, handleSendError); + }) + .catch((e) => { + process.send({ description, status: 'FAIL', error: util.inspect(e) }, handleSendError); + }); + } + }); +} diff --git a/engine262/test/test_root.sh b/engine262/test/test_root.sh new file mode 100755 index 0000000..2003b26 --- /dev/null +++ b/engine262/test/test_root.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -x + +E=0 + +npm run test:test262 || E=$? +npm run test:json || E=$? +npm run test:supplemental || E=$? + +exit $E