From 6e92d2345e8231a44556ce7d416451f5f3e6c398 Mon Sep 17 00:00:00 2001 From: bendtherules Date: Fri, 27 Mar 2026 10:07:29 +0530 Subject: [PATCH] Squashed 'engine262/' content from commit ae71998 git-subtree-dir: engine262 git-subtree-split: ae71998cc5a8315700555135b1ac202a0d6d0b31 --- .eslintignore | 6 + .eslintrc.js | 117 + .git-blame-ignore-revs | 12 + .github/FUNDING.yml | 12 + .github/workflows/publish.yml | 62 + .github/workflows/test.yml | 48 + .gitignore | 14 + .gitmodules | 9 + .npmignore | 11 + .vscode/launch.json | 21 + .vscode/settings.json | 13 + CODE_OF_CONDUCT.md | 46 + LICENSE | 19 + README.md | 153 + babel.config.json | 3 + bin/engine262.js | 5 + lib-src/inspector/context.mts | 405 + lib-src/inspector/index.mts | 139 + lib-src/inspector/inspect.mts | 624 ++ lib-src/inspector/internal-utils.mts | 18 + lib-src/inspector/js_protocol.json | 3288 ++++++ lib-src/inspector/methods.mts | 355 + lib-src/inspector/tsconfig.json | 17 + lib-src/inspector/types.mts | 225 + lib-src/inspector/utils.mts | 81 + lib-src/node/bin.mts | 239 + lib-src/node/example.mts | 58 + lib-src/node/inspector.mts | 124 + lib-src/node/module.mts | 74 + lib-src/node/tsconfig.json | 21 + package-lock.json | 9250 +++++++++++++++++ package.json | 153 + scripts/Unicode/PropertyValueAliases.txt | 1710 +++ scripts/gen_regex_sets.mts | 136 + scripts/generate_error_message_hint.mts | 103 + scripts/rollup.config.mts | 177 + scripts/tag_version_with_git_hash.mts | 17 + scripts/transform.mts | 456 + scripts/tsconfig.json | 9 + src/abstract-ops/all.mts | 37 + src/abstract-ops/arguments-operations.mts | 245 + src/abstract-ops/array-objects.mts | 315 + src/abstract-ops/arraybuffer-objects.mts | 245 + .../async-function-operations.mts | 46 + src/abstract-ops/async-generator-objects.mts | 343 + src/abstract-ops/data-types-and-values.mts | 43 + src/abstract-ops/dataview-objects.mts | 149 + src/abstract-ops/date-objects.mts | 242 + src/abstract-ops/error-objects.mts | 38 + src/abstract-ops/execution-contexts.mts | 21 + src/abstract-ops/function-operations.mts | 759 ++ src/abstract-ops/generator-operations.mts | 329 + src/abstract-ops/global-object.mts | 392 + .../immutable-prototype-objects.mts | 21 + src/abstract-ops/import-calls.mts | 112 + src/abstract-ops/iterator-operations.mts | 319 + src/abstract-ops/keyed-collections.mts | 19 + src/abstract-ops/math.mts | 8 + .../module-namespace-exotic-objects.mts | 305 + src/abstract-ops/module-records.mts | 579 ++ src/abstract-ops/notational-conventions.mts | 61 + src/abstract-ops/object-operations.mts | 599 ++ src/abstract-ops/objects.mts | 453 + src/abstract-ops/private-names.mts | 170 + src/abstract-ops/promise-operations.mts | 448 + src/abstract-ops/proxy-objects.mts | 579 ++ src/abstract-ops/realms.mts | 200 + src/abstract-ops/reference-operations.mts | 211 + src/abstract-ops/regexp-objects.mts | 306 + src/abstract-ops/shadow-realm.mts | 216 + src/abstract-ops/shared-arraybuffer.mts | 8 + src/abstract-ops/spec-types.mts | 220 + src/abstract-ops/string-objects.mts | 156 + src/abstract-ops/symbol-objects.mts | 30 + src/abstract-ops/temporal/addition.mts | 274 + src/abstract-ops/temporal/all.mts | 12 + src/abstract-ops/temporal/calendar.mts | 724 ++ src/abstract-ops/temporal/duration.mts | 1126 ++ src/abstract-ops/temporal/instant.mts | 177 + src/abstract-ops/temporal/not-implemented.mts | 15 + src/abstract-ops/temporal/now.mts | 37 + src/abstract-ops/temporal/plain-date-time.mts | 235 + src/abstract-ops/temporal/plain-date.mts | 241 + src/abstract-ops/temporal/plain-month-day.mts | 85 + src/abstract-ops/temporal/plain-time.mts | 326 + .../temporal/plain-year-month.mts | 193 + src/abstract-ops/temporal/temporal.mts | 877 ++ src/abstract-ops/temporal/time-zone.mts | 300 + src/abstract-ops/temporal/zoned-datetime.mts | 370 + src/abstract-ops/testing-comparison.mts | 380 + src/abstract-ops/type-conversion.mts | 564 + src/abstract-ops/typedarray-objects.mts | 396 + src/abstract-ops/weak-operations.mts | 20 + src/api.mts | 477 + src/completion.mts | 482 + src/ecma402/not-implemented.mts | 4 + src/evaluator.mts | 353 + src/execution-context/Agent.mts | 345 + src/execution-context/Environment.mts | 956 ++ src/execution-context/ExecutionContext.mts | 134 + src/execution-context/Job.mts | 54 + src/execution-context/PrivateEnvironment.mts | 41 + src/execution-context/Realm.mts | 363 + src/execution-context/WeakReference.mts | 77 + src/execution-context/all.mts | 7 + src/helpers.mts | 631 ++ src/host-defined/debugger-eval.mts | 128 + src/host-defined/debugger-util.mts | 15 + src/host-defined/engine.mts | 273 + src/host-defined/error-messages.mts | 281 + src/host-defined/inspect.mts | 250 + src/host-defined/test262-intrinsics.mts | 164 + src/index.mts | 48 + src/intrinsics/AggregateError.mts | 68 + src/intrinsics/AggregateErrorPrototype.mts | 12 + src/intrinsics/Array.mts | 326 + src/intrinsics/ArrayBuffer.mts | 49 + src/intrinsics/ArrayBufferPrototype.mts | 120 + src/intrinsics/ArrayIteratorPrototype.mts | 23 + src/intrinsics/ArrayPrototype.mts | 755 ++ src/intrinsics/ArrayPrototypeShared.mts | 691 ++ .../AsyncFromSyncIteratorPrototype.mts | 164 + src/intrinsics/AsyncFunction.mts | 33 + src/intrinsics/AsyncFunctionPrototype.mts | 8 + src/intrinsics/AsyncGeneratorFunction.mts | 39 + .../AsyncGeneratorFunctionPrototype.mts | 19 + ...yncGeneratorFunctionPrototypePrototype.mts | 148 + src/intrinsics/AsyncIteratorPrototype.mts | 17 + src/intrinsics/BigInt.mts | 69 + src/intrinsics/BigIntPrototype.mts | 83 + src/intrinsics/Boolean.mts | 47 + src/intrinsics/BooleanPrototype.mts | 59 + src/intrinsics/DataView.mts | 82 + src/intrinsics/DataViewPrototype.mts | 271 + src/intrinsics/Date.mts | 218 + src/intrinsics/DatePrototype.mts | 788 ++ src/intrinsics/Error.mts | 85 + src/intrinsics/ErrorPrototype.mts | 113 + src/intrinsics/FinalizationRegistry.mts | 68 + .../FinalizationRegistryPrototype.mts | 109 + src/intrinsics/ForInIteratorPrototype.mts | 120 + src/intrinsics/Function.mts | 24 + src/intrinsics/FunctionPrototype.mts | 232 + src/intrinsics/GeneratorFunction.mts | 34 + src/intrinsics/GeneratorFunctionPrototype.mts | 22 + .../GeneratorFunctionPrototypePrototype.mts | 59 + src/intrinsics/Iterator.mts | 122 + src/intrinsics/IteratorHelperPrototype.mts | 61 + src/intrinsics/IteratorPrototype.mts | 751 ++ src/intrinsics/JSON.mts | 738 ++ src/intrinsics/Map.mts | 128 + src/intrinsics/MapIteratorPrototype.mts | 81 + src/intrinsics/MapPrototype.mts | 295 + src/intrinsics/Math.mts | 288 + src/intrinsics/NativeError.mts | 88 + src/intrinsics/Number.mts | 135 + src/intrinsics/NumberPrototype.mts | 125 + src/intrinsics/Object.mts | 465 + src/intrinsics/ObjectPrototype.mts | 333 + src/intrinsics/Promise.mts | 604 ++ src/intrinsics/PromisePrototype.mts | 125 + src/intrinsics/Proxy.mts | 100 + src/intrinsics/Reflect.mts | 211 + src/intrinsics/RegExp.mts | 165 + src/intrinsics/RegExpPrototype.mts | 761 ++ .../RegExpStringIteratorPrototype.mts | 73 + src/intrinsics/Set.mts | 75 + src/intrinsics/SetIteratorPrototype.mts | 78 + src/intrinsics/SetPrototype.mts | 715 ++ src/intrinsics/ShadowRealm.mts | 72 + src/intrinsics/ShadowRealmPrototype.mts | 47 + src/intrinsics/String.mts | 138 + src/intrinsics/StringIteratorPrototype.mts | 23 + src/intrinsics/StringPrototype.mts | 858 ++ src/intrinsics/Symbol.mts | 106 + src/intrinsics/SymbolPrototype.mts | 83 + src/intrinsics/Temporal/Duration.mts | 139 + src/intrinsics/Temporal/DurationPrototype.mts | 424 + src/intrinsics/Temporal/Instant.mts | 90 + src/intrinsics/Temporal/InstantPrototype.mts | 208 + src/intrinsics/Temporal/Now.mts | 66 + src/intrinsics/Temporal/PlainDate.mts | 78 + .../Temporal/PlainDatePrototype.mts | 331 + src/intrinsics/Temporal/PlainDateTime.mts | 113 + .../Temporal/PlainDateTimePrototype.mts | 419 + src/intrinsics/Temporal/PlainMonthDay.mts | 81 + .../Temporal/PlainMonthDayPrototype.mts | 139 + src/intrinsics/Temporal/PlainTime.mts | 76 + .../Temporal/PlainTimePrototype.mts | 222 + src/intrinsics/Temporal/PlainYearMonth.mts | 96 + .../Temporal/PlainYearMonthPrototype.mts | 223 + src/intrinsics/Temporal/Temporal.mts | 30 + src/intrinsics/Temporal/ZonedDateTime.mts | 103 + .../Temporal/ZonedDateTimePrototype.mts | 485 + src/intrinsics/ThrowTypeError.mts | 21 + src/intrinsics/TypedArray.mts | 576 + src/intrinsics/TypedArrayConstructors.mts | 85 + src/intrinsics/TypedArrayPrototype.mts | 722 ++ src/intrinsics/TypedArrayPrototypes.mts | 17 + src/intrinsics/TypedArray_Uint8Array.mts | 443 + src/intrinsics/URIHandling.mts | 277 + src/intrinsics/WeakMap.mts | 56 + src/intrinsics/WeakMapPrototype.mts | 203 + src/intrinsics/WeakRef.mts | 44 + src/intrinsics/WeakRefPrototype.mts | 23 + src/intrinsics/WeakSet.mts | 66 + src/intrinsics/WeakSetPrototype.mts | 99 + .../WrapForValidIteratorPrototype.mts | 65 + src/intrinsics/bootstrap.mts | 153 + src/intrinsics/eval.mts | 16 + src/intrinsics/isFinite.mts | 23 + src/intrinsics/isNaN.mts | 23 + src/intrinsics/parseFloat.mts | 78 + src/intrinsics/parseInt.mts | 101 + src/messages.mts | 209 + src/modules.mts | 771 ++ src/parse.mts | 266 + src/parser/BaseParser.mts | 36 + src/parser/ExpressionParser.mts | 1607 +++ src/parser/FunctionParser.mts | 389 + src/parser/IdentifierParser.mts | 144 + src/parser/LanguageParser.mts | 136 + src/parser/Lexer.mts | 1195 +++ src/parser/ModuleParser.mts | 336 + src/parser/ParseNode.mts | 2871 +++++ src/parser/Parser.mts | 201 + src/parser/RegExpParser.mts | 1424 +++ src/parser/Scope.mts | 538 + src/parser/StatementParser.mts | 959 ++ src/parser/TemporalParser.mts | 243 + src/parser/tokens.mts | 213 + src/parser/unicode.d.ts | 20 + src/parser/utils.mts | 114 + src/runtime-semantics/AdditiveExpression.mts | 29 + .../ApplyStringOrNumericBinaryOperator.mts | 78 + .../ArgumentListEvaluation.mts | 178 + src/runtime-semantics/ArrayLiteral.mts | 99 + src/runtime-semantics/ArrowFunction.mts | 8 + .../AssignmentExpression.mts | 281 + src/runtime-semantics/AsyncArrowFunction.mts | 8 + .../AsyncFunctionExpression.mts | 11 + .../AsyncGeneratorExpression.mts | 11 + src/runtime-semantics/AwaitExpression.mts | 16 + .../BindingInitialization.mts | 93 + src/runtime-semantics/BitwiseOperators.mts | 14 + src/runtime-semantics/Block.mts | 72 + src/runtime-semantics/BreakStatement.mts | 18 + src/runtime-semantics/BreakableStatement.mts | 18 + src/runtime-semantics/CallExpression.mts | 57 + src/runtime-semantics/ClassDeclaration.mts | 41 + .../ClassDefinitionEvaluation.mts | 805 ++ src/runtime-semantics/ClassExpression.mts | 24 + .../ClassFieldDefinitionEvaluation.mts | 189 + .../ClassStaticBlockDefinitionEvaluation.mts | 48 + src/runtime-semantics/CoalesceExpression.mts | 24 + src/runtime-semantics/CommaOperator.mts | 18 + .../ConditionalExpression.mts | 31 + src/runtime-semantics/ContinueStatement.mts | 18 + .../CreateDynamicFunction.mts | 178 + src/runtime-semantics/DebuggerStatement.mts | 19 + src/runtime-semantics/DefineMethod.mts | 41 + .../DestructuringAssignmentEvaluation.mts | 306 + src/runtime-semantics/EmptyStatement.mts | 9 + src/runtime-semantics/EqualityExpression.mts | 60 + src/runtime-semantics/EvaluateBody.mts | 206 + src/runtime-semantics/EvaluateCall.mts | 63 + .../EvaluatePropertyAccess.mts | 39 + ...valuateStringOrNumericBinaryExpression.mts | 19 + .../ExponentiationExpression.mts | 11 + src/runtime-semantics/ExportDeclaration.mts | 100 + src/runtime-semantics/ExpressionStatement.mts | 14 + src/runtime-semantics/FunctionDeclaration.mts | 11 + .../FunctionDeclarationInstantiation.mts | 274 + src/runtime-semantics/FunctionExpression.mts | 11 + .../FunctionStatementList.mts | 11 + src/runtime-semantics/GeneratorExpression.mts | 11 + src/runtime-semantics/GetSubstitution.mts | 90 + .../GlobalDeclarationInstantiation.mts | 141 + .../HoistableDeclaration.mts | 12 + src/runtime-semantics/IdentifierReference.mts | 15 + src/runtime-semantics/IfStatement.mts | 49 + src/runtime-semantics/ImportCall.mts | 134 + src/runtime-semantics/ImportDeclaration.mts | 9 + src/runtime-semantics/ImportMeta.mts | 45 + .../InstantiateArrowFunctionExpression.mts | 35 + ...nstantiateAsyncArrowFunctionExpression.mts | 37 + .../InstantiateAsyncFunctionExpression.mts | 75 + ...ntiateAsyncGeneratorFunctionExpression.mts | 90 + .../InstantiateFunctionObject.mts | 123 + ...InstantiateGeneratorFunctionExpression.mts | 82 + .../InstantiateOrdinaryFunctionExpression.mts | 64 + .../IteratorBindingInitialization.mts | 212 + .../KeyedBindingInitialization.mts | 61 + src/runtime-semantics/LabelledEvaluation.mts | 753 ++ src/runtime-semantics/LabelledStatement.mts | 11 + src/runtime-semantics/LexicalDeclaration.mts | 92 + src/runtime-semantics/Literal.mts | 36 + .../LogicalANDExpression.mts | 25 + src/runtime-semantics/LogicalORExpression.mts | 25 + src/runtime-semantics/MV.mts | 10 + src/runtime-semantics/MemberExpression.mts | 71 + .../MethodDefinitionEvaluation.mts | 356 + src/runtime-semantics/Module.mts | 15 + src/runtime-semantics/ModuleBody.mts | 10 + .../MultiplicativeExpression.mts | 18 + src/runtime-semantics/NamedEvaluation.mts | 97 + src/runtime-semantics/NewExpression.mts | 50 + src/runtime-semantics/NewTarget.mts | 8 + src/runtime-semantics/NumberToBigInt.mts | 17 + src/runtime-semantics/ObjectLiteral.mts | 26 + src/runtime-semantics/OptionalExpression.mts | 128 + .../ParenthesizedExpression.mts | 8 + .../PropertyBindingInitialization.mts | 45 + .../PropertyDefinitionEvaluation.mts | 129 + src/runtime-semantics/PropertyName.mts | 62 + src/runtime-semantics/RegExp.mts | 1343 +++ .../RegularExpressionLiteral.mts | 16 + .../RelationalExpression.mts | 151 + .../RestBindingInitialization.mts | 29 + src/runtime-semantics/ReturnStatement.mts | 32 + src/runtime-semantics/Script.mts | 15 + src/runtime-semantics/ScriptBody.mts | 7 + src/runtime-semantics/ShiftExpression.mts | 17 + src/runtime-semantics/StatementList.mts | 33 + src/runtime-semantics/StringIndexOf.mts | 43 + src/runtime-semantics/StringPad.mts | 34 + src/runtime-semantics/SuperCall.mts | 65 + src/runtime-semantics/SuperProperty.mts | 58 + src/runtime-semantics/SwitchStatement.mts | 222 + .../TaggedTemplateExpression.mts | 23 + src/runtime-semantics/TemplateLiteral.mts | 34 + src/runtime-semantics/This.mts | 9 + src/runtime-semantics/ThrowStatement.mts | 22 + src/runtime-semantics/TrimString.mts | 19 + src/runtime-semantics/TryStatement.mts | 120 + src/runtime-semantics/UnaryExpression.mts | 218 + src/runtime-semantics/Unicode.mts | 252 + src/runtime-semantics/UpdateExpression.mts | 126 + src/runtime-semantics/VariableStatement.mts | 72 + src/runtime-semantics/WithStatement.mts | 32 + src/runtime-semantics/YieldExpression.mts | 174 + src/runtime-semantics/all.mts | 108 + src/static-semantics/BodyText.mts | 7 + src/static-semantics/BoundNames.mts | 101 + src/static-semantics/CharacterValue.mts | 112 + src/static-semantics/CodePointAt.mts | 53 + src/static-semantics/CodePointsToString.mts | 15 + src/static-semantics/ConstructorMethod.mts | 10 + src/static-semantics/ContainsArguments.mts | 33 + src/static-semantics/ContainsExpression.mts | 59 + src/static-semantics/DeclarationPart.mts | 5 + .../ExpectedArgumentCount.mts | 26 + src/static-semantics/ExportEntries.mts | 129 + .../ExportEntriesForModule.mts | 63 + src/static-semantics/FlagText.mts | 7 + src/static-semantics/HasInitializer.mts | 5 + src/static-semantics/HasName.mts | 8 + src/static-semantics/ImportEntries.mts | 36 + .../ImportEntriesForModule.mts | 97 + src/static-semantics/ImportedLocalNames.mts | 14 + .../IsAnonymousFunctionDefinition.mts | 18 + .../IsComputedPropertyKey.mts | 7 + .../IsConstantDeclaration.mts | 5 + src/static-semantics/IsDestructuring.mts | 21 + src/static-semantics/IsFunctionDefinition.mts | 15 + src/static-semantics/IsIdentifierRef.mts | 5 + src/static-semantics/IsInTailPosition.mts | 5 + .../IsSimpleParameterList.mts | 23 + src/static-semantics/IsStatic.mts | 10 + src/static-semantics/IsStrict.mts | 7 + .../IsStringWellFormedUnicode.mts | 24 + .../LexicallyDeclaredNames.mts | 26 + .../LexicallyScopedDeclarations.mts | 82 + src/static-semantics/ModuleRequests.mts | 98 + .../NonConstructorElements.mts | 15 + src/static-semantics/NumericValue.mts | 7 + .../PrivateBoundIdentifiers.mts | 23 + src/static-semantics/PropName.mts | 23 + src/static-semantics/StringToCodePoints.mts | 22 + src/static-semantics/StringValue.mts | 19 + src/static-semantics/TemplateStrings.mts | 109 + .../TopLevelLexicallyDeclaredNames.mts | 21 + .../TopLevelLexicallyScopedDeclarations.mts | 23 + .../TopLevelVarDeclaredNames.mts | 26 + .../TopLevelVarScopedDeclarations.mts | 34 + src/static-semantics/UTF16EncodeCodePoint.mts | 18 + .../UTF16SurrogatePairToCodePoint.mts | 12 + src/static-semantics/VarDeclaredNames.mts | 109 + .../VarScopedDeclarations.mts | 115 + src/static-semantics/all.mts | 47 + src/syntax-error.d.ts | 5 + src/tsconfig.json | 11 + src/unicode/.gitkeep | 0 src/value.mts | 1016 ++ test/base.mts | 177 + test/engine262/WeakRef.test.mts | 77 + test/engine262/debugger.test.mts | 45 + test/engine262/error.test.mts | 72 + test/engine262/module.test.mts | 119 + test/engine262/section.test.mts | 93 + test/eslint-plugin-engine262/index.mts | 9 + .../mathematical-value.mts | 206 + .../no-floating-generator.mts | 69 + test/eslint-plugin-engine262/package.json | 6 + .../safe-function-with-q.mts | 155 + test/eslint-plugin-engine262/tsconfig.json | 13 + .../__snapshots__/console.test.mts.snap | 71 + .../__snapshots__/source.test.mts.snap | 95 + .../toRemoteObject.test.mts.snap | 4298 ++++++++ test/inspector/console.test.mts | 278 + test/inspector/debugger.test.mts | 489 + test/inspector/reports.test.mts | 224 + test/inspector/source.test.mts | 90 + test/inspector/toRemoteObject.test.mts | 341 + test/inspector/utils.mts | 86 + test/json/JSONTestSuite | 1 + test/json/json.mts | 100 + test/test262/failed | 106 + test/test262/features | 27 + test/test262/skip | 124 + test/test262/slow | 97 + test/test262/slow-ci | 37 + test/test262/test262 | 1 + test/test262/test262-runner.mts | 446 + test/test262/test262-worker.mts | 260 + test/test262/test262.mts | 103 + test/test_root.sh | 11 + test/tsconfig.json | 20 + test/tui.mts | 405 + tsconfig.base.json | 38 + tsconfig.json | 10 + vitest.config.mts | 11 + website | 1 + 433 files changed, 90478 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .git-blame-ignore-revs create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .npmignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 babel.config.json create mode 100755 bin/engine262.js create mode 100644 lib-src/inspector/context.mts create mode 100644 lib-src/inspector/index.mts create mode 100644 lib-src/inspector/inspect.mts create mode 100644 lib-src/inspector/internal-utils.mts create mode 100644 lib-src/inspector/js_protocol.json create mode 100644 lib-src/inspector/methods.mts create mode 100644 lib-src/inspector/tsconfig.json create mode 100644 lib-src/inspector/types.mts create mode 100644 lib-src/inspector/utils.mts create mode 100644 lib-src/node/bin.mts create mode 100644 lib-src/node/example.mts create mode 100644 lib-src/node/inspector.mts create mode 100644 lib-src/node/module.mts create mode 100644 lib-src/node/tsconfig.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/Unicode/PropertyValueAliases.txt create mode 100644 scripts/gen_regex_sets.mts create mode 100644 scripts/generate_error_message_hint.mts create mode 100644 scripts/rollup.config.mts create mode 100644 scripts/tag_version_with_git_hash.mts create mode 100644 scripts/transform.mts create mode 100644 scripts/tsconfig.json create mode 100644 src/abstract-ops/all.mts create mode 100644 src/abstract-ops/arguments-operations.mts create mode 100644 src/abstract-ops/array-objects.mts create mode 100644 src/abstract-ops/arraybuffer-objects.mts create mode 100644 src/abstract-ops/async-function-operations.mts create mode 100644 src/abstract-ops/async-generator-objects.mts create mode 100644 src/abstract-ops/data-types-and-values.mts create mode 100644 src/abstract-ops/dataview-objects.mts create mode 100644 src/abstract-ops/date-objects.mts create mode 100644 src/abstract-ops/error-objects.mts create mode 100644 src/abstract-ops/execution-contexts.mts create mode 100644 src/abstract-ops/function-operations.mts create mode 100644 src/abstract-ops/generator-operations.mts create mode 100644 src/abstract-ops/global-object.mts create mode 100644 src/abstract-ops/immutable-prototype-objects.mts create mode 100644 src/abstract-ops/import-calls.mts create mode 100644 src/abstract-ops/iterator-operations.mts create mode 100644 src/abstract-ops/keyed-collections.mts create mode 100644 src/abstract-ops/math.mts create mode 100644 src/abstract-ops/module-namespace-exotic-objects.mts create mode 100644 src/abstract-ops/module-records.mts create mode 100644 src/abstract-ops/notational-conventions.mts create mode 100644 src/abstract-ops/object-operations.mts create mode 100644 src/abstract-ops/objects.mts create mode 100644 src/abstract-ops/private-names.mts create mode 100644 src/abstract-ops/promise-operations.mts create mode 100644 src/abstract-ops/proxy-objects.mts create mode 100644 src/abstract-ops/realms.mts create mode 100644 src/abstract-ops/reference-operations.mts create mode 100644 src/abstract-ops/regexp-objects.mts create mode 100644 src/abstract-ops/shadow-realm.mts create mode 100644 src/abstract-ops/shared-arraybuffer.mts create mode 100644 src/abstract-ops/spec-types.mts create mode 100644 src/abstract-ops/string-objects.mts create mode 100644 src/abstract-ops/symbol-objects.mts create mode 100644 src/abstract-ops/temporal/addition.mts create mode 100644 src/abstract-ops/temporal/all.mts create mode 100644 src/abstract-ops/temporal/calendar.mts create mode 100644 src/abstract-ops/temporal/duration.mts create mode 100644 src/abstract-ops/temporal/instant.mts create mode 100644 src/abstract-ops/temporal/not-implemented.mts create mode 100644 src/abstract-ops/temporal/now.mts create mode 100644 src/abstract-ops/temporal/plain-date-time.mts create mode 100644 src/abstract-ops/temporal/plain-date.mts create mode 100644 src/abstract-ops/temporal/plain-month-day.mts create mode 100644 src/abstract-ops/temporal/plain-time.mts create mode 100644 src/abstract-ops/temporal/plain-year-month.mts create mode 100644 src/abstract-ops/temporal/temporal.mts create mode 100644 src/abstract-ops/temporal/time-zone.mts create mode 100644 src/abstract-ops/temporal/zoned-datetime.mts create mode 100644 src/abstract-ops/testing-comparison.mts create mode 100644 src/abstract-ops/type-conversion.mts create mode 100644 src/abstract-ops/typedarray-objects.mts create mode 100644 src/abstract-ops/weak-operations.mts create mode 100644 src/api.mts create mode 100644 src/completion.mts create mode 100644 src/ecma402/not-implemented.mts create mode 100644 src/evaluator.mts create mode 100644 src/execution-context/Agent.mts create mode 100644 src/execution-context/Environment.mts create mode 100644 src/execution-context/ExecutionContext.mts create mode 100644 src/execution-context/Job.mts create mode 100644 src/execution-context/PrivateEnvironment.mts create mode 100644 src/execution-context/Realm.mts create mode 100644 src/execution-context/WeakReference.mts create mode 100644 src/execution-context/all.mts create mode 100644 src/helpers.mts create mode 100644 src/host-defined/debugger-eval.mts create mode 100644 src/host-defined/debugger-util.mts create mode 100644 src/host-defined/engine.mts create mode 100644 src/host-defined/error-messages.mts create mode 100644 src/host-defined/inspect.mts create mode 100644 src/host-defined/test262-intrinsics.mts create mode 100644 src/index.mts create mode 100644 src/intrinsics/AggregateError.mts create mode 100644 src/intrinsics/AggregateErrorPrototype.mts create mode 100644 src/intrinsics/Array.mts create mode 100644 src/intrinsics/ArrayBuffer.mts create mode 100644 src/intrinsics/ArrayBufferPrototype.mts create mode 100644 src/intrinsics/ArrayIteratorPrototype.mts create mode 100644 src/intrinsics/ArrayPrototype.mts create mode 100644 src/intrinsics/ArrayPrototypeShared.mts create mode 100644 src/intrinsics/AsyncFromSyncIteratorPrototype.mts create mode 100644 src/intrinsics/AsyncFunction.mts create mode 100644 src/intrinsics/AsyncFunctionPrototype.mts create mode 100644 src/intrinsics/AsyncGeneratorFunction.mts create mode 100644 src/intrinsics/AsyncGeneratorFunctionPrototype.mts create mode 100644 src/intrinsics/AsyncGeneratorFunctionPrototypePrototype.mts create mode 100644 src/intrinsics/AsyncIteratorPrototype.mts create mode 100644 src/intrinsics/BigInt.mts create mode 100644 src/intrinsics/BigIntPrototype.mts create mode 100644 src/intrinsics/Boolean.mts create mode 100644 src/intrinsics/BooleanPrototype.mts create mode 100644 src/intrinsics/DataView.mts create mode 100644 src/intrinsics/DataViewPrototype.mts create mode 100644 src/intrinsics/Date.mts create mode 100644 src/intrinsics/DatePrototype.mts create mode 100644 src/intrinsics/Error.mts create mode 100644 src/intrinsics/ErrorPrototype.mts create mode 100644 src/intrinsics/FinalizationRegistry.mts create mode 100644 src/intrinsics/FinalizationRegistryPrototype.mts create mode 100644 src/intrinsics/ForInIteratorPrototype.mts create mode 100644 src/intrinsics/Function.mts create mode 100644 src/intrinsics/FunctionPrototype.mts create mode 100644 src/intrinsics/GeneratorFunction.mts create mode 100644 src/intrinsics/GeneratorFunctionPrototype.mts create mode 100644 src/intrinsics/GeneratorFunctionPrototypePrototype.mts create mode 100644 src/intrinsics/Iterator.mts create mode 100644 src/intrinsics/IteratorHelperPrototype.mts create mode 100644 src/intrinsics/IteratorPrototype.mts create mode 100644 src/intrinsics/JSON.mts create mode 100644 src/intrinsics/Map.mts create mode 100644 src/intrinsics/MapIteratorPrototype.mts create mode 100644 src/intrinsics/MapPrototype.mts create mode 100644 src/intrinsics/Math.mts create mode 100644 src/intrinsics/NativeError.mts create mode 100644 src/intrinsics/Number.mts create mode 100644 src/intrinsics/NumberPrototype.mts create mode 100644 src/intrinsics/Object.mts create mode 100644 src/intrinsics/ObjectPrototype.mts create mode 100644 src/intrinsics/Promise.mts create mode 100644 src/intrinsics/PromisePrototype.mts create mode 100644 src/intrinsics/Proxy.mts create mode 100644 src/intrinsics/Reflect.mts create mode 100644 src/intrinsics/RegExp.mts create mode 100644 src/intrinsics/RegExpPrototype.mts create mode 100644 src/intrinsics/RegExpStringIteratorPrototype.mts create mode 100644 src/intrinsics/Set.mts create mode 100644 src/intrinsics/SetIteratorPrototype.mts create mode 100644 src/intrinsics/SetPrototype.mts create mode 100644 src/intrinsics/ShadowRealm.mts create mode 100644 src/intrinsics/ShadowRealmPrototype.mts create mode 100644 src/intrinsics/String.mts create mode 100644 src/intrinsics/StringIteratorPrototype.mts create mode 100644 src/intrinsics/StringPrototype.mts create mode 100644 src/intrinsics/Symbol.mts create mode 100644 src/intrinsics/SymbolPrototype.mts create mode 100644 src/intrinsics/Temporal/Duration.mts create mode 100644 src/intrinsics/Temporal/DurationPrototype.mts create mode 100644 src/intrinsics/Temporal/Instant.mts create mode 100644 src/intrinsics/Temporal/InstantPrototype.mts create mode 100644 src/intrinsics/Temporal/Now.mts create mode 100644 src/intrinsics/Temporal/PlainDate.mts create mode 100644 src/intrinsics/Temporal/PlainDatePrototype.mts create mode 100644 src/intrinsics/Temporal/PlainDateTime.mts create mode 100644 src/intrinsics/Temporal/PlainDateTimePrototype.mts create mode 100644 src/intrinsics/Temporal/PlainMonthDay.mts create mode 100644 src/intrinsics/Temporal/PlainMonthDayPrototype.mts create mode 100644 src/intrinsics/Temporal/PlainTime.mts create mode 100644 src/intrinsics/Temporal/PlainTimePrototype.mts create mode 100644 src/intrinsics/Temporal/PlainYearMonth.mts create mode 100644 src/intrinsics/Temporal/PlainYearMonthPrototype.mts create mode 100644 src/intrinsics/Temporal/Temporal.mts create mode 100644 src/intrinsics/Temporal/ZonedDateTime.mts create mode 100644 src/intrinsics/Temporal/ZonedDateTimePrototype.mts create mode 100644 src/intrinsics/ThrowTypeError.mts create mode 100644 src/intrinsics/TypedArray.mts create mode 100644 src/intrinsics/TypedArrayConstructors.mts create mode 100644 src/intrinsics/TypedArrayPrototype.mts create mode 100644 src/intrinsics/TypedArrayPrototypes.mts create mode 100644 src/intrinsics/TypedArray_Uint8Array.mts create mode 100644 src/intrinsics/URIHandling.mts create mode 100644 src/intrinsics/WeakMap.mts create mode 100644 src/intrinsics/WeakMapPrototype.mts create mode 100644 src/intrinsics/WeakRef.mts create mode 100644 src/intrinsics/WeakRefPrototype.mts create mode 100644 src/intrinsics/WeakSet.mts create mode 100644 src/intrinsics/WeakSetPrototype.mts create mode 100644 src/intrinsics/WrapForValidIteratorPrototype.mts create mode 100644 src/intrinsics/bootstrap.mts create mode 100644 src/intrinsics/eval.mts create mode 100644 src/intrinsics/isFinite.mts create mode 100644 src/intrinsics/isNaN.mts create mode 100644 src/intrinsics/parseFloat.mts create mode 100644 src/intrinsics/parseInt.mts create mode 100644 src/messages.mts create mode 100644 src/modules.mts create mode 100644 src/parse.mts create mode 100644 src/parser/BaseParser.mts create mode 100644 src/parser/ExpressionParser.mts create mode 100644 src/parser/FunctionParser.mts create mode 100644 src/parser/IdentifierParser.mts create mode 100644 src/parser/LanguageParser.mts create mode 100644 src/parser/Lexer.mts create mode 100644 src/parser/ModuleParser.mts create mode 100644 src/parser/ParseNode.mts create mode 100644 src/parser/Parser.mts create mode 100644 src/parser/RegExpParser.mts create mode 100644 src/parser/Scope.mts create mode 100644 src/parser/StatementParser.mts create mode 100644 src/parser/TemporalParser.mts create mode 100644 src/parser/tokens.mts create mode 100644 src/parser/unicode.d.ts create mode 100644 src/parser/utils.mts create mode 100644 src/runtime-semantics/AdditiveExpression.mts create mode 100644 src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mts create mode 100644 src/runtime-semantics/ArgumentListEvaluation.mts create mode 100644 src/runtime-semantics/ArrayLiteral.mts create mode 100644 src/runtime-semantics/ArrowFunction.mts create mode 100644 src/runtime-semantics/AssignmentExpression.mts create mode 100644 src/runtime-semantics/AsyncArrowFunction.mts create mode 100644 src/runtime-semantics/AsyncFunctionExpression.mts create mode 100644 src/runtime-semantics/AsyncGeneratorExpression.mts create mode 100644 src/runtime-semantics/AwaitExpression.mts create mode 100644 src/runtime-semantics/BindingInitialization.mts create mode 100644 src/runtime-semantics/BitwiseOperators.mts create mode 100644 src/runtime-semantics/Block.mts create mode 100644 src/runtime-semantics/BreakStatement.mts create mode 100644 src/runtime-semantics/BreakableStatement.mts create mode 100644 src/runtime-semantics/CallExpression.mts create mode 100644 src/runtime-semantics/ClassDeclaration.mts create mode 100644 src/runtime-semantics/ClassDefinitionEvaluation.mts create mode 100644 src/runtime-semantics/ClassExpression.mts create mode 100644 src/runtime-semantics/ClassFieldDefinitionEvaluation.mts create mode 100644 src/runtime-semantics/ClassStaticBlockDefinitionEvaluation.mts create mode 100644 src/runtime-semantics/CoalesceExpression.mts create mode 100644 src/runtime-semantics/CommaOperator.mts create mode 100644 src/runtime-semantics/ConditionalExpression.mts create mode 100644 src/runtime-semantics/ContinueStatement.mts create mode 100644 src/runtime-semantics/CreateDynamicFunction.mts create mode 100644 src/runtime-semantics/DebuggerStatement.mts create mode 100644 src/runtime-semantics/DefineMethod.mts create mode 100644 src/runtime-semantics/DestructuringAssignmentEvaluation.mts create mode 100644 src/runtime-semantics/EmptyStatement.mts create mode 100644 src/runtime-semantics/EqualityExpression.mts create mode 100644 src/runtime-semantics/EvaluateBody.mts create mode 100644 src/runtime-semantics/EvaluateCall.mts create mode 100644 src/runtime-semantics/EvaluatePropertyAccess.mts create mode 100644 src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mts create mode 100644 src/runtime-semantics/ExponentiationExpression.mts create mode 100644 src/runtime-semantics/ExportDeclaration.mts create mode 100644 src/runtime-semantics/ExpressionStatement.mts create mode 100644 src/runtime-semantics/FunctionDeclaration.mts create mode 100644 src/runtime-semantics/FunctionDeclarationInstantiation.mts create mode 100644 src/runtime-semantics/FunctionExpression.mts create mode 100644 src/runtime-semantics/FunctionStatementList.mts create mode 100644 src/runtime-semantics/GeneratorExpression.mts create mode 100644 src/runtime-semantics/GetSubstitution.mts create mode 100644 src/runtime-semantics/GlobalDeclarationInstantiation.mts create mode 100644 src/runtime-semantics/HoistableDeclaration.mts create mode 100644 src/runtime-semantics/IdentifierReference.mts create mode 100644 src/runtime-semantics/IfStatement.mts create mode 100644 src/runtime-semantics/ImportCall.mts create mode 100644 src/runtime-semantics/ImportDeclaration.mts create mode 100644 src/runtime-semantics/ImportMeta.mts create mode 100644 src/runtime-semantics/InstantiateArrowFunctionExpression.mts create mode 100644 src/runtime-semantics/InstantiateAsyncArrowFunctionExpression.mts create mode 100644 src/runtime-semantics/InstantiateAsyncFunctionExpression.mts create mode 100644 src/runtime-semantics/InstantiateAsyncGeneratorFunctionExpression.mts create mode 100644 src/runtime-semantics/InstantiateFunctionObject.mts create mode 100644 src/runtime-semantics/InstantiateGeneratorFunctionExpression.mts create mode 100644 src/runtime-semantics/InstantiateOrdinaryFunctionExpression.mts create mode 100644 src/runtime-semantics/IteratorBindingInitialization.mts create mode 100644 src/runtime-semantics/KeyedBindingInitialization.mts create mode 100644 src/runtime-semantics/LabelledEvaluation.mts create mode 100644 src/runtime-semantics/LabelledStatement.mts create mode 100644 src/runtime-semantics/LexicalDeclaration.mts create mode 100644 src/runtime-semantics/Literal.mts create mode 100644 src/runtime-semantics/LogicalANDExpression.mts create mode 100644 src/runtime-semantics/LogicalORExpression.mts create mode 100644 src/runtime-semantics/MV.mts create mode 100644 src/runtime-semantics/MemberExpression.mts create mode 100644 src/runtime-semantics/MethodDefinitionEvaluation.mts create mode 100644 src/runtime-semantics/Module.mts create mode 100644 src/runtime-semantics/ModuleBody.mts create mode 100644 src/runtime-semantics/MultiplicativeExpression.mts create mode 100644 src/runtime-semantics/NamedEvaluation.mts create mode 100644 src/runtime-semantics/NewExpression.mts create mode 100644 src/runtime-semantics/NewTarget.mts create mode 100644 src/runtime-semantics/NumberToBigInt.mts create mode 100644 src/runtime-semantics/ObjectLiteral.mts create mode 100644 src/runtime-semantics/OptionalExpression.mts create mode 100644 src/runtime-semantics/ParenthesizedExpression.mts create mode 100644 src/runtime-semantics/PropertyBindingInitialization.mts create mode 100644 src/runtime-semantics/PropertyDefinitionEvaluation.mts create mode 100644 src/runtime-semantics/PropertyName.mts create mode 100644 src/runtime-semantics/RegExp.mts create mode 100644 src/runtime-semantics/RegularExpressionLiteral.mts create mode 100644 src/runtime-semantics/RelationalExpression.mts create mode 100644 src/runtime-semantics/RestBindingInitialization.mts create mode 100644 src/runtime-semantics/ReturnStatement.mts create mode 100644 src/runtime-semantics/Script.mts create mode 100644 src/runtime-semantics/ScriptBody.mts create mode 100644 src/runtime-semantics/ShiftExpression.mts create mode 100644 src/runtime-semantics/StatementList.mts create mode 100644 src/runtime-semantics/StringIndexOf.mts create mode 100644 src/runtime-semantics/StringPad.mts create mode 100644 src/runtime-semantics/SuperCall.mts create mode 100644 src/runtime-semantics/SuperProperty.mts create mode 100644 src/runtime-semantics/SwitchStatement.mts create mode 100644 src/runtime-semantics/TaggedTemplateExpression.mts create mode 100644 src/runtime-semantics/TemplateLiteral.mts create mode 100644 src/runtime-semantics/This.mts create mode 100644 src/runtime-semantics/ThrowStatement.mts create mode 100644 src/runtime-semantics/TrimString.mts create mode 100644 src/runtime-semantics/TryStatement.mts create mode 100644 src/runtime-semantics/UnaryExpression.mts create mode 100644 src/runtime-semantics/Unicode.mts create mode 100644 src/runtime-semantics/UpdateExpression.mts create mode 100644 src/runtime-semantics/VariableStatement.mts create mode 100644 src/runtime-semantics/WithStatement.mts create mode 100644 src/runtime-semantics/YieldExpression.mts create mode 100644 src/runtime-semantics/all.mts create mode 100644 src/static-semantics/BodyText.mts create mode 100644 src/static-semantics/BoundNames.mts create mode 100644 src/static-semantics/CharacterValue.mts create mode 100644 src/static-semantics/CodePointAt.mts create mode 100644 src/static-semantics/CodePointsToString.mts create mode 100644 src/static-semantics/ConstructorMethod.mts create mode 100644 src/static-semantics/ContainsArguments.mts create mode 100644 src/static-semantics/ContainsExpression.mts create mode 100644 src/static-semantics/DeclarationPart.mts create mode 100644 src/static-semantics/ExpectedArgumentCount.mts create mode 100644 src/static-semantics/ExportEntries.mts create mode 100644 src/static-semantics/ExportEntriesForModule.mts create mode 100644 src/static-semantics/FlagText.mts create mode 100644 src/static-semantics/HasInitializer.mts create mode 100644 src/static-semantics/HasName.mts create mode 100644 src/static-semantics/ImportEntries.mts create mode 100644 src/static-semantics/ImportEntriesForModule.mts create mode 100644 src/static-semantics/ImportedLocalNames.mts create mode 100644 src/static-semantics/IsAnonymousFunctionDefinition.mts create mode 100644 src/static-semantics/IsComputedPropertyKey.mts create mode 100644 src/static-semantics/IsConstantDeclaration.mts create mode 100644 src/static-semantics/IsDestructuring.mts create mode 100644 src/static-semantics/IsFunctionDefinition.mts create mode 100644 src/static-semantics/IsIdentifierRef.mts create mode 100644 src/static-semantics/IsInTailPosition.mts create mode 100644 src/static-semantics/IsSimpleParameterList.mts create mode 100644 src/static-semantics/IsStatic.mts create mode 100644 src/static-semantics/IsStrict.mts create mode 100644 src/static-semantics/IsStringWellFormedUnicode.mts create mode 100644 src/static-semantics/LexicallyDeclaredNames.mts create mode 100644 src/static-semantics/LexicallyScopedDeclarations.mts create mode 100644 src/static-semantics/ModuleRequests.mts create mode 100644 src/static-semantics/NonConstructorElements.mts create mode 100644 src/static-semantics/NumericValue.mts create mode 100644 src/static-semantics/PrivateBoundIdentifiers.mts create mode 100644 src/static-semantics/PropName.mts create mode 100644 src/static-semantics/StringToCodePoints.mts create mode 100644 src/static-semantics/StringValue.mts create mode 100644 src/static-semantics/TemplateStrings.mts create mode 100644 src/static-semantics/TopLevelLexicallyDeclaredNames.mts create mode 100644 src/static-semantics/TopLevelLexicallyScopedDeclarations.mts create mode 100644 src/static-semantics/TopLevelVarDeclaredNames.mts create mode 100644 src/static-semantics/TopLevelVarScopedDeclarations.mts create mode 100644 src/static-semantics/UTF16EncodeCodePoint.mts create mode 100644 src/static-semantics/UTF16SurrogatePairToCodePoint.mts create mode 100644 src/static-semantics/VarDeclaredNames.mts create mode 100644 src/static-semantics/VarScopedDeclarations.mts create mode 100644 src/static-semantics/all.mts create mode 100644 src/syntax-error.d.ts create mode 100644 src/tsconfig.json create mode 100644 src/unicode/.gitkeep create mode 100644 src/value.mts create mode 100644 test/base.mts create mode 100644 test/engine262/WeakRef.test.mts create mode 100644 test/engine262/debugger.test.mts create mode 100644 test/engine262/error.test.mts create mode 100644 test/engine262/module.test.mts create mode 100644 test/engine262/section.test.mts create mode 100644 test/eslint-plugin-engine262/index.mts create mode 100644 test/eslint-plugin-engine262/mathematical-value.mts create mode 100644 test/eslint-plugin-engine262/no-floating-generator.mts create mode 100644 test/eslint-plugin-engine262/package.json create mode 100644 test/eslint-plugin-engine262/safe-function-with-q.mts create mode 100644 test/eslint-plugin-engine262/tsconfig.json create mode 100644 test/inspector/__snapshots__/console.test.mts.snap create mode 100644 test/inspector/__snapshots__/source.test.mts.snap create mode 100644 test/inspector/__snapshots__/toRemoteObject.test.mts.snap create mode 100644 test/inspector/console.test.mts create mode 100644 test/inspector/debugger.test.mts create mode 100644 test/inspector/reports.test.mts create mode 100644 test/inspector/source.test.mts create mode 100644 test/inspector/toRemoteObject.test.mts create mode 100644 test/inspector/utils.mts create mode 160000 test/json/JSONTestSuite create mode 100644 test/json/json.mts create mode 100644 test/test262/failed create mode 100644 test/test262/features create mode 100644 test/test262/skip create mode 100644 test/test262/slow create mode 100644 test/test262/slow-ci create mode 160000 test/test262/test262 create mode 100644 test/test262/test262-runner.mts create mode 100644 test/test262/test262-worker.mts create mode 100644 test/test262/test262.mts create mode 100755 test/test_root.sh create mode 100644 test/tsconfig.json create mode 100644 test/tui.mts create mode 100644 tsconfig.base.json create mode 100644 tsconfig.json create mode 100644 vitest.config.mts create mode 160000 website diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..be20756 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +!.eslintrc.js +lib +bin/engine262.mjs +test/test262/test262 +test/json/JSONTestSuite +website diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a1ddd21 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,117 @@ +'use strict'; + +// TODO: +// - Any spec object must inherit from OrdinaryObject or ExoticObject. +// - JSString | SymbolValue => PropertyKeyValue +// - NormalCompletion | ThrowCompletion => PlainCompletion +// - PlainCompletion => ExpressionCompletion +module.exports = { + root: true, + extends: 'airbnb-base', + plugins: ['@engine262', '@typescript-eslint'], + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + project: [ + './src/tsconfig.json', + './test/tsconfig.json', + './test/eslint-plugin-engine262/tsconfig.json', + './scripts/tsconfig.json', + './lib-src/node/tsconfig.json', + './lib-src/inspector/tsconfig.json', + ], + }, + overrides: [ + { + files: ['*.js'], + parserOptions: { sourceType: 'module', project: null }, + }, + { + files: ['src/**/*.mts'], + rules: { + '@engine262/safe-function-with-q': 'error', + '@engine262/no-floating-generator': 'error', + }, + }, + { + files: ['*.mts'], + extends: 'plugin:@typescript-eslint/recommended', + rules: { + // TODO: enable this rule after upgrade eslint + // '@stylistic/padding-line-between-statements': ['error', { + // blankLine: 'always', + // prev: '*', + // next: ['interface', 'type'], + // }], + // checked by tsc. + '@typescript-eslint/no-unused-vars': 'off', + 'no-redeclare': 'off', + 'no-fallthrough': 'off', + 'import/export': 'off', + 'no-dupe-class-members': 'off', + 'curly': 'off', + 'yoda': 'off', + // false positive + 'no-shadow': 'off', + // we need it for now + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + // spec convention + '@typescript-eslint/no-this-alias': 'off', + // this rule errors for non null assertion. + // '@typescript-eslint/no-unnecessary-type-assertion': 'error', + }, + }, + ], + globals: { + globalThis: false, + Atomics: false, + BigInt: false, + BigUint64Array: false, + SharedArrayBuffer: false, + }, + rules: { + '@engine262/mathematical-value': 'error', + 'arrow-parens': ['error', 'always'], + 'brace-style': ['error', '1tbs', { allowSingleLine: false }], + 'curly': ['error', 'all'], + '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 }], + 'no-constructor-return': 'off', + 'quote-props': ['error', 'consistent'], + 'strict': ['error', 'global'], + 'default-param-last': 'off', + 'camelcase': 'off', + 'class-methods-use-this': 'off', + 'global-require': 'off', + 'import/extensions': 'off', + 'import/named': 'off', + 'import/no-unresolved': 'off', + 'import/no-cycle': 'off', + 'import/no-mutable-exports': 'off', + 'import/prefer-default-export': 'off', + '@stylistic/eslint-plugin-js/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', + 'require-yield': 'off', + }, +}; diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..180bdfe --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,12 @@ +# comment fixes +38bf0e269181616b30ccf6e0d24e811f11da7ee5 +29a28e5329bfe814199d3beafbad19e7a28c0485 + +# convert all file extension from mjs to mts +2f8453351267d8b500c65303d19c16f0f3c72f80 + +# replace new Value(...) with Value(...) +af59abf2192b92604ce6a40417361291197689f8 + +# enable --allowImportingTsExtensions +fade10ff7f250493925670d40febc3024ca9d8cc diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fb79233 --- /dev/null +++ b/.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/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..4e20ebd --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,62 @@ +name: publish + +on: + workflow_run: + workflows: [test] + types: [completed] + branches: [main] + +concurrency: + group: publish + cancel-in-progress: true + +jobs: + publish: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + contents: read + id-token: write + packages: write + steps: + # Set everything up + - uses: actions/checkout@v4 + with: + persist-credentials: false + submodules: 'recursive' + - uses: actions/setup-node@v4 + with: + node-version: 22 + + # Run tests and whatnot + - run: npm install + - run: npm run build + + # Publish to npm registry + - run: npm set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }} + - run: npm config set registry https://registry.npmjs.org + - run: npm exec npm@latest -- publish --provenance --access public --tag latest + + # Publish to github registry + - run: npm set //npm.pkg.github.com/:_authToken ${{ github.token }} + - run: npm config set registry https://npm.pkg.github.com + # note: remove this after we can release as @engine262/engine262 on npm + - run: node -e "let pkg=require('./package.json'); pkg.name='@engine262/engine262'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));" + - run: npm publish --access=public + + # 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 -r lib/* . + git add engine262.* + git add inspector.* + 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/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3ca8228 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,48 @@ +name: test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + # Set everything up + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - uses: actions/setup-node@v4 + with: + node-version: 22 + + # build and lint + - run: npm install + - run: npm run build + - run: npm run lint + + # upload build artifacts + - uses: actions/upload-artifact@v4 + with: + name: engine262-lib + path: | + lib/engine262.js + lib/engine262.js.map + lib/engine262.mjs + lib/engine262.mjs.map + + # run tests/coverage + - run: npm run coverage:all + env: + CONTINUOUS_INTEGRATION: 1 + NUM_WORKERS: 1 + + # Upload coverage data + - name: Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{github.token}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b954315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +node_modules +declaration +lib +test.mjs +test.js +.eslintcache +coverage +.nyc_output +*-gen.json +test/test262/last-failed.log +test/test262/last-failed-list +test/test262/last-run.json +**/tsconfig.tsbuildinfo +src/unicode/*.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0a0ffc2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[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 +[submodule "website"] + path = website + url = https://github.com/engine262/engine262.github.io diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..999cd74 --- /dev/null +++ b/.npmignore @@ -0,0 +1,11 @@ +test +src +declaration/.tsbuildinfo +declaration/**/*.map +scripts +coverage +rollup.config.mts +.eslintcache +.eslintrc.js +.eslintignore +.travis.yml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bff80a9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\bin\\engine262.js", + "args": ["d:/dev/scratch/dispose.js"], + "outFiles": [ + "${workspaceFolder}/lib/*.js" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e72b7cc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.preferences.quoteStyle": "single", + "editor.tabSize": 2, + "npm.packageManager": "npm", + "eslint.useFlatConfig": false, + "files.associations": { + "slow": "ini", + "skip": "ini", + "features": "ini", + "failed": "ini" + }, +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..52f554f --- /dev/null +++ b/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 [https://contributor-covenant.org/version/1/4][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6cbeb88 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..6b2f311 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# 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 in `#engine262:matrix.org`. + +> [!NOTE] +> Due to [recent changes on npm](https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/), engine262 cannot release new versions in CI with its old name (`@engine262/engine262`), and the current active maintainer does not have permission to fix it. We're temporarily releasing it under the name `@magic-works/engine262`. + + +## 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 playground 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.mts ++++ b/src/evaluator.mts +@@ -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.mts ++++ b/src/parser/ExpressionParser.mts +@@ -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/test262.mts`) require a recent version of Node.js. + +## Using engine262 + +You can install it from npm. + +```shell +npm install @magic-works/engine262 +yarn install @magic-works/engine262 +pnpm install @magic-works/engine262 +``` + +If you install it globally, you can use the CLI like so: + +`$ engine262` + +### engine262 playground + +[Classic playground](https://engine262.js.org) and [Chrome Devtools style playground](https://engine262.js.org/devtools.html) + +### engine262 CLI + +#### --module/-m + +Evaluate the file as a module. + +#### --eval \ / -e \ + +Evaluate the given string and exit. + +#### --features=\ / --features=all + +Run `engine262 --list-features` to see all ECMAScript features can be switched. + +#### --no-test262 + +Do not expose `$` and `$262` global variable for test262 test suite. + +#### --no-inspector + +Do not start an inspector. + +By default engine262 will start an inspector on `ws://localhost:9229/` (like Node.js with `--inspector`). See the [Node.js guide](https://nodejs.org/en/learn/getting-started/debugging#inspector-clients) for connecting. + +#### --no-preview + +Do not enable the preview feature in the inspector. + +### engine262 API + +See the [example](https://github.com/engine262/engine262/blob/main/lib-src/node/example.mts). + +## Developing engine262 + +`npm run build` and `npm run watch` will build and watch the build. + +`npm run test:test262` will run the [test262][] test suite. Run `npm run test:test262 -- --help` to see the test runner options. + +`npm start` start the engine262 CLI. + +`npm run inspector` start the website (debugging engine262 mainly happens here). + +## 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. + +- +- +- + +[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 +[optional chaining]: https://github.com/tc39/proposal-optional-chaining +[pattern matching]: https://github.com/tc39/proposal-pattern-matching +[test262]: https://github.com/tc39/test262 +[the pipeline operator]: https://github.com/tc39/proposal-pipeline-operator +[NPM]: https://npmjs.com/@magic-works/engine262 diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000..1e8c1b3 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,3 @@ +{ + "plugins": ["@babel/plugin-transform-explicit-resource-management"] +} diff --git a/bin/engine262.js b/bin/engine262.js new file mode 100755 index 0000000..891b010 --- /dev/null +++ b/bin/engine262.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + + +// this file is for esvu compatibility +(async () => import('../lib/node/bin.mjs'))(); diff --git a/lib-src/inspector/context.mts b/lib-src/inspector/context.mts new file mode 100644 index 0000000..b4e7824 --- /dev/null +++ b/lib-src/inspector/context.mts @@ -0,0 +1,405 @@ +import type { Protocol } from 'devtools-protocol'; +import { getInspector } from './inspect.mts'; +import type { Inspector } from './index.mts'; +import { + EnsureCompletion, JSStringValue, ManagedRealm, NullValue, ObjectValue, SymbolValue, ThrowCompletion, Value, + getHostDefinedErrorStack, + type ValueCompletion, + getCurrentStack, + isECMAScriptFunctionObject, + SymbolDescriptiveString, + type EnvironmentRecordWithThisBinding, + EnvironmentRecord, + DeclarativeEnvironmentRecord, + ObjectEnvironmentRecord, + FunctionEnvironmentRecord, + GlobalEnvironmentRecord, + ModuleEnvironmentRecord, + OrdinaryObjectCreate, + Descriptor, + isArgumentExoticObject, + Agent, + surroundingAgent, + IsAccessorDescriptor, + isIntegerIndex, + isBuiltinFunctionObject, + isArrayBufferObject, + DataBlock, + CallSite, + CallFrame, + type OrdinaryObject, +} from '#self'; + +interface InspectedRealmDescriptor { + readonly realm: ManagedRealm; + readonly descriptor: Protocol.Runtime.ExecutionContextDescription; + readonly agent: Agent; + detach(): void; +} +export class InspectorContext { + #io: Inspector; + + constructor(io: Inspector) { + this.#io = io; + } + + realms: (InspectedRealmDescriptor | undefined)[] = []; + + attachRealm(realm: ManagedRealm, agent: Agent) { + const id = this.realms.length; + const descriptor: Protocol.Runtime.ExecutionContextDescription = { + id, + origin: realm.HostDefined.specifier || 'vm://repl', + name: realm.HostDefined.name || 'engine262', + uniqueId: id.toString(), + }; + this.realms.push({ + realm, + descriptor, + agent, + detach: () => { + realm.HostDefined.attachingInspector = oldInspector; + realm.HostDefined.attachingInspectorReportError = function attachingInspectorReportError(realm, error) { + if (this.attachingInspector && realm instanceof ManagedRealm) { + (this.attachingInspector as Inspector).console(realm, 'error' as Protocol.Runtime.ConsoleAPICalledEventType, [error]); + } + }; + }, + }); + const oldInspector = realm.HostDefined.attachingInspector; + realm.HostDefined.attachingInspector = this.#io; + const oldPromiseRejectionTracker = realm.HostDefined.promiseRejectionTracker; + realm.HostDefined.promiseRejectionTracker = (promise, operation) => { + oldPromiseRejectionTracker?.(promise, operation); + if (operation === 'reject') { + this.#io.sendEvent['Runtime.exceptionThrown']({ + timestamp: Date.now(), + exceptionDetails: this.createExceptionDetails(promise, true), + }); + } else { + const id = this.#exceptionMap.get(promise); + if (id) { + this.#io.sendEvent['Runtime.exceptionRevoked']({ + reason: 'Handler added to rejected promise', + exceptionId: id, + }); + } + } + }; + this.#io.sendEvent['Runtime.executionContextCreated']({ context: descriptor }); + } + + detachAgent(agent: Agent) { + for (const realm of this.realms) { + if (realm?.agent === agent) { + this.detachRealm(realm.realm); + } + } + } + + detachRealm(realm: ManagedRealm) { + const index = this.realms.findIndex((c) => c?.realm === realm); + if (index === -1) { + return; + } + const { descriptor } = this.realms[index]!; + realm.HostDefined.attachingInspector = undefined; + realm.HostDefined.attachingInspectorReportError = undefined; + this.realms[index] = undefined; + this.#io.sendEvent['Runtime.executionContextDestroyed']({ executionContextId: descriptor.id, executionContextUniqueId: descriptor.uniqueId }); + } + + getRealm(realm: ManagedRealm | string | number | undefined) { + if (realm === undefined) { + if (surroundingAgent.runningExecutionContext && surroundingAgent.currentRealmRecord instanceof ManagedRealm) { + realm = surroundingAgent.currentRealmRecord; + } else { + return undefined; + } + } + if (typeof realm === 'string') { + return this.realms.find((c) => c?.descriptor.uniqueId === realm); + } else if (typeof realm === 'number') { + return this.realms[realm]; + } + return this.realms.find((c) => c?.realm === realm); + } + + /** @deprecated in this case we are guessing the realm should be using, which may create bad result */ + getAnyRealm() { + return this.realms.find(Boolean); + } + + #idToObject = new Map(); + + // id 0 is falsy, skip it + #idToArrayBufferBlock: (undefined | ArrayBuffer)[] = [undefined]; + + #objectToId = new Map(); + + #objectCounter = 1; + + #internObject(object: ObjectValue | SymbolValue, 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; + } + + releaseObject(id: string) { + const object = this.#idToObject.get(id); + if (object) { + this.#idToObject.delete(id); + this.#objectToId.delete(object); + } + } + + releaseObjectGroup(group: string) { + for (const [id, object] of this.#idToObject.entries()) { + if (id.startsWith(group)) { + this.#idToObject.delete(id); + this.#objectToId.delete(object); + } + } + } + + getObject(objectId: string) { + return this.#idToObject.get(objectId); + } + + toRemoteObject(value: Value, options: { objectGroup?: string, generatePreview?: boolean }): Protocol.Runtime.RemoteObject { + return getInspector(value).toRemoteObject(value, (val) => this.#internObject(val, options.objectGroup), options.generatePreview); + } + + getProperties({ + objectId, accessorPropertiesOnly, generatePreview, nonIndexedPropertiesOnly, ownProperties, + }: Protocol.Runtime.GetPropertiesRequest): Protocol.Runtime.GetPropertiesResponse { + const object = this.getObject(objectId); + if (!(object instanceof ObjectValue)) { + return { result: [] }; + } + const wrap = (v: Value) => this.toRemoteObject(v, { generatePreview }); + + const properties: Protocol.Runtime.PropertyDescriptor[] = []; + const internalProperties: Protocol.Runtime.InternalPropertyDescriptor[] = []; + const privateProperties: Protocol.Runtime.PrivatePropertyDescriptor[] = []; + + object.PrivateElements.forEach((value) => { + privateProperties.push({ + name: value.Key.Description.stringValue(), + value: value.Value ? wrap(value.Value) : undefined, + get: value.Get ? wrap(value.Get) : undefined, + set: value.Set ? wrap(value.Set) : undefined, + }); + }); + + (() => { + let p: NullValue | ObjectValue = object; + while (p instanceof ObjectValue) { + for (const key of p.properties.keys()) { + if (nonIndexedPropertiesOnly && isIntegerIndex(key)) { + continue; + } + const desc = (p.properties.get(key)); + if (!desc) { + return; + } + if (accessorPropertiesOnly && !IsAccessorDescriptor(desc)) { + continue; + } + const descriptor: Protocol.Runtime.PropertyDescriptor = { + name: key instanceof JSStringValue + ? key.stringValue() + : SymbolDescriptiveString(key).stringValue(), + value: desc.Value && !('HostUninitializedBindingMarkerObject' in desc.Value) ? wrap(desc.Value) : undefined, + writable: desc.Writable === Value.true, + get: desc.Get ? wrap(desc.Get) : undefined, + set: desc.Set ? wrap(desc.Set) : undefined, + configurable: desc.Configurable === Value.true, + enumerable: desc.Enumerable === Value.true, + wasThrown: false, + isOwn: p === object, + symbol: key instanceof SymbolValue ? wrap(key) : undefined, + }; + properties.push(descriptor); + } + + if (ownProperties) { + break; + } + if ('Prototype' in p) { + p = (p as OrdinaryObject).Prototype; + } else { + p = Value.null; + } + } + })(); + + const additionalInternalFields = getInspector(object).toInternalProperties?.(object, (val) => this.#internObject(val, 'default'), generatePreview); + if (additionalInternalFields) { + internalProperties.push(...additionalInternalFields); + } + + if ('Prototype' in object) { + internalProperties.push({ + name: '[[Prototype]]', + value: wrap(object.Prototype as Value), + }); + } + if (isBuiltinFunctionObject(object) && object.nativeFunction.section) { + internalProperties.push({ + name: '[[Section]]', + value: { + type: 'string', + value: object.nativeFunction.section, + }, + }); + } + if (isArrayBufferObject(object) && object.ArrayBufferData instanceof DataBlock) { + internalProperties.push({ + name: '[[ArrayBufferByteLength]]', + value: { + type: 'number', + value: object.ArrayBufferByteLength, + }, + }); + this.#idToArrayBufferBlock.push(object.ArrayBufferData.buffer); + internalProperties.push({ + name: '[[ArrayBufferData]]', + value: { + type: 'number', + value: this.#idToArrayBufferBlock.length - 1, + }, + }); + } + + return { result: properties, internalProperties, privateProperties }; + } + + #exceptionMap = new WeakMap(); + + createExceptionDetails(completion: ThrowCompletion | Value, isPromise: boolean): Protocol.Runtime.ExceptionDetails { + const value = completion instanceof ThrowCompletion ? completion.Value : completion; + const stack = getHostDefinedErrorStack(value); + const frames = InspectorContext.callSiteToCallFrame(stack); + const exceptionId = this.#objectCounter; + this.#objectCounter += 1; + this.#exceptionMap.set(value, exceptionId); + return { + text: isPromise ? 'Uncaught (in promise)' : 'Uncaught', + stackTrace: stack ? { callFrames: frames } : undefined, + exception: getInspector(value).toRemoteObject(value, (val) => this.#internObject(val), false), + lineNumber: frames[0]?.lineNumber || 0, + columnNumber: frames[0]?.columnNumber || 0, + exceptionId, + scriptId: frames[0]?.scriptId, + url: frames[0]?.url, + }; + } + + static callSiteToCallFrame(callSite: readonly (CallSite | CallFrame)[] | undefined): Protocol.Runtime.CallFrame[] { + return callSite?.map((call) => call.toCallFrame()!).filter(Boolean) || []; + } + + createEvaluationResult(completion: ValueCompletion): Protocol.Runtime.EvaluateResponse { + completion = EnsureCompletion(completion); + if (!(completion.Value instanceof Value)) { + throw new RangeError('Invalid completion value'); + } + return { + exceptionDetails: completion instanceof ThrowCompletion ? this.createExceptionDetails(completion, false) : undefined, + result: this.toRemoteObject(completion.Value, {}), + }; + } + + getDebuggerCallFrame(): Protocol.Debugger.CallFrame[] { + const stacks = getCurrentStack(false); + const length = surroundingAgent.executionContextStack.length; + return stacks.map((stack, index): Protocol.Debugger.CallFrame => { + if (!stack.getScriptId()) { + return undefined!; + } + const scopeChain: Protocol.Debugger.Scope[] = []; + let env: EnvironmentRecord | NullValue = stack.context.LexicalEnvironment; + while (env instanceof EnvironmentRecord) { + const result = getDisplayObjectFromEnvironmentRecord(env); + if (result) { + scopeChain.push({ type: result.type, object: this.toRemoteObject(result.object, {}) }); + } + env = env.OuterEnv; + } + return { + callFrameId: String(length - index - 1), + functionName: stack.getFunctionName() || '', + location: { + scriptId: stack.getScriptId()!, + lineNumber: (stack.lineNumber || 1) - 1, + columnNumber: (stack.columnNumber || 1) - 1, + }, + this: this.toRemoteObject(HostGetThisEnvironment(stack.context.LexicalEnvironment), {}), + url: stack.getSpecifier() || '', + canBeRestarted: false, + functionLocation: isECMAScriptFunctionObject(stack.context.Function) ? { + lineNumber: (stack.context.Function.ECMAScriptCode?.location.start.line || 1) - 1, + columnNumber: (stack.context.Function.ECMAScriptCode?.location.start.column || 1) - 1, + scriptId: stack.getScriptId() || '', + } : undefined, + scopeChain, + }; + }).filter(Boolean); + } + + evaluateMode: 'script' | 'module' | 'console' = 'script'; +} + +function HostGetThisEnvironment(env: EnvironmentRecord | NullValue): Value { + while (!(env instanceof NullValue)) { + const exists = env.HasThisBinding(); + if (exists === Value.true) { + const value = (env as EnvironmentRecordWithThisBinding).GetThisBinding(); + if (value instanceof ThrowCompletion) { + return Value.undefined; + } + return value as Value; + } + const outer = env.OuterEnv; + env = outer; + } + throw new ReferenceError('No this environment found'); +} + +function getDisplayObjectFromEnvironmentRecord(record: EnvironmentRecord): undefined | { type: Protocol.Debugger.Scope['type'], object: ObjectValue } { + if (record instanceof DeclarativeEnvironmentRecord) { + const object = OrdinaryObjectCreate(Value.null, ['HostInspectorScopePreview']); + for (const [key, binding] of record.bindings) { + const value = binding.initialized ? binding.value! : OrdinaryObjectCreate(Value.null, ['HostUninitializedBindingMarkerObject']); + if (isArgumentExoticObject(value)) { + continue; + } + object.properties.set(key, Descriptor({ + Enumerable: isArgumentExoticObject(value) ? Value.false : Value.true, + Value: value, + Writable: binding.mutable ? Value.true : Value.false, + })); + } + let type: Protocol.Debugger.Scope['type'] = 'block'; + if (record instanceof FunctionEnvironmentRecord) { + type = 'local'; + } else if (record instanceof ModuleEnvironmentRecord) { + type = 'module'; + } + if (type !== 'local' && !object.properties.size) { + return undefined; + } + return { type, object }; + } else if (record instanceof ObjectEnvironmentRecord) { + return { type: record.IsWithEnvironment === Value.true ? 'with' : 'global', object: record.BindingObject }; + } else if (record instanceof GlobalEnvironmentRecord) { + return { type: 'global', object: record.GlobalThisValue }; + } + throw new TypeError('Unknown environment record'); +} diff --git a/lib-src/inspector/index.mts b/lib-src/inspector/index.mts new file mode 100644 index 0000000..4ace37b --- /dev/null +++ b/lib-src/inspector/index.mts @@ -0,0 +1,139 @@ +import type { Protocol } from 'devtools-protocol'; +import { InspectorContext } from './context.mts'; +import * as impl from './methods.mts'; +import type { DebuggerContext, DebuggerPreference, DevtoolEvents } from './types.mts'; +import { getParsedEvent } from './internal-utils.mts'; +import { + Agent, ManagedRealm, Realm, type Arguments, +} from '#self'; + +const ignoreNamespaces = ['Network']; +const ignoreMethods: string[] = []; + +export type { DebuggerPreference } from './types.mts'; +export { createConsole } from './utils.mts'; + +interface AgentRecord { + readonly agent: Agent; + onDetach(): void; +} +export abstract class Inspector { + #context = new InspectorContext(this); + + #agents: AgentRecord[] = []; + + attachAgent(agent: Agent, priorRealms: ManagedRealm[]) { + const oldOnDebugger = agent.hostDefinedOptions.onDebugger; + agent.hostDefinedOptions.onDebugger = () => { + oldOnDebugger?.(); + this.sendEvent['Debugger.paused']({ + reason: 'debugCommand', + callFrames: this.#context.getDebuggerCallFrame(), + }); + }; + + const oldOnRealmCreated = agent.hostDefinedOptions.onRealmCreated; + agent.hostDefinedOptions.onRealmCreated = (realm) => { + oldOnRealmCreated?.(realm); + this.#context.attachRealm(realm, agent); + }; + + const oldOnScriptParsed = agent.hostDefinedOptions.onScriptParsed; + agent.hostDefinedOptions.onScriptParsed = (script, id) => { + oldOnScriptParsed?.(script, id); + const realmId = this.#context.getRealm(script.Realm as ManagedRealm)?.descriptor.id; + if (realmId === undefined) { + return; + } + this.sendEvent['Debugger.scriptParsed'](getParsedEvent(script, id, realmId)); + }; + this.#agents.push({ + agent, + onDetach: () => { + agent.hostDefinedOptions.onDebugger = oldOnDebugger; + agent.hostDefinedOptions.onRealmCreated = oldOnRealmCreated; + agent.hostDefinedOptions.onScriptParsed = oldOnScriptParsed; + this.#agents = this.#agents.filter((x) => x.agent !== agent); + }, + }); + priorRealms.forEach((realm) => { + this.#context.attachRealm(realm, agent); + }); + } + + detachAgent(agent: Agent) { + const record = this.#agents.find((x) => x.agent === agent); + record?.onDetach(); + this.#context.detachAgent(agent); + } + + protected abstract send(data: object): void; + + readonly preference: DebuggerPreference = { previewDebug: false }; + + protected onMessage(id: unknown, methodArg: string, params: unknown): void { + if (ignoreMethods.includes(methodArg)) { + return; + } + const [namespace, method] = methodArg.split('.'); + if (ignoreNamespaces.includes(namespace)) { + return; + } + if (!(namespace in impl)) { + // eslint-disable-next-line no-console + console.error(`Unknown namespace requested: ${namespace}`); + return; + } + const ns = (impl as Record)[namespace]; + if (!(method in ns)) { + // eslint-disable-next-line no-console + console.error(`Unknown method requested: ${namespace}.${method}`); + return; + } + + const f = (ns as Record unknown>)[method]; + new Promise((resolve) => { + resolve(f(params, this.#debugContext)); + }).then((result = {}) => { + this.send({ id, result }); + }); + } + + sendEvent: DevtoolEvents = Object.create(new Proxy({}, { + get: (_, key: string) => { + const f = (params: Record) => { + this.send({ method: key, params }); + }; + Object.defineProperty(this.sendEvent, key, { value: f }); + return f; + }, + })); + + console(realm: Realm, type: Protocol.Runtime.ConsoleAPICalledEventType, args: Arguments) { + const context = this.#context.getRealm(realm as ManagedRealm); + if (!context) { + return; + } + this.sendEvent['Runtime.consoleAPICalled']({ + type, + args: args.map((x) => this.#context.toRemoteObject(x, { })), + executionContextId: context.descriptor.id, + timestamp: Date.now(), + }); + } + + #debugContext: DebuggerContext = { + sendEvent: this.sendEvent, + preference: this.preference, + context: this.#context, + onDebuggerAttached: () => { + this.#context.realms.forEach((realm) => { + if (realm) { + this.sendEvent['Runtime.executionContextCreated']({ + context: realm.descriptor, + }); + } + }); + }, + }; +} diff --git a/lib-src/inspector/inspect.mts b/lib-src/inspector/inspect.mts new file mode 100644 index 0000000..91f3cea --- /dev/null +++ b/lib-src/inspector/inspect.mts @@ -0,0 +1,624 @@ +import type { Protocol } from 'devtools-protocol'; +import { + BigIntValue, + Descriptor, + evalQ, + Get, + IntrinsicsFunctionToString, isArrayBufferObject, isArrayExoticObject, IsCallable, isDataViewObject, isDateObject, isECMAScriptFunctionObject, isErrorObject, isIntegerIndex, isMapObject, isPromiseObject, isProxyExoticObject, isRegExpObject, isSetObject, isTypedArrayObject, isWeakMapObject, isWeakSetObject, JSStringValue, NumberValue, ObjectValue, PrivateElementRecord, PrivateName, R, surroundingAgent, SymbolDescriptiveString, SymbolValue, ToString, skipDebugger, UndefinedValue, Value, type ArrayBufferObject, type BooleanValue, type DataViewObject, type DateObject, type FunctionObject, type MapObject, type NullValue, type PromiseObject, type PropertyKeyValue, type ProxyObject, type RegExpObject, type SetObject, type TypedArrayObject, + type WeakMapObject, + type WeakSetObject, + type ModuleNamespaceObject, + isModuleNamespaceObject, + DataBlock, + TypedArrayGetElement, + TypedArrayLength, + MakeTypedArrayWithBufferWitnessRecord, + DateProto_toISOString, + ValueOfNormalCompletion, + NormalCompletion, + type ShadowRealmObject, + isShadowRealmObject, + isWrappedFunctionExoticObject, + ArrayExoticObjectInternalMethods, + F, + type TemporalInstantObject, + TemporalInstantToString, + isTemporalInstantObject, + TemporalDurationToString, + type TemporalDurationObject, + isTemporalDurationObject, + isTemporalPlainDateObject, + type TemporalPlainDateObject, + ISODateTimeToString, + isTemporalPlainDateTimeObject, + type TemporalPlainDateTimeObject, + TemporalMonthDayToString, + isTemporalPlainMonthDayObject, + type TemporalPlainMonthDayObject, + TimeRecordToString, + isTemporalPlainTimeObject, + type TemporalPlainTimeObject, + TemporalYearMonthToString, + isTemporalPlainYearMonthObject, + type TemporalPlainYearMonthObject, + TemporalDateToString, + TemporalZonedDateTimeToString, + isTemporalZonedDateTimeObject, + type TemporalZonedDateTimeObject, +} from '#self'; + +/* +Test code: copy this into the inspector console. +primitive: console.log('primitive:', null, undefined, true, false, 0, -0, NaN, Infinity, -Infinity, 1n, 'string', Symbol(), Symbol('text'), Symbol.for('global'), Symbol.iterator); +fn: console.log('builtin:', eval, '\nfunction:', function() { code }, '\ngenerator:', function*() { code }, '\nasync:', async function() { code }, '\nasync generator:', async function*() { code }, '\narrow:', () => { code }, '\narrow async:', async () => { code }); + +normal: console.log('normal:', {}, new (class T { #a }), globalThis); +arraybuffer: console.log('arraybuffer:', new ArrayBuffer(8)); +dataview: console.log('dataview:', new DataView(new ArrayBuffer(8))); +map: console.log('map:', new Map(), new Map([[eval, globalThis], [1, 2]])); +set: console.log('set:', new Set(), new Set([1, globalThis])); +weakmap: console.log('weakmap:', new WeakMap(), new WeakMap([[{}, 1]])); +weakset: console.log('weakset:', new WeakSet(), new WeakSet([{}])); +date: console.log('date:', new Date()); +promise: console.log('promise:', new Promise(() => {}), Promise.resolve(globalThis), Promise.reject(globalThis)); +proxy: { const x = Proxy.revocable({}, {}); x.revoke(); console.log('proxy:', new Proxy({}, {}), new Proxy(function() {}, {}), x.proxy); } +regexp: console.log('regexp:', /pattern/, new RegExp('pattern', 'g')); +array: console.log('array:', [], [1, 2], Object.assign([1, 2], { a: 1 }), [0, ,,, 3]); +typedarray: console.log('typedarray:', new Int8Array(8), new Int16Array(8), new Int32Array(8), new Uint8Array(8), new Uint16Array(8), new Uint32Array(8), new Uint8ClampedArray(8), new Float32Array(8), new Float64Array(8), new BigInt64Array(8), new BigUint64Array(8)); +*/ +interface Inspector { + toRemoteObject(value: T, getObjectId: (val: SymbolValue | ObjectValue) => string, generatePreview: boolean | undefined): Protocol.Runtime.RemoteObject; + toObjectPreview(value: T): Protocol.Runtime.ObjectPreview; + toPropertyPreview(name: string, value: T): Protocol.Runtime.PropertyPreview; + toDescription(value: T): string; + toInternalProperties?(value: T, getObjectId: (val: SymbolValue | ObjectValue) => string, generatePreview: boolean | undefined): Protocol.Runtime.InternalPropertyDescriptor[]; +} + +const Null: Inspector = { + toRemoteObject: () => ({ type: 'object', subtype: 'null', value: null }), + toObjectPreview: () => ({ + type: 'object', subtype: 'null', properties: [], overflow: false, + }), + toPropertyPreview: (name) => ({ + name, type: 'object', subtype: 'null', value: 'null', + }), + toDescription: () => '', +}; + +const Undefined: Inspector = { + toRemoteObject: () => ({ type: 'undefined' }), + toObjectPreview: () => ({ + type: 'undefined', properties: [], overflow: false, + }), + toPropertyPreview: (name) => ({ + name, type: 'undefined', value: 'undefined', + }), + toDescription: () => 'undefined', +}; + +const Boolean: Inspector = { + toRemoteObject: (value) => ({ type: 'boolean', value: value.booleanValue() }), + toPropertyPreview: (name, value) => ({ + name, type: 'boolean', value: value.booleanValue().toString(), + }), + toObjectPreview(value) { + return { + type: 'boolean', + value: value.booleanValue(), + description: value.booleanValue().toString(), + overflow: false, + properties: [], + }; + }, + toDescription: (value) => value.booleanValue().toString(), +}; + +const Symbol: Inspector = { + toRemoteObject: (value, getObjectId) => ({ + type: 'symbol', + description: SymbolDescriptiveString(value).stringValue(), + objectId: getObjectId(value), + }), + toPropertyPreview: (name, value) => ({ + name, type: 'symbol', value: SymbolDescriptiveString(value).stringValue(), + }), + toObjectPreview: (value) => ({ + type: 'symbol', + description: SymbolDescriptiveString(value).stringValue(), + overflow: false, + properties: [], + }), + toDescription: (value) => SymbolDescriptiveString(value).stringValue(), +}; + +const String: Inspector = { + toRemoteObject: (value) => ({ type: 'string', value: value.stringValue() }), + toPropertyPreview(name, value) { + return { + name, type: 'string', value: value.stringValue(), + }; + }, + toObjectPreview(value) { + return { + type: 'string', + description: value.stringValue(), + overflow: false, + properties: [], + }; + }, + toDescription: (value) => value.stringValue(), +}; + +const Number: Inspector = { + toRemoteObject(value) { + const v = R(value); + let description = v.toString(); + const isNeg0 = Object.is(v, -0); + // Includes values `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. + if (isNeg0 || !globalThis.Number.isFinite(v)) { + if (typeof v === 'bigint') { + description += 'n'; + return { type: 'bigint', unserializableValue: description, description }; + } + return { type: 'number', unserializableValue: description, description: isNeg0 ? '-0' : description }; + } + return { type: 'number', value: v, description }; + }, + toPropertyPreview(name, value) { + return { + name, type: 'number', value: this.toDescription(value), + }; + }, + toObjectPreview(value) { + return { + type: 'number', + description: this.toDescription(value), + overflow: false, + properties: [], + }; + }, + toDescription: (value) => { + const r = R(value); + return value instanceof BigIntValue ? `${r}n` : r.toString(); + }, +}; + +function unwrapFunction(value: FunctionObject): FunctionObject { + if (isWrappedFunctionExoticObject(value)) { + return unwrapFunction(value.WrappedTargetFunction); + } + return value; +} +const Function: Inspector = { + toRemoteObject(value, getObjectId) { + value = unwrapFunction(value); + const result: Protocol.Runtime.RemoteObject = { + type: 'function', + objectId: getObjectId(value), + }; + result.description = IntrinsicsFunctionToString(value); + if (isECMAScriptFunctionObject(value) && value.ECMAScriptCode) { + if (value.ECMAScriptCode.type === 'FunctionBody') { + result.className = 'Function'; + } else if (value.ECMAScriptCode.type === 'GeneratorBody') { + result.className = 'GeneratorFunction'; + } else if (value.ECMAScriptCode.type === 'AsyncBody') { + result.className = 'AsyncFunction'; + } else if (value.ECMAScriptCode.type === 'AsyncGeneratorBody') { + result.className = 'AsyncGeneratorFunction'; + } + } else { + result.className = 'Function'; + } + return result; + }, + toPropertyPreview: (name) => ({ name, type: 'function', value: '' }), + toObjectPreview(value) { + return { + type: 'function', + description: IntrinsicsFunctionToString(value), + overflow: false, + properties: [], + }; + }, + toDescription: () => 'Function', +}; + +class ObjectInspector implements Inspector { + subtype; + + className; + + toDescription; + + private toEntries; + + private additionalProperties; + + private internalProperties; + + constructor( + className: string | ((value: Value) => string), + subtype: Protocol.Runtime.RemoteObject['subtype'], + toDescription: (value: T) => string, + additionalOptions?: { + entries?: (value: T) => Protocol.Runtime.ObjectPreview['entries']; + additionalProperties?: (value: T) => Iterable<[string, Value]>; + internalProperties?: (value: T) => Iterable<[string, Value | MapObject['MapData'] | SetObject['SetData']]>; + }, + ) { + this.className = className; + this.subtype = subtype; + this.toDescription = toDescription; + this.toEntries = additionalOptions?.entries; + this.additionalProperties = additionalOptions?.additionalProperties; + this.internalProperties = additionalOptions?.internalProperties; + } + + toRemoteObject(value: T, getObjectId: (val: ObjectValue) => string): Protocol.Runtime.RemoteObject { + return { + type: 'object', + subtype: this.subtype, + objectId: getObjectId(value), + className: typeof this.className === 'string' ? this.className : this.className(value), + description: this.toDescription(value), + preview: this.toObjectPreview(value), + }; + } + + toPropertyPreview(name: string, value: T): Protocol.Runtime.PropertyPreview { + return { + name, + type: 'object', + subtype: this.subtype, + value: this.toDescription(value), + }; + } + + toInternalProperties(value: T, getObjectId: (val: ObjectValue | SymbolValue) => string, generatePreview: boolean | undefined): Protocol.Runtime.InternalPropertyDescriptor[] { + const internalProperties = [...this.internalProperties?.(value) || []]; + if (!internalProperties.length) { + return []; + } + return internalProperties.map(([name, val]): Protocol.Runtime.InternalPropertyDescriptor => { + let value: Protocol.Runtime.RemoteObject; + if (val instanceof Value) { + value = getInspector(val).toRemoteObject(val, getObjectId, generatePreview); + } else { + const array = new ObjectValue([]); + array.DefineOwnProperty = ArrayExoticObjectInternalMethods.DefineOwnProperty; + array.properties.set('length', Descriptor({ Value: F(val.length) })); + for (const [index, item] of val.entries()) { + let value; + if (item instanceof Value) { + value = item; + } else { + if (!item?.Key || !item.Value) { + continue; + } + value = new ObjectValue(['InspectorEntry']); + value.properties.set('key', Descriptor({ Value: item.Key })); + value.properties.set('value', Descriptor({ Value: item.Value })); + } + array.properties.set(Value(index.toString()), Descriptor({ Value: value })); + } + value = Array.toRemoteObject(array, getObjectId, generatePreview); + } + return ({ name, value }); + }); + } + + toObjectPreview(value: T): Protocol.Runtime.ObjectPreview { + const e = this.toEntries?.(value); + return { + type: 'object', + subtype: this.subtype, + description: this.toDescription(value), + entries: e?.length ? e : undefined, + ...propertiesToPropertyPreview(value, [...this.internalProperties?.(value) || [], ...this.additionalProperties?.(value) || []]), + }; + } +} + +const InspectorEntry = new ObjectInspector('Object', 'internal#entry' as never, (value) => { + const key = value.properties.get(Value('key'))!.Value!; + const val = value.properties.get(Value('value'))!.Value!; + return `{${getInspector(key).toDescription(key)} => ${getInspector(val).toDescription(val)}}`; +}); + +const Default = new ObjectInspector('Object', undefined, (object) => { + const [ctor] = object.ConstructedBy; + if (!ctor) { + return 'Object'; + } + return propertyNameToString(ctor.HostInitialName); +}); + +const ArrayBuffer = new ObjectInspector('ArrayBuffer', 'arraybuffer', (value) => `ArrayBuffer(${value.ArrayBufferByteLength})`, {}); +const DataView = new ObjectInspector('DataView', 'dataview', (value) => `DataView(${value.ByteLength})`); +const Error = new ObjectInspector('SyntaxError', 'error', (value) => { + let text = ''; + surroundingAgent.debugger_scopePreview(() => { + evalQ((Q) => { + if (value instanceof ObjectValue) { + const stack = Q(skipDebugger(Get(value, Value('stack')))); + if (stack !== Value.undefined) { + text += Q(skipDebugger(ToString(stack))).stringValue(); + } + } + }); + }); + return text; +}); + +const Map = new ObjectInspector('Map', 'map', (value) => `Map(${value.MapData.filter((x) => !!x.Key).length})`, { + additionalProperties: (value) => [['size', Value(value.MapData.filter((x) => !!x.Key).length)]], + internalProperties: (value) => [['[[Entries]]', value.MapData]], + entries: (value) => value.MapData.filter((x) => x.Key).map(({ Key, Value }) => ({ + key: getInspector(Key!).toObjectPreview(Key!), + value: getInspector(Value!).toObjectPreview(Value!), + })), +}); +const Set = new ObjectInspector('Set', 'set', (value) => `Set(${value.SetData.filter(globalThis.Boolean).length})`, { + additionalProperties: (value) => [['size', Value(value.SetData.filter(globalThis.Boolean).length)]], + internalProperties: (value) => [['[[Entries]]', value.SetData]], + entries: (value) => value.SetData.filter(globalThis.Boolean).map((Value) => ({ + value: getInspector(Value!).toObjectPreview(Value!), + })), +}); +const WeakMap = new ObjectInspector('WeakMap', 'weakmap', () => 'WeakMap', { + internalProperties: (value) => [['[[Entries]]', value.WeakMapData]], + entries: (value) => value.WeakMapData.filter((x) => x.Key).map(({ Key, Value }) => ({ + key: getInspector(Key!).toObjectPreview(Key!), + value: getInspector(Value!).toObjectPreview(Value!), + })), +}); +const WeakSet = new ObjectInspector('WeakSet', 'weakset', () => 'WeakSet', { + internalProperties: (value) => [['[[Entries]]', value.WeakSetData]], + entries: (value) => value.WeakSetData.filter(globalThis.Boolean).map((Value) => ({ + value: getInspector(Value!).toObjectPreview(Value!), + })), +}); + +const Date = new ObjectInspector('Date', 'date', ((value: DateObject) => { + if (!globalThis.Number.isFinite(R(value.DateValue))) { + return 'Invalid Date'; + } + const val = DateProto_toISOString([], { thisValue: value, NewTarget: Value.undefined }); + return ValueOfNormalCompletion(val as NormalCompletion).stringValue(); +})); +const TemporalInstant = new ObjectInspector( + 'Temporal.Instant', + 'date', + (value) => `Temporal.Instant <${TemporalInstantToString(value, undefined, 'auto')}>`, +); +const TemporalDuration = new ObjectInspector('Temporal.Duration', 'date', (value) => `Temporal.Duration <${TemporalDurationToString(value, 'auto')}>`); +const TemporalPlainDate = new ObjectInspector('Temporal.PlainDate', 'date', (value) => `Temporal.PlainDate <${TemporalDateToString(value, 'auto')}>`); +const TemporalPlainDateTime = new ObjectInspector( + 'Temporal.PlainDateTime', + 'date', + (value) => `Temporal.PlainDateTime <${ISODateTimeToString(value.ISODateTime, value.Calendar, 'auto', 'auto')}>`, +); +const TemporalPlainMonthDay = new ObjectInspector( + 'Temporal.PlainMonthDay', + 'date', + (value) => `Temporal.PlainMonthDay <${TemporalMonthDayToString(value, 'auto')}>`, +); +const TemporalPlainTime = new ObjectInspector('Temporal.PlainTime', 'date', (value) => `Temporal.PlainTime <${TimeRecordToString(value.Time, 'auto')}>`); +const TemporalPlainYearMonth = new ObjectInspector( + 'Temporal.PlainYearMonth', + 'date', + (value) => `Temporal.PlainYearMonth <${TemporalYearMonthToString(value, 'auto')}>`, +); +const TemporalZonedDateTime = new ObjectInspector( + 'Temporal.ZonedDateTime', + 'date', + (value) => `Temporal.ZonedDateTime <${TemporalZonedDateTimeToString(value, 'auto', 'auto', 'auto', 'auto')}>`, +); +const Promise = new ObjectInspector('Promise', 'promise', () => 'Promise', { + internalProperties: (value) => [['[[PromiseState]]', Value(value.PromiseState)], ['[[PromiseResult]]', value.PromiseResult || Value.undefined]], +}); +const Proxy = new ObjectInspector('Proxy', 'proxy', (value) => { + if (IsCallable(value.ProxyTarget)) { + return 'Proxy(Function)'; + } + if (value.ProxyTarget instanceof ObjectValue) { + return 'Proxy(Object)'; + } + return 'Proxy'; +}); +const RegExp = new ObjectInspector('RegExp', 'regexp', (value) => `/${value.OriginalSource.stringValue()}/${value.OriginalFlags.stringValue()}`); +const Module = new ObjectInspector('Module', undefined, () => 'Module', {}); +const ShadowRealm = new ObjectInspector('ShadowRealm', undefined, () => 'ShadowRealm', { + internalProperties: (realm) => [['[[GlobalObject]]', realm.ShadowRealm.GlobalObject]], +}); + +const Array: Inspector = { + toRemoteObject(value, getObjectId) { + return { + type: 'object', + className: 'Array', + subtype: 'array', + objectId: getObjectId(value), + description: getInspector(value).toDescription(value), + preview: this.toObjectPreview?.(value), + }; + }, + toPropertyPreview(name, value) { + return { + name, type: 'object', subtype: 'array', value: this.toDescription(value), + }; + }, + toObjectPreview(value) { + const result: Protocol.Runtime.ObjectPreview = { + type: 'object', + subtype: 'array', + overflow: false, + properties: [], + description: this.toDescription(value), + }; + const indexProp: Protocol.Runtime.PropertyPreview[] = []; + const otherProp: Protocol.Runtime.PropertyPreview[] = []; + for (const [key, desc] of value.properties) { + if (indexProp.length > 100) { + result.overflow = true; + break; + } + if (isIntegerIndex(key)) { + indexProp.push(propertyToPropertyPreview(key, desc)); + } else if (!(key instanceof JSStringValue && key.stringValue() === 'length')) { + otherProp.push(propertyToPropertyPreview(key, desc)); + } + } + result.properties = indexProp.concat(otherProp).slice(0, 100); + return result; + }, + toDescription(value) { + const length = [...value.properties.entries()].find(([key]) => key instanceof JSStringValue && key.stringValue() === 'length'); + if (!length || !(length[1].Value instanceof NumberValue)) { + throw new TypeError('Bad ArrayExoticObject'); + } + return `Array(${R(length[1].Value)})`; + }, +}; +const TypedArray = new ObjectInspector('TypedArray', 'typedarray', (value) => `${value.TypedArrayName.stringValue()}(${value.ArrayLength})`); + +function propertyNameToString(value: PropertyKeyValue | PrivateName): string { + if (value instanceof JSStringValue) { + return value.stringValue(); + } else if (value instanceof PrivateName) { + return value.Description.stringValue(); + } else { + return SymbolDescriptiveString(value).stringValue(); + } +} +function propertyToPropertyPreview(key: PropertyKeyValue | PrivateName, desc: Descriptor | PrivateElementRecord): Protocol.Runtime.PropertyPreview { + const name = propertyNameToString(key); + if (desc.Get || desc.Set) { + return { name, type: 'accessor' }; + } else { + return getInspector(desc.Value!).toPropertyPreview(name, desc.Value!); + } +} + +function propertiesToPropertyPreview(value: ObjectValue, extra: undefined | Iterable<[string, Value | MapObject['MapData'] | SetObject['SetData']]>, max = 5) { + let overflow = false; + const properties: Protocol.Runtime.PropertyPreview[] = []; + if (extra) { + for (const [key, value] of extra) { + if (value instanceof Value) { + properties.push(getInspector(value).toPropertyPreview(key, value)); + } + // TODO:... handle Value[] + } + } + if (isTypedArrayObject(value) && value.ViewedArrayBuffer instanceof ObjectValue && value.ViewedArrayBuffer.ArrayBufferData instanceof DataBlock) { + const record = MakeTypedArrayWithBufferWitnessRecord(value, 'seq-cst'); + const length = TypedArrayLength(record); + for (let index = 0; index < length; index += 1) { + const index_value = TypedArrayGetElement(value, Value(index)); + if (index_value instanceof UndefinedValue) { + break; + } + if (properties.length > 100) { + overflow = true; + break; + } + properties.push(getInspector(index_value).toPropertyPreview(index.toString(), index_value)); + } + properties.push( + { + name: 'buffer', type: 'object', subtype: 'arraybuffer', value: `ArrayBuffer(${value.ViewedArrayBuffer.ArrayBufferData.byteLength})`, + }, + { name: 'byteLength', type: 'number', value: globalThis.String(value.ArrayLength) }, + { name: 'byteOffset', type: 'number', value: globalThis.String(value.ByteOffset) }, + { name: 'length', type: 'number', value: globalThis.String(length) }, + ); + } + for (const [key, desc] of value.properties) { + if (properties.length > max) { + overflow = true; + break; + } + properties.push(propertyToPropertyPreview(key, desc)); + } + for (const desc of value.PrivateElements) { + if (properties.length > max) { + overflow = true; + break; + } + properties.push(propertyToPropertyPreview(desc.Key, desc)); + } + return { overflow, properties }; +} + +export function getInspector(value: Value): Inspector { + switch (true) { + case value === Value.null: + return Null; + case value === Value.undefined: + return Undefined; + case value === Value.true || value === Value.false: + return Boolean; + case value instanceof SymbolValue: + return Symbol; + case value instanceof JSStringValue: + return String; + case value instanceof NumberValue: + case value instanceof BigIntValue: + return Number; + case isProxyExoticObject(value): + return Proxy; + case IsCallable(value): + return Function; + case isArrayExoticObject(value): + return Array; + case isRegExpObject(value): + return RegExp; + case isDateObject(value): + return Date; + case isMapObject(value): + return Map; + case isSetObject(value): + return Set; + case isWeakMapObject(value): + return WeakMap; + case isWeakSetObject(value): + return WeakSet; + // generator + case isErrorObject(value): + return Error; + case isPromiseObject(value): + return Promise; + case isTypedArrayObject(value): + return TypedArray; + case isArrayBufferObject(value): + return ArrayBuffer; + case isDataViewObject(value): + return DataView; + case isModuleNamespaceObject(value): + return Module; + case isShadowRealmObject(value): + return ShadowRealm; + case isTemporalInstantObject(value): + return TemporalInstant; + case isTemporalDurationObject(value): + return TemporalDuration; + case isTemporalPlainDateObject(value): + return TemporalPlainDate; + case isTemporalPlainDateTimeObject(value): + return TemporalPlainDateTime; + case isTemporalPlainMonthDayObject(value): + return TemporalPlainMonthDay; + case isTemporalPlainTimeObject(value): + return TemporalPlainTime; + case isTemporalPlainYearMonthObject(value): + return TemporalPlainYearMonth; + case isTemporalZonedDateTimeObject(value): + return TemporalZonedDateTime; + case (value as ObjectValue).internalSlotsList.includes('InspectorEntry'): + return InspectorEntry; + default: + return Default; + } +} diff --git a/lib-src/inspector/internal-utils.mts b/lib-src/inspector/internal-utils.mts new file mode 100644 index 0000000..89ba7ce --- /dev/null +++ b/lib-src/inspector/internal-utils.mts @@ -0,0 +1,18 @@ +import type { Protocol } from 'devtools-protocol'; +import { DynamicParsedCodeRecord, SourceTextModuleRecord, type ScriptRecord } from '#self'; + +export function getParsedEvent(source: ScriptRecord | SourceTextModuleRecord | DynamicParsedCodeRecord, id: string, executionContextId: number): Protocol.Debugger.ScriptParsedEvent { + const lines = source.ECMAScriptCode.sourceText.split('\n'); + return { + isModule: source instanceof SourceTextModuleRecord, + scriptId: id, + url: source.HostDefined.specifier || `vm:///${id}`, + startLine: 0, + startColumn: 0, + endLine: lines.length, + endColumn: lines.pop()!.length, + executionContextId, + hash: '', + buildId: '', + }; +} diff --git a/lib-src/inspector/js_protocol.json b/lib-src/inspector/js_protocol.json new file mode 100644 index 0000000..a200a5b --- /dev/null +++ b/lib-src/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/lib-src/inspector/methods.mts b/lib-src/inspector/methods.mts new file mode 100644 index 0000000..a1c5329 --- /dev/null +++ b/lib-src/inspector/methods.mts @@ -0,0 +1,355 @@ +import type { Protocol } from 'devtools-protocol'; +import type { + DebuggerContext, + DebuggerNamespace, HeapProfilerNamespace, ProfilerNamespace, RuntimeNamespace, + TargetNamespace, +} from './types.mts'; +import { getParsedEvent } from './internal-utils.mts'; +import { InspectorContext } from './context.mts'; +import { + Call, NormalCompletion, ObjectValue, ParseScript, runJobQueue, ScriptRecord, surroundingAgent, ThrowCompletion, skipDebugger, Value, type FunctionObject, + ParseModule, + SourceTextModuleRecord, performDevtoolsEval, + ValueOfNormalCompletion, + JSStringValue, + evalQ, + Assert, + kInternal, + captureStack, +} from '#self'; + +export const Debugger: DebuggerNamespace = { + enable(_req, { onDebuggerAttached }) { + onDebuggerAttached(); + return { debuggerId: 'debugger.0' }; + }, + getScriptSource({ scriptId }) { + const source = surroundingAgent.parsedSources.get(scriptId); + if (!source) { + throw new Error('Not found'); + } + return { scriptSource: source.ECMAScriptCode.sourceText }; + }, + setAsyncCallStackDepth() { }, + setBlackboxPatterns() { }, + setBlackboxExecutionContexts() { }, + + // #region breakpoints + getPossibleBreakpoints() { + // getPossibleBreakpoints({ start, end, restrictToFunction }) { + return { locations: [] }; + // return { locations: getBreakpointCandidates(start, end, restrictToFunction) }; + }, + removeBreakpoint({ breakpointId }) { + surroundingAgent?.removeBreakpoint(breakpointId); + }, + // setBreakpoint({ location, condition }) { }, + setBreakpointByUrl(req) { + return surroundingAgent?.addBreakpointByUrl(req); + }, + // setBreakpointOnFunctionCall({ objectId, condition }) { }, + setBreakpointsActive({ active }) { + surroundingAgent.breakpointsEnabled = active; + }, + // setInstrumentationBreakpoint({ instrumentation }) { }, + setPauseOnExceptions({ state }) { + if (surroundingAgent) { + surroundingAgent.pauseOnExceptions = state === 'none' ? undefined : state; + } + }, + // #endregion + + stepInto(_, { sendEvent }) { + sendEvent['Debugger.resumed'](); + surroundingAgent.resumeEvaluate({ pauseAt: 'step-in' }); + }, + resume(_, { sendEvent }) { + sendEvent['Debugger.resumed'](); + surroundingAgent.resumeEvaluate(); + }, + stepOver(_req, { sendEvent }) { + sendEvent['Debugger.resumed'](); + surroundingAgent.resumeEvaluate({ pauseAt: 'step-over' }); + }, + stepOut(_req, { sendEvent }) { + sendEvent['Debugger.resumed'](); + surroundingAgent.resumeEvaluate({ pauseAt: 'step-out' }); + }, + evaluateOnCallFrame(req, context) { + return evaluate({ + ...req, + uniqueContextId: context.context.getRealm(undefined)!.descriptor.uniqueId, + evalMode: context.context.evaluateMode, + }, context); + }, + engine262_setEvaluateMode({ mode }, { context }) { + if (mode === 'module' || mode === 'script' || mode === 'console') { + context.evaluateMode = mode; + } + }, + engine262_setFeatures() { + throw new Error('Method should not be implemented here.'); + }, +}; +export const Profiler: ProfilerNamespace = { + enable() { }, +}; +export const Runtime: RuntimeNamespace = { + discardConsoleEntries() { }, + enable() {}, + compileScript(options, { context, sendEvent }) { + let parsed!: ScriptRecord | SourceTextModuleRecord | ObjectValue[]; + let realm = context.getRealm(options.executionContextId); + if (!realm && !options.persistScript) { + realm = context.getAnyRealm(); + } + if (!realm) { + return unsupportedError; + } + realm.realm.scope(() => { + if (context.evaluateMode === 'module') { + parsed = ParseModule(options.expression, realm.realm, { specifier: options.sourceURL, doNotTrackScriptId: !options.persistScript }); + } else { + parsed = ParseScript(options.expression, realm.realm, { specifier: options.sourceURL, doNotTrackScriptId: !options.persistScript, [kInternal]: { allowAllPrivateNames: true } }); + } + }); + if (!parsed) { + throw new Error('No parsed result'); + } + if (Array.isArray(parsed)) { + const e = context.createExceptionDetails(ThrowCompletion(parsed[0]), false); + // Note: it has to be this message to trigger devtools' line wrap. + e.exception!.description = 'SyntaxError: Unexpected end of input'; + return { exceptionDetails: e }; + } + if (options.persistScript) { + if (realm?.descriptor.id === undefined) { + throw new Error('No realm id found'); + } + const event = getParsedEvent(parsed, parsed.HostDefined.scriptId!, realm.descriptor.id); + sendEvent['Debugger.scriptParsed'](event); + return { scriptId: event.scriptId }; + } + return {}; + }, + callFunctionOn(options, { context }): Protocol.Runtime.CallFunctionOnResponse { + const realmDesc = context.getRealm(options.uniqueContextId || options.executionContextId) || context.getAnyRealm(); + if (!realmDesc) { + throw new Error('No realm found'); + } + const { Value: F } = realmDesc.realm.evaluateScript(`(${options.functionDeclaration})`, { doNotTrackScriptId: true }) as NormalCompletion; + const thisValue = options.objectId + ? context.getObject(options.objectId)! + : Value.undefined; + const args = options.arguments?.map((a) => { + // TODO: revisit + if ('value' in a) { + return Value(a.value); + } + if (a.objectId) { + return context.getObject(a.objectId)!; + } + if ('unserializableValue' in a) { + throw new RangeError(); + } + return Value.undefined; + }); + return realmDesc.realm.scope((): Protocol.Runtime.CallFunctionOnResponse => { + const completion = evalQ((Q, X): Protocol.Runtime.CallFunctionOnResponse => { + const r = Q(skipDebugger(Call(F, thisValue, args || []))); + if (options.returnByValue) { + const value = X(Call(realmDesc.realm.Intrinsics['%JSON.stringify%'], Value.undefined, [r])); + if (value instanceof JSStringValue) { + const valueRealized = JSON.parse(value.stringValue()); + return { result: { type: typeof value, value: valueRealized } }; + } + } + return context.createEvaluationResult(r); + }); + if (completion instanceof ThrowCompletion) { + return { result: { type: 'undefined' }, exceptionDetails: context.createExceptionDetails(completion, false) }; + } + return completion.Value; + }); + }, + evaluate(options, context) { + return evaluate({ + ...options, + evalMode: context.context.evaluateMode, + uniqueContextId: options.uniqueContextId!, + }, context); + }, + getExceptionDetails(req, { context }) { + const object = context.getObject(req.errorObjectId)!; + if (object instanceof ObjectValue) { + return { + exceptionDetails: context.createExceptionDetails(ThrowCompletion(object), false), + }; + } + return { + exceptionDetails: { + text: 'unsupported', lineNumber: 0, columnNumber: 0, exceptionId: 0, + }, + }; + }, + getHeapUsage() { + return { + usedSize: 0, totalSize: 0, backingStorageSize: 0, embedderHeapUsedSize: 0, + }; + }, + getIsolateId() { + return { id: 'isolate.0' }; + }, + getProperties(options, { context }) { + return context.getProperties(options); + }, + globalLexicalScopeNames({ executionContextId }, { context }) { + const global = context.getRealm(executionContextId)?.realm.GlobalObject; + if (!global) { + return { names: [] }; + } + const keys = skipDebugger(global.OwnPropertyKeys()); + if (keys instanceof ThrowCompletion) { + return { names: [] }; + } + return { names: ValueOfNormalCompletion(keys).map((k) => (k instanceof JSStringValue ? k.stringValue() : null!)).filter(Boolean) }; + }, + releaseObject(req, { context }) { + context.releaseObject(req.objectId); + }, + releaseObjectGroup({ objectGroup }, { context }) { + context.releaseObjectGroup(objectGroup); + }, + runIfWaitingForDebugger() { }, +}; +export const HeapProfiler: HeapProfilerNamespace = { + enable() { }, + collectGarbage() { }, +}; + +export const Target: TargetNamespace = { + setDiscoverTargets() { }, + // @ts-expect-error no doc + setRemoteLocations() { }, +}; + +const unsupportedError: Protocol.Runtime.EvaluateResponse = { + result: { type: 'undefined' }, + exceptionDetails: { + text: 'unsupported', lineNumber: 0, columnNumber: 0, exceptionId: 0, + }, +}; +function evaluate(options: { + uniqueContextId: string, + expression: string, + evalMode: InspectorContext['evaluateMode'], + throwOnSideEffect?: boolean, + awaitPromise?: boolean, + callFrameId?: string, +}, _context: DebuggerContext): Protocol.Runtime.EvaluateResponse | Promise { + const { context } = _context; + const isPreview = options.throwOnSideEffect; + if (options.awaitPromise) { + return unsupportedError; + } + const realm = context.getRealm(options.uniqueContextId); + if (!realm) { + return unsupportedError; + } + + const isCallOnFrame = typeof options.callFrameId === 'string'; + let callOnFramePoppedLevel = 0; + const oldExecutionStack = [...surroundingAgent.executionContextStack]; + if (isCallOnFrame) { + const frame = surroundingAgent.executionContextStack[options.callFrameId as `${number}`]; + if (!frame) { + // eslint-disable-next-line no-console + console.error('Execution context not found: ', options.callFrameId); + return unsupportedError; + } + for (const currentFrame of [...surroundingAgent.executionContextStack].reverse()) { + if (currentFrame === frame) { + break; + } + callOnFramePoppedLevel += 1; + surroundingAgent.executionContextStack.pop(currentFrame); + } + } + const promise = new Promise((resolve) => { + let toBeEvaluated; + if (isPreview || options.evalMode === 'console' || isCallOnFrame) { + toBeEvaluated = performDevtoolsEval(options.expression, realm.realm, false, !!(isPreview || isCallOnFrame)); + } else { + let parsed!: ScriptRecord | SourceTextModuleRecord | ObjectValue[]; + const realm = context.getRealm(options.uniqueContextId); + realm?.realm.scope(() => { + if (options.evalMode === 'module') { + parsed = ParseModule(options.expression, realm.realm); + } else { + parsed = ParseScript(options.expression, realm.realm); + } + }); + if (Array.isArray(parsed)) { + const e = context.createExceptionDetails(ThrowCompletion(parsed[0]), false); + resolve({ exceptionDetails: e, result: { type: 'undefined' } }); + return; + } + toBeEvaluated = parsed; + } + + const noDebuggerEvaluate = () => { + if (!('next' in toBeEvaluated)) { + throw new Assert.Error('Unexpected'); + } + resolve(context.createEvaluationResult(skipDebugger(toBeEvaluated))); + }; + if (isPreview) { + surroundingAgent.debugger_scopePreview(noDebuggerEvaluate); + return; + } + if (isCallOnFrame) { + noDebuggerEvaluate(); + return; + } + + const completion = realm.realm.evaluate(toBeEvaluated, (completion) => { + resolve(context.createEvaluationResult(completion)); + runJobQueue(); + }); + if (completion) { + return; + } + surroundingAgent.resumeEvaluate(); + }); + promise.then(() => { + if (callOnFramePoppedLevel) { + Assert(oldExecutionStack.length - callOnFramePoppedLevel === surroundingAgent.executionContextStack.length); + for (const [newIndex, newStack] of surroundingAgent.executionContextStack.entries()) { + Assert(newStack === oldExecutionStack[newIndex]); + } + surroundingAgent.executionContextStack.length = 0; + for (const stack of oldExecutionStack) { + surroundingAgent.executionContextStack.push(stack); + } + } + }, (err): Protocol.Runtime.EvaluateResponse => { + const expr = surroundingAgent.runningExecutionContext.callSite.lastNode?.sourceText; + const frame = InspectorContext.callSiteToCallFrame(captureStack().stack); + _context.sendEvent['Runtime.exceptionThrown']({ + timestamp: Date.now(), + exceptionDetails: { + stackTrace: frame.length ? { callFrames: frame } : undefined, + text: `engine262 error when evaluating the following node:\n\n ${expr}\n\n${err.constructor.name}: ${err.message}\n${err.stack.slice(err.stack.indexOf(err.message) + err.message.length + 1)}\n\nFrom now on, the engine262 VM state is broken, please press the reload button.`, + columnNumber: frame[0]?.columnNumber, + lineNumber: frame[0]?.lineNumber, + scriptId: frame[0]?.scriptId, + url: frame[0]?.url, + exceptionId: 0, + }, + }); + return { + result: { type: 'undefined' }, + }; + }); + return promise; +} diff --git a/lib-src/inspector/tsconfig.json b/lib-src/inspector/tsconfig.json new file mode 100644 index 0000000..90dc057 --- /dev/null +++ b/lib-src/inspector/tsconfig.json @@ -0,0 +1,17 @@ +{ + "references": [{ "path": "../../src/" }], + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "incremental": true, + "declarationDir": "../../lib/inspector", + "tsBuildInfoFile": "../../lib/inspector/.tsbuildinfo", + "erasableSyntaxOnly": true, + "rootDir": "./", + "outDir": "../../lib/inspector/", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "resolveJsonModule": true + }, + "include": ["./*.mts"] +} diff --git a/lib-src/inspector/types.mts b/lib-src/inspector/types.mts new file mode 100644 index 0000000..0205f6c --- /dev/null +++ b/lib-src/inspector/types.mts @@ -0,0 +1,225 @@ +import type { Protocol } from 'devtools-protocol'; +import type { InspectorContext } from './context.mts'; + +export interface DebuggerPreference { + previewDebug: boolean; +} + +export interface DebuggerContext { + sendEvent: DevtoolEvents; + onDebuggerAttached(): void; + preference: DebuggerPreference; + context: InspectorContext; +} + +export interface DebuggerNamespace { + engine262_setEvaluateMode(req: { mode: 'module' | 'script' | 'console' }, context: DebuggerContext): void; + engine262_setFeatures(req: { features: string[] }, context: DebuggerContext): void; +} +export interface DebuggerNamespace { + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-continueToLocation */ + continueToLocation?(req: Protocol.Debugger.ContinueToLocationRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-disable */ + disable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-enable */ + enable?(req: Protocol.Debugger.EnableRequest, context: DebuggerContext): Protocol.Debugger.EnableResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-evaluateOnCallFrame */ + evaluateOnCallFrame?(req: Protocol.Debugger.EvaluateOnCallFrameRequest, context: DebuggerContext): Protocol.Debugger.EvaluateOnCallFrameResponse | Promise; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-getPossibleBreakpoints */ + getPossibleBreakpoints?(req: Protocol.Debugger.GetPossibleBreakpointsRequest, context: DebuggerContext): Protocol.Debugger.GetPossibleBreakpointsResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-getScriptSource */ + getScriptSource?(req: Protocol.Debugger.GetScriptSourceRequest, context: DebuggerContext): Protocol.Debugger.GetScriptSourceResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-pause */ + pause?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-removeBreakpoint */ + removeBreakpoint?(req: Protocol.Debugger.RemoveBreakpointRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-restartFrame */ + restartFrame?(req: Protocol.Debugger.RestartFrameRequest, context: DebuggerContext): Protocol.Debugger.RestartFrameResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-resume */ + resume?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-searchInContent */ + searchInContent?(req: Protocol.Debugger.SearchInContentRequest, context: DebuggerContext): Protocol.Debugger.SearchInContentResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setAsyncCallStackDepth */ + setAsyncCallStackDepth?(req: Protocol.Debugger.SetAsyncCallStackDepthRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpoint */ + setBreakpoint?(req: Protocol.Debugger.SetBreakpointRequest, context: DebuggerContext): Protocol.Debugger.SetBreakpointResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpointByUrl */ + setBreakpointByUrl?(req: Protocol.Debugger.SetBreakpointByUrlRequest, context: DebuggerContext): Protocol.Debugger.SetBreakpointByUrlResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpointsActive */ + setBreakpointsActive?(req: Protocol.Debugger.SetBreakpointsActiveRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setInstrumentationBreakpoint */ + setInstrumentationBreakpoint?(req: Protocol.Debugger.SetInstrumentationBreakpointRequest, context: DebuggerContext): Protocol.Debugger.SetInstrumentationBreakpointResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setPauseOnExceptions */ + setPauseOnExceptions?(req: Protocol.Debugger.SetPauseOnExceptionsRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setScriptSource */ + setScriptSource?(req: Protocol.Debugger.SetScriptSourceRequest, context: DebuggerContext): Protocol.Debugger.SetScriptSourceResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setSkipAllPauses */ + setSkipAllPauses?(req: Protocol.Debugger.SetSkipAllPausesRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setVariableValue */ + setVariableValue?(req: Protocol.Debugger.SetVariableValueRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-stepInto */ + stepInto?(req: Protocol.Debugger.StepIntoRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-stepOut */ + stepOut?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-stepOver */ + stepOver?(req: Protocol.Debugger.StepOverRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-getStackTrace */ + getStackTrace?(req: Protocol.Debugger.GetStackTraceRequest, context: DebuggerContext): Protocol.Debugger.GetStackTraceResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBlackboxedRanges */ + setBlackboxedRanges?(req: Protocol.Debugger.SetBlackboxedRangesRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBlackboxExecutionContexts */ + setBlackboxExecutionContexts?(req: Protocol.Debugger.SetBlackboxExecutionContextsRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBlackboxPatterns */ + setBlackboxPatterns?(req: Protocol.Debugger.SetBlackboxPatternsRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpointOnFunctionCall */ + setBreakpointOnFunctionCall?(req: Protocol.Debugger.SetBreakpointOnFunctionCallRequest, context: DebuggerContext): Protocol.Debugger.SetBreakpointOnFunctionCallResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setReturnValue */ + setReturnValue?(req: Protocol.Debugger.SetReturnValueRequest, context: DebuggerContext): void; +} + +export interface ProfilerNamespace { + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-disable */ + disable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-enable */ + enable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-getBestEffortCoverage */ + getBestEffortCoverage?(req: void, context: DebuggerContext): Protocol.Profiler.GetBestEffortCoverageResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-setSamplingInterval */ + setSamplingInterval?(req: Protocol.Profiler.SetSamplingIntervalRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-start */ + start?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-startPreciseCoverage */ + startPreciseCoverage?(req: Protocol.Profiler.StartPreciseCoverageRequest, context: DebuggerContext): Protocol.Profiler.StartPreciseCoverageResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-stop */ + stop?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-stopPreciseCoverage */ + stopPreciseCoverage?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-takePreciseCoverage */ + takePreciseCoverage?(req: void, context: DebuggerContext): Protocol.Profiler.TakePreciseCoverageResponse; +} + +export interface RuntimeNamespace { + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-addBinding */ + addBinding?(req: Protocol.Runtime.AddBindingRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-awaitPromise */ + awaitPromise?(req: Protocol.Runtime.AwaitPromiseRequest, context: DebuggerContext): Protocol.Runtime.AwaitPromiseResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-callFunctionOn */ + callFunctionOn?(req: Protocol.Runtime.CallFunctionOnRequest, context: DebuggerContext): Protocol.Runtime.CallFunctionOnResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-compileScript */ + compileScript?(req: Protocol.Runtime.CompileScriptRequest, context: DebuggerContext): Protocol.Runtime.CompileScriptResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-disable */ + disable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-discardConsoleEntries */ + discardConsoleEntries?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-enable */ + enable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-evaluate */ + evaluate?(req: Protocol.Runtime.EvaluateRequest, context: DebuggerContext): Protocol.Runtime.EvaluateResponse | Promise; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getProperties */ + getProperties?(req: Protocol.Runtime.GetPropertiesRequest, context: DebuggerContext): Protocol.Runtime.GetPropertiesResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-globalLexicalScopeNames */ + globalLexicalScopeNames?(req: Protocol.Runtime.GlobalLexicalScopeNamesRequest, context: DebuggerContext): Protocol.Runtime.GlobalLexicalScopeNamesResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-queryObjects */ + queryObjects?(req: Protocol.Runtime.QueryObjectsRequest, context: DebuggerContext): Protocol.Runtime.QueryObjectsResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-releaseObject */ + releaseObject?(req: Protocol.Runtime.ReleaseObjectRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-releaseObjectGroup */ + releaseObjectGroup?(req: Protocol.Runtime.ReleaseObjectGroupRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-removeBinding */ + removeBinding?(req: Protocol.Runtime.RemoveBindingRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-runIfWaitingForDebugger */ + runIfWaitingForDebugger?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-runScript */ + runScript?(req: Protocol.Runtime.RunScriptRequest, context: DebuggerContext): Protocol.Runtime.RunScriptResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-setAsyncCallStackDepth */ + setAsyncCallStackDepth?(req: Protocol.Runtime.SetAsyncCallStackDepthRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getExceptionDetails */ + getExceptionDetails?(req: Protocol.Runtime.GetExceptionDetailsRequest, context: DebuggerContext): Protocol.Runtime.GetExceptionDetailsResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getHeapUsage */ + getHeapUsage?(req: void, context: DebuggerContext): Protocol.Runtime.GetHeapUsageResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getIsolateId */ + getIsolateId?(req: void, context: DebuggerContext): Protocol.Runtime.GetIsolateIdResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-setCustomObjectFormatterEnabled */ + setCustomObjectFormatterEnabled?(req: Protocol.Runtime.SetCustomObjectFormatterEnabledRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-setMaxCallStackSizeToCapture */ + setMaxCallStackSizeToCapture?(req: Protocol.Runtime.SetMaxCallStackSizeToCaptureRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-terminateExecution */ + terminateExecution?(req: void, context: DebuggerContext): void; +} + +export interface HeapProfilerNamespace { + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-addInspectedHeapObject */ + addInspectedHeapObject?(req: Protocol.HeapProfiler.AddInspectedHeapObjectRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-collectGarbage */ + collectGarbage?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-disable */ + disable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-enable */ + enable?(req: void, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-getHeapObjectId */ + getHeapObjectId?(req: Protocol.HeapProfiler.GetHeapObjectIdRequest, context: DebuggerContext): Protocol.HeapProfiler.GetHeapObjectIdResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-getObjectByHeapObjectId */ + getObjectByHeapObjectId?(req: Protocol.HeapProfiler.GetObjectByHeapObjectIdRequest, context: DebuggerContext): Protocol.HeapProfiler.GetObjectByHeapObjectIdResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-getSamplingProfile */ + getSamplingProfile?(req: void, context: DebuggerContext): Protocol.HeapProfiler.GetSamplingProfileResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-startSampling */ + startSampling?(req: Protocol.HeapProfiler.StartSamplingRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-startTrackingHeapObjects */ + startTrackingHeapObjects?(req: Protocol.HeapProfiler.StartTrackingHeapObjectsRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-stopSampling */ + stopSampling?(req: void, context: DebuggerContext): Protocol.HeapProfiler.StopSamplingResponse; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-stopTrackingHeapObjects */ + stopTrackingHeapObjects?(req: Protocol.HeapProfiler.StopTrackingHeapObjectsRequest, context: DebuggerContext): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-takeHeapSnapshot */ + takeHeapSnapshot?(req: Protocol.HeapProfiler.TakeHeapSnapshotRequest, context: DebuggerContext): void; +} + +// https://chromedevtools.github.io/devtools-protocol/1-3/Target/ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface TargetNamespace { + /** https://chromedevtools.github.io/devtools-protocol/1-3/Target/#method-setDiscoverTargets */ + setDiscoverTargets?(req: Protocol.Target.SetDiscoverTargetsRequest, context: DebuggerContext): void; +} + +export interface DevtoolEvents { + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-paused */ + 'Debugger.paused'(event: Protocol.Debugger.PausedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-resumed */ + 'Debugger.resumed'(event: void): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-scriptFailedToParse */ + 'Debugger.scriptFailedToParse'(event: Protocol.Debugger.ScriptFailedToParseEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-scriptParsed */ + 'Debugger.scriptParsed'(event: Protocol.Debugger.ScriptParsedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-addHeapSnapshotChunk */ + 'HeapProfiler.addHeapSnapshotChunk'(event: Protocol.HeapProfiler.AddHeapSnapshotChunkEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-heapStatsUpdate */ + 'HeapProfiler.heapStatsUpdate'(event: Protocol.HeapProfiler.HeapStatsUpdateEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-lastSeenObjectId */ + 'HeapProfiler.lastSeenObjectId'(event: Protocol.HeapProfiler.LastSeenObjectIdEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-reportHeapSnapshotProgress */ + 'HeapProfiler.reportHeapSnapshotProgress'(event: Protocol.HeapProfiler.ReportHeapSnapshotProgressEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-resetProfiles */ + 'HeapProfiler.resetProfiles'(event: void): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#event-consoleProfileFinished */ + 'Profiler.consoleProfileFinished'(event: Protocol.Profiler.ConsoleProfileFinishedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#event-consoleProfileStarted */ + 'Profiler.consoleProfileStarted'(event: Protocol.Profiler.ConsoleProfileStartedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#event-preciseCoverageDeltaUpdate */ + 'Profiler.preciseCoverageDeltaUpdate'(event: Protocol.Profiler.PreciseCoverageDeltaUpdateEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-consoleAPICalled */ + 'Runtime.consoleAPICalled'(event: Protocol.Runtime.ConsoleAPICalledEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-exceptionRevoked */ + 'Runtime.exceptionRevoked'(event: Protocol.Runtime.ExceptionRevokedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-exceptionThrown */ + 'Runtime.exceptionThrown'(event: Protocol.Runtime.ExceptionThrownEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-executionContextCreated */ + 'Runtime.executionContextCreated'(event: Protocol.Runtime.ExecutionContextCreatedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-executionContextDestroyed */ + 'Runtime.executionContextDestroyed'(event: Protocol.Runtime.ExecutionContextDestroyedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-executionContextsCleared */ + 'Runtime.executionContextsCleared'(event: void): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-inspectRequested */ + 'Runtime.inspectRequested'(event: Protocol.Runtime.InspectRequestedEvent): void; + /** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-bindingCalled */ + 'Runtime.bindingCalled'(event: Protocol.Runtime.BindingCalledEvent): void; +} diff --git a/lib-src/inspector/utils.mts b/lib-src/inspector/utils.mts new file mode 100644 index 0000000..623824b --- /dev/null +++ b/lib-src/inspector/utils.mts @@ -0,0 +1,81 @@ +import type Protocol from 'devtools-protocol'; +import type { Inspector } from './index.mts'; +import { + CreateBuiltinFunction, CreateDataProperty, DefinePropertyOrThrow, Descriptor, OrdinaryObjectCreate, surroundingAgent, ThrowCompletion, skipDebugger, Value, type Arguments, type ManagedRealm, + type PlainEvaluator, + type PlainCompletion, +} from '#self'; + +const consoleMethods = [ + 'log', + 'debug', + 'info', + 'error', + 'warning', + 'dir', + 'dirxml', + 'table', + 'trace', + 'clear', + 'startGroup', + 'startGroupCollapsed', + 'endGroup', + 'assert', + 'profile', + 'profileEnd', + 'count', + 'timeEnd', +] as const; +type ConsoleMethod = typeof consoleMethods[number]; +export function createConsole( + realm: ManagedRealm, + defaultBehaviour: Partial void | PlainCompletion | PlainEvaluator>> & { default?: (method: ConsoleMethod, args: Arguments) => void | PlainCompletion | PlainEvaluator }, +) { + realm.scope(() => { + const console = OrdinaryObjectCreate(realm.Intrinsics['%Object.prototype%']); + skipDebugger(DefinePropertyOrThrow( + realm.GlobalObject, + Value('console'), + Descriptor({ + Configurable: Value.true, + Enumerable: Value.false, + Writable: Value.true, + Value: console, + }), + )); + consoleMethods.forEach((method) => { + const f = CreateBuiltinFunction( + function* Console(args): PlainEvaluator { + if (surroundingAgent.debugger_isPreviewing) { + return Value.undefined; + } + + let completion; + if (defaultBehaviour[method]) { + completion = defaultBehaviour[method](args); + } else if (defaultBehaviour.default) { + completion = defaultBehaviour.default(method, args); + } + + if (completion) { + if (typeof completion === 'object' && 'next' in completion) { + completion = yield* completion; + } + // Do not use Q(host) here. A host may return something invalid like ReturnCompletion. + if (completion instanceof ThrowCompletion) { + return completion; + } + } + if (realm.HostDefined.attachingInspector) { + (realm.HostDefined.attachingInspector as Inspector).console(realm, method as Protocol.Protocol.Runtime.ConsoleAPICalledEventType, args); + } + return Value.undefined; + }, + 1, + Value(method), + [], + ); + skipDebugger(CreateDataProperty(console, Value(method), f)); + }); + }); +} diff --git a/lib-src/node/bin.mts b/lib-src/node/bin.mts new file mode 100644 index 0000000..f7e2523 --- /dev/null +++ b/lib-src/node/bin.mts @@ -0,0 +1,239 @@ +#!/usr/bin/env node + +import { start } from 'node:repl'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { format as _format, inspect as _inspect, parseArgs } from 'node:util'; +// let's try if the following message on old node causes test failure on test262.fyi +// "ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time" +// import packageJson from '../../package.json' with { type: 'json' }; +import { createRequire } from 'node:module'; +import { createConsole } from '../inspector/utils.mts'; +import type { NodeWebsocketInspector } from './inspector.mts'; +import { loadImportedModule } from './module.mts'; +import { + setSurroundingAgent, FEATURES, inspect, Value, Completion, AbruptCompletion, + type Arguments, + evalQ, + Agent, + ManagedRealm, + skipDebugger, + type ValueCompletion, + createTest262Intrinsics, + surroundingAgent, + ThrowCompletion, + ValueOfNormalCompletion, + ScriptEvaluation, + type PlainEvaluator, +} from '#self'; + +const packageJson = createRequire(import.meta.url)('../../package.json'); +const help = ` +engine262 v${packageJson.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. + -e, --eval Evaluate the given string. + --features=... A comma separated list of features. + --features=all Enable all features. + --list-features List available features. + --no-test262 Do not expose $ and $262 for test262. + --no-inspector Do not attach an inspector. + --no-preview Do not enable preview in the inspector. +`; + +const argv = parseArgs({ + args: process.argv.slice(2), + allowPositionals: true, + allowNegative: true, + strict: true, + options: { + 'help': { type: 'boolean', short: 'h' }, + 'eval': { type: 'string', short: 'e' }, + 'module': { type: 'boolean', short: 'm' }, + 'features': { type: 'string' }, + 'list-features': { type: 'boolean' }, + 'inspector': { type: 'boolean' }, + 'test262': { type: 'boolean', default: true }, + // hidden options + 'preview-debug': { type: 'boolean' }, + }, +}); + +if (argv.values.help) { + process.stdout.write(help); + process.exit(0); +} else if (argv.values['list-features']) { + 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 = (f: string, n: string, u: string) => { + process.stdout.write(`${f.padEnd(flagLength, ' ')} ${n.padEnd(nameLength, ' ')} ${u}\n`); + }; + log('flag', 'name', 'url'); + log('----', '----', '---'); + FEATURES.forEach((f) => { + log(f.flag, f.name, f.url); + }); + process.exit(0); +} + +let features: string[]; +if (argv.values.features === 'all') { + features = FEATURES.map((f) => f.flag); +} else if (argv.values.features) { + features = argv.values.features.split(','); +} else { + features = []; +} + +const agent = new Agent({ + features, + supportedImportAttributes: ['type'], + loadImportedModule, +}); +setSurroundingAgent(agent); + +const realm = new ManagedRealm({ resolverCache: new Map(), name: 'repl', specifier: process.cwd() }); +// Define console.log +{ + const format = (function* format(args: Arguments): PlainEvaluator { + const str = []; + for (const arg of args.values()) { + // TODO: inspect should return a PlainEvaluator so debugger can hook in. + str.push(inspect(arg)); + } + return str.join(' '); + }); + createConsole(realm, { + * log(args) { + process.stdout.write(`${yield* format(args)}\n`); + }, + * error(args) { + process.stderr.write(`${yield* format(args)}\n`); + }, + * debug(args) { + process.stderr.write(`${yield* format(args)}\n`); + }, + }); +} +if (argv.values.test262) { + createTest262Intrinsics(realm, argv.values.test262); +} + +let inspector: NodeWebsocketInspector | undefined; +if (argv.values.inspector !== false) { + let has_ws = false; + try { + await import('ws'); + has_ws = true; + } catch { + if (argv.values.inspector === true) { + process.stderr.write('--inspector requires the "ws" package to be installed.\n'); + process.exit(1); + } + } + if (has_ws) { + const { NodeWebsocketInspector } = await import('./inspector.mts'); + inspector = await NodeWebsocketInspector.new(); + inspector.attachAgent(surroundingAgent, [realm]); + inspector.preference.previewDebug = argv.values['preview-debug'] || false; + } +} + +function oneShotEval(source: string, filename: string) { + realm.scope(() => { + const completion = evalQ((Q) => { + if (argv.values.module || filename.endsWith('.mjs')) { + const module = Q(realm.compileModule(source, { specifier: filename })); + realm.HostDefined.resolverCache?.set(filename, module); + const load = Q(module.LoadRequestedModules()); + if (load.PromiseState === 'rejected') { + Q(ThrowCompletion(load.PromiseResult!)); + } else if (load.PromiseState === 'pending') { + throw new Error('Internal error: .LoadRequestedModules() returned a pending promise'); + } + Q(module.Link()); + const evaluate = Q(skipDebugger(module.Evaluate())); + if (evaluate.PromiseState === 'rejected') { + Q(ThrowCompletion(evaluate.PromiseResult!)); + } + } else { + Q(realm.evaluateScript(source, { specifier: filename })); + } + }); + if (completion instanceof AbruptCompletion) { + const inspected = inspect(completion); + process.stderr.write(`${inspected}\n`); + process.exit(1); + } + }); + + inspector?.stop(); +} + +if (argv.positionals[0]) { + const source = readFileSync(argv.positionals[0], 'utf8'); + oneShotEval(source, resolve(argv.positionals[0])); +} 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 if (argv.values.eval) { + oneShotEval(argv.values.eval, process.cwd()); +} else { + process.stdout.write(`${packageJson.name} v${String(packageJson.version).replace('0.0.1-', '')} +Type ".help" for more information. Please report bugs to ${packageJson.bugs.url} +`); + const server = start({ + prompt: '> ', + eval: (cmd, _context, _filename, callback) => { + try { + const script = realm.compileScript(cmd, {}); + if (script instanceof ThrowCompletion) { + callback(null, script); + return; + } + let c; + surroundingAgent.evaluate(ScriptEvaluation(ValueOfNormalCompletion(script)), (completion) => { + c = completion; + callback(null, completion); + }); + if (!c) { + surroundingAgent.resumeEvaluate(); + } + } catch (e) { + callback(e as Error, null); + } + }, + preview: false, + writer: (o) => realm.scope(() => { + if (o instanceof Value || o instanceof Completion) { + return inspect(o as Value | ValueCompletion); + } + return _inspect(o); + }), + }); + + server.on('exit', () => inspector?.stop()); +} diff --git a/lib-src/node/example.mts b/lib-src/node/example.mts new file mode 100644 index 0000000..f44c106 --- /dev/null +++ b/lib-src/node/example.mts @@ -0,0 +1,58 @@ +/* eslint-disable no-console */ +import { + Agent, inspect, ManagedRealm, NormalCompletion, setSurroundingAgent, ThrowCompletion, type Arguments, type PlainEvaluator, +} from '#self'; +import { createConsole } from '#self/inspector'; + +// Agent is the running environment. +const agent = new Agent({ +}); +// Only one agent can be active at a time. +setSurroundingAgent(agent); + +// A Realm is a separate global environment. +// In Web browsers, each iframe has its own Realm and they may interact with each other. +const realm = new ManagedRealm({ resolverCache: new Map(), name: 'My Realm', specifier: process.cwd() }); + +// Define console.log +{ + const format = (function* format(args: Arguments): PlainEvaluator { + const str = []; + for (const arg of args.values()) { + str.push(inspect(arg)); + } + return str.join(' '); + }); + createConsole(realm, { + * log(args) { + process.stdout.write(`${yield* format(args)}\n`); + }, + * error(args) { + process.stderr.write(`${yield* format(args)}\n`); + }, + * debug(args) { + process.stderr.write(`${yield* format(args)}\n`); + }, + * default(method, args) { + process.stdout.write(`[console.${method}] ${yield* format(args)}\n`); + }, + }); +} + +// Do not forget to use realm.scope when running code. +realm.scope(() => { + // Run ECMAScript code in the Realm. + realm.evaluateScript(` + console.log('Hello from engine262!'); + console.log('2 + 2 =', 2 + 2); + `, { specifier: 'example.mts' }); + + const result = realm.evaluateScript(` + throw new Error('This is an example error'); + `, { specifier: 'example.mts' }); + if (result instanceof NormalCompletion) { + console.log('No Error'); + } else if (result instanceof ThrowCompletion) { + console.error('Caught error from evaluated script:', inspect(result.Value)); + } +}); diff --git a/lib-src/node/inspector.mts b/lib-src/node/inspector.mts new file mode 100644 index 0000000..1890ee5 --- /dev/null +++ b/lib-src/node/inspector.mts @@ -0,0 +1,124 @@ +import http from 'node:http'; +import https from 'node:https'; +import { WebSocketServer } from 'ws'; +import packageJson from '../../package.json' with { type: 'json' }; +// Note: typescript will not copy json files, so it will not appear in the lib directory +// eslint-disable-next-line import/no-useless-path-segments +import protocol from '../../lib-src/inspector/js_protocol.json' with { type: 'json' }; +import { Inspector } from '../inspector/index.mts'; + +const ANSI = { + reset: '\u001b[0m', + red: '\u001b[31m', + green: '\u001b[32m', + yellow: '\u001b[33m', + blue: '\u001b[34m', +}; + +export class NodeWebsocketInspector extends Inspector { + _server: http.Server | https.Server; + + _ws: WebSocketServer; + + isDebug = false; + + protected override send(data: object): void { + const s = JSON.stringify(data); + this._ws.clients.forEach((ws) => { + ws.send(s); + }); + } + + protected constructor(server: http.Server | https.Server, isDebug: boolean) { + super(); + this._server = server; + const ws = new WebSocketServer({ server }); + this._ws = ws; + ws.on('connection', (ws) => { + const send = (obj: unknown) => { + const s = JSON.stringify(obj); + ws.send(s); + }; + + const sendEvent = Object.create(new Proxy({}, { + get: (_, key: string) => { + const f = (params: Record) => { + send({ method: key, params }); + }; + Object.defineProperty(sendEvent, key, { value: key }); + return f; + }, + })); + + ws.on('message', (data: string) => { + const { id, method, params } = JSON.parse(data); + if (isDebug) { + process.stdout.write(`${ANSI.green}${method}${ANSI.reset}: ${JSON.stringify(params)}\n`); + } + this.onMessage(id, method, params); + }); + }); + } + + static inspectorHTTPServer(req: http.IncomingMessage, res: http.ServerResponse) { + if (req.method !== 'GET') { + res.writeHead(405); + res.end(); + return; + } + + const json = (obj: unknown) => { + 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; + } + } + + static new(port = 9229, host = '127.0.0.1', isDebug = !!process.env.DEBUG) { + const server = http.createServer(NodeWebsocketInspector.inspectorHTTPServer); + const inspector = new NodeWebsocketInspector(server, isDebug); + return new Promise((resolve) => { + server.listen(port, host, () => { + resolve(inspector); + }); + }); + } + + stop() { + this._server.close(); + this._ws.close(); + } +} diff --git a/lib-src/node/module.mts b/lib-src/node/module.mts new file mode 100644 index 0000000..d4c5b91 --- /dev/null +++ b/lib-src/node/module.mts @@ -0,0 +1,74 @@ +import { readFile, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { + evalQ, ManagedRealm, Realm, Throw, ThrowCompletion, type AgentHostDefined, +} from '#self'; + +export function createLoadImportedModule(getCache = (realm: ManagedRealm) => realm.HostDefined.resolverCache) { + const validateType = (attributes: Map, finish: (completion: ThrowCompletion) => void) => { + const type = attributes.get('type'); + if (type && type !== 'json') { + finish(Throw('TypeError', 'UnsupportedModuleType', type)); + return false; + } + return true; + }; + + const parseModule = (realm: ManagedRealm, resolved: string, attributes: Map, source: string) => (attributes.get('type') === 'json' || resolved.endsWith('.json') + ? realm.createJSONModule(resolved, source) + : realm.compileModule(source, { specifier: resolved })); + + const loadImportedModuleSyncOrAsync = ( + readFile: (path: string, callback: (err: NodeJS.ErrnoException | null, data: string) => void) => void, + ...[referrer, specifier, attributes, _hostDefined, finish]: Parameters> + ) => { + const realm = (referrer instanceof Realm ? referrer : referrer.Realm) as ManagedRealm; + const cache = getCache(realm); + + if (!referrer.HostDefined.specifier) { + finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier)); + return; + } + + if (!validateType(attributes, finish)) { + return; + } + + evalQ(async (Q) => { + const base = path.dirname(referrer.HostDefined.specifier!); + const resolved = path.resolve(base, specifier); + if (cache?.has(resolved)) { + finish(cache.get(resolved)!); + return; + } + try { + readFile(resolved, (err, data) => { + if (err) { + finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier)); + return; + } + const m = Q(parseModule(realm, resolved, attributes, data)); + cache?.set(resolved, m); + finish(m); + }); + } catch (error) { + finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier)); + } + }); + }; + + const loadImportedModule: NonNullable = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => { + readFile(path, 'utf8', callback); + }); + const loadImportedModuleSync: NonNullable = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => { + try { + const data = readFileSync(path, 'utf8'); + callback(null, data); + } catch (error) { + callback(error as NodeJS.ErrnoException, ''); + } + }); + return { loadImportedModule, loadImportedModuleSync }; +} + +export const { loadImportedModule, loadImportedModuleSync } = createLoadImportedModule(); diff --git a/lib-src/node/tsconfig.json b/lib-src/node/tsconfig.json new file mode 100644 index 0000000..51aa2a9 --- /dev/null +++ b/lib-src/node/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.base.json", + "references": [{ "path": "../inspector" }, { "path": "../../src" }], + "compilerOptions": { + "incremental": true, + "declarationDir": "../../lib/node", + "tsBuildInfoFile": "../../lib/node/.tsbuildinfo", + "erasableSyntaxOnly": true, + "rewriteRelativeImportExtensions": true, + "rootDir": "./", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "outDir": "../../lib/node/" + }, + "include": [ + "./example.mts", + "./bin.mts", + "./inspector.mts", + "./module.mts" + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2fa6a42 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9250 @@ +{ + "name": "@magic-works/engine262", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@magic-works/engine262", + "version": "0.0.1", + "license": "MIT", + "bin": { + "engine262": "lib/node/bin.mjs" + }, + "devDependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/core": "^7.28.5", + "@babel/plugin-proposal-decorators": "^7.28.0", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/preset-env": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@engine262/eslint-plugin": "file:test/eslint-plugin-engine262", + "@pppp606/ink-chart": "^0.2.4", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@stylistic/eslint-plugin-js": "^3.1.0", + "@types/babel__code-frame": "^7.0.6", + "@types/babel__core": "^7.20.5", + "@types/babel__traverse": "^7.28.0", + "@types/eslint": "^8.56.12", + "@types/estree": "^1.0.8", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.13.4", + "@types/react": "^19.2.7", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.51.0", + "@typescript-eslint/parser": "^8.51.0", + "@unicode/unicode-16.0.0": "^1.6.16", + "@vitest/coverage-v8": "^4.0.16", + "c8": "^10.1.3", + "cli-highlight": "^2.1.11", + "cross-env": "^10.1.0", + "devtools-protocol": "^0.0.1561482", + "eslint": "^8.57.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.32.0", + "http-server": "^14.1.1", + "ink": "^6.6.0", + "ink-task-list": "^2.0.0", + "js-yaml": "^4.1.0", + "npm-run-all": "^4.1.5", + "react": "^19.2.3", + "rollup": "^4.54.0", + "tinyglobby": "^0.2.15", + "typescript": "5.8.2", + "vitest": "^4.0.16", + "ws": "^8.18.3" + }, + "peerDependencies": { + "ws": "^8.18.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + } + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.2.tgz", + "integrity": "sha512-mkOh+Wwawzuf5wa30bvc4nA+Qb6DIrGWgBhRR/Pw4T9nsgYait8izvXkNyU78D6Wcu3Z+KUdwCmLCxlWjEotYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.5", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.4", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@engine262/eslint-plugin": { + "resolved": "test/eslint-plugin-engine262", + "link": true + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pppp606/ink-chart": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@pppp606/ink-chart/-/ink-chart-0.2.4.tgz", + "integrity": "sha512-iqzTFePJKgzypEMdziIIo/uRDtQGBnunWxsvmaHTIBBxQIK0sRDoOszJzDc0UNGRxM+bOSPtuC844sqH/lJorg==", + "dev": true, + "license": "MIT", + "bin": { + "ink-chart-demo": "bin/demo.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/pppp606" + }, + "peerDependencies": { + "ink": ">=6", + "react": ">=19" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", + "integrity": "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-3.1.0.tgz", + "integrity": "sha512-lQktsOiCr8S6StG29C5fzXYxLOD6ID1rp4j6TRS+E/qY1xd59Fm7dy5qm9UauJIEoSTlYx6yGsCHYh5UkgXPyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin-js/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@types/babel__code-frame": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz", + "integrity": "sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.51.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@unicode/unicode-16.0.0": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/@unicode/unicode-16.0.0/-/unicode-16.0.0-1.6.16.tgz", + "integrity": "sha512-R2Vxi0XEsCMD9WOQT85O2npa7g+i4RsJ8Xtn+/KODLqa5wH5zCTn5an6JILJhMSfFGD3t3amES2XnvUHnKjMZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", + "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.16", + "ast-v8-to-istanbul": "^0.3.8", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.16", + "vitest": "4.0.16" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", + "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", + "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", + "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.16", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", + "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", + "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", + "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1561482", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1561482.tgz", + "integrity": "sha512-nSXIMgdQxupOkVN94VPoE01UWJVXPOJ/IkEDQOlDbx3tmGzlKgVd3b54AT1cy8XIb4TQPcYac1nxYntEhiFKAQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.181", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.181.tgz", + "integrity": "sha512-+ISMj8OIQ+0qEeDj14Rt8WwcTOiqHyAB+5bnK1K7xNNLjBJ4hRCQfUkw8RWtcLbfBzDwc15ZnKH0c7SNOfwiyA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-toolkit": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", + "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "dev": true, + "license": "ISC" + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/ink": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.6.0.tgz", + "integrity": "sha512-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.1", + "ansi-escapes": "^7.2.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.0", + "type-fest": "^4.27.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": "^6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-task-list": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ink-task-list/-/ink-task-list-2.0.0.tgz", + "integrity": "sha512-+WadfalLGUrYT/5D4cHeShN73t5urEsI5VOwFYw3qNWdOL+GQVTLbTS0tCZkzIcBcL+dfZcQAtuoW56ZBZV+Bg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/ink-task-list?sponsor=1" + }, + "peerDependencies": { + "ink": ">=3.0.0", + "react": ">=16.8.0" + } + }, + "node_modules/ink/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ink/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ink/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ink/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ink/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ink/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/portfinder": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.35.tgz", + "integrity": "sha512-73JaFg4NwYNAufDtS5FsFu/PdM49ahJrO1i44aCRsDWju1z5wuGDaqyFUQWR6aJoK2JPDWlaYYAGFNIGTSUHSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", + "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", + "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.16", + "@vitest/mocker": "4.0.16", + "@vitest/pretty-format": "4.0.16", + "@vitest/runner": "4.0.16", + "@vitest/snapshot": "4.0.16", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.16", + "@vitest/browser-preview": "4.0.16", + "@vitest/browser-webdriverio": "4.0.16", + "@vitest/ui": "4.0.16", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" + }, + "test/eslint-plugin-engine262": { + "name": "@engine262/eslint-plugin", + "version": "0.0.0", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fa5ac63 --- /dev/null +++ b/package.json @@ -0,0 +1,153 @@ +{ + "name": "@magic-works/engine262", + "version": "0.0.1", + "packageManager": "npm@9.8.0", + "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": "lib/engine262.js", + "types": "./declaration/index.d.mts", + "imports": { + "#self": { + "rollup": "./src/index.mts", + "types": "./declaration/index.d.mts", + "default": "./lib/engine262.mjs" + }, + "#self/inspector": { + "types": "./lib/inspector/index.d.mts", + "default": "./lib/inspector.mjs" + } + }, + "exports": { + ".": { + "require": { + "types": "./declaration/index.d.mts", + "default": "./lib/engine262.js" + }, + "import": { + "types": "./declaration/index.d.mts", + "default": "./lib/engine262.mjs" + } + }, + "./inspector": { + "require": { + "types": "./declaration-inspector/index.d.mts", + "default": "./lib/inspector.js" + }, + "import": { + "types": "./declaration-inspector/index.d.mts", + "default": "./lib/inspector.mjs" + } + }, + "./lib/": "./lib/" + }, + "scripts": { + "start": "node --enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types ./lib-src/node/bin.mts", + "inspector": "node ./website/server.mjs -c-1", + "lint": "cross-env NODE_OPTIONS=\"--enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types\" eslint test/ src/ bin/ lib-src/ scripts/ --cache --ext=js,mjs,mts", + "lint:fix": "npm run lint -- --fix", + "gen-err": "node scripts/generate_error_message_hint.mts", + "watch": "run-p \"watch:*\"", + "build": "run-s gen-err \"build:*\"", + "build:regex_data": "node --enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types scripts/gen_regex_sets.mts", + "build:dts": "tsc -b .", + "watch:dts": "tsc -b . -w", + "build:engine": "cross-env NODE_OPTIONS=\"--enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types\" rollup -c ./scripts/rollup.config.mts", + "watch:engine": "npm run build:engine -- --watch", + "test:all": "run-s -c test:inspector test:owned test:json test:test262", + "test": "run-s -c test:owned test:json test:test262", + "test:test262": "node --enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types test/test262/test262.mts", + "test:owned": "vitest test/engine262 --watch=false", + "test:owned:watch": "vitest test/engine262", + "test:owned:coverage": "vitest test/engine262 --coverage", + "test:json": "node --enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types test/json/json.mts", + "test:inspector": "vitest test/inspector --watch=false", + "test:inspector:watch": "vitest test/inspector", + "test:inspector:coverage": "vitest test/inspector --coverage", + "coverage": "c8 --reporter=lcov npm run test", + "coverage:all": "c8 --reporter=lcov npm run test:all", + "prepublishOnly": "node --enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types scripts/tag_version_with_git_hash.mts", + "postpublish": "git reset --hard HEAD" + }, + "bin": { + "engine262": "lib/node/bin.mjs" + }, + "files": [ + "bin", + "declaration", + "declaration-inspector", + "!declaration/.tsbuildinfo", + "!lib/node/.tsbuildinfo", + "!lib/node/tsconfig.json", + "!lib/inspector/.tsbuildinfo", + "!lib/inspector/tsconfig.json", + "lib", + "src", + "lib-src" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/engine262/engine262.git" + }, + "peerDependencies": { + "ws": "^8.18.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + } + }, + "devDependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/core": "^7.28.5", + "@babel/plugin-proposal-decorators": "^7.28.0", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/preset-env": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@engine262/eslint-plugin": "file:test/eslint-plugin-engine262", + "@pppp606/ink-chart": "^0.2.4", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@stylistic/eslint-plugin-js": "^3.1.0", + "@types/babel__code-frame": "^7.0.6", + "@types/babel__core": "^7.20.5", + "@types/babel__traverse": "^7.28.0", + "@types/eslint": "^8.56.12", + "@types/estree": "^1.0.8", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.13.4", + "@types/react": "^19.2.7", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.51.0", + "@typescript-eslint/parser": "^8.51.0", + "@unicode/unicode-16.0.0": "^1.6.16", + "@vitest/coverage-v8": "^4.0.16", + "c8": "^10.1.3", + "cli-highlight": "^2.1.11", + "cross-env": "^10.1.0", + "devtools-protocol": "^0.0.1561482", + "eslint": "^8.57.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.32.0", + "http-server": "^14.1.1", + "ink": "^6.6.0", + "ink-task-list": "^2.0.0", + "js-yaml": "^4.1.0", + "npm-run-all": "^4.1.5", + "react": "^19.2.3", + "rollup": "^4.54.0", + "tinyglobby": "^0.2.15", + "typescript": "5.8.2", + "vitest": "^4.0.16", + "ws": "^8.18.3" + }, + "overrides": { + "typescript": "5.8.2" + } +} diff --git a/scripts/Unicode/PropertyValueAliases.txt b/scripts/Unicode/PropertyValueAliases.txt new file mode 100644 index 0000000..e52aa3d --- /dev/null +++ b/scripts/Unicode/PropertyValueAliases.txt @@ -0,0 +1,1710 @@ +# https://unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt + +# PropertyValueAliases-16.0.0.txt +# Date: 2024-07-30, 19:59:00 GMT +# © 2024 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use and license, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ +# +# This file contains aliases for property values used in the UCD. +# These names can be used for XML formats of UCD data, for regular-expression +# property tests, and other programmatic textual descriptions of Unicode data. +# +# The names may be translated in appropriate environments, and additional +# aliases may be useful. +# +# FORMAT +# +# Each line describes a property value name. +# This consists of three or more fields, separated by semicolons. +# +# First Field: The first field describes the property for which that +# property value name is used. +# +# Second Field: The second field is the short name for the property value. +# It is typically an abbreviation, but in a number of cases it is simply +# a duplicate of the "long name" in the third field. +# +# Third Field: The third field is the long name for the property value, +# typically the formal name used in documentation about the property value. +# +# In the case of Canonical_Combining_Class (ccc), there are 4 fields: +# The second field is numeric, the third is the short name, and the fourth is the long name. +# +# The above are the preferred aliases. Other aliases may be listed in additional fields. +# +# Loose matching should be applied to all property names and property values, with +# the exception of String Property values. With loose matching of property names and +# values, the case distinctions, whitespace, hyphens, and '_' are ignored. +# For Numeric Property values, numeric equivalence is applied: thus "01.00" +# is equivalent to "1". +# +# NOTE: Property value names are NOT unique across properties. For example: +# +# AL means Arabic Letter for the Bidi_Class property, and +# AL means Above_Left for the Canonical_Combining_Class property, and +# AL means Alphabetic for the Line_Break property. +# +# In addition, some property names may be the same as some property value names. +# For example: +# +# sc means the Script property, and +# Sc means the General_Category property value Currency_Symbol (Sc) +# +# The combination of property value and property name is, however, unique. +# +# For more information, see UAX #44, Unicode Character Database, and +# UTS #18, Unicode Regular Expressions. +# ================================================ + + +# ASCII_Hex_Digit (AHex) + +AHex; N ; No ; F ; False +AHex; Y ; Yes ; T ; True + +# Age (age) + +age; 1.1 ; V1_1 +age; 2.0 ; V2_0 +age; 2.1 ; V2_1 +age; 3.0 ; V3_0 +age; 3.1 ; V3_1 +age; 3.2 ; V3_2 +age; 4.0 ; V4_0 +age; 4.1 ; V4_1 +age; 5.0 ; V5_0 +age; 5.1 ; V5_1 +age; 5.2 ; V5_2 +age; 6.0 ; V6_0 +age; 6.1 ; V6_1 +age; 6.2 ; V6_2 +age; 6.3 ; V6_3 +age; 7.0 ; V7_0 +age; 8.0 ; V8_0 +age; 9.0 ; V9_0 +age; 10.0 ; V10_0 +age; 11.0 ; V11_0 +age; 12.0 ; V12_0 +age; 12.1 ; V12_1 +age; 13.0 ; V13_0 +age; 14.0 ; V14_0 +age; 15.0 ; V15_0 +age; 15.1 ; V15_1 +age; 16.0 ; V16_0 +age; NA ; Unassigned + +# Alphabetic (Alpha) + +Alpha; N ; No ; F ; False +Alpha; Y ; Yes ; T ; True + +# Bidi_Class (bc) + +bc ; AL ; Arabic_Letter +bc ; AN ; Arabic_Number +bc ; B ; Paragraph_Separator +bc ; BN ; Boundary_Neutral +bc ; CS ; Common_Separator +bc ; EN ; European_Number +bc ; ES ; European_Separator +bc ; ET ; European_Terminator +bc ; FSI ; First_Strong_Isolate +bc ; L ; Left_To_Right +bc ; LRE ; Left_To_Right_Embedding +bc ; LRI ; Left_To_Right_Isolate +bc ; LRO ; Left_To_Right_Override +bc ; NSM ; Nonspacing_Mark +bc ; ON ; Other_Neutral +bc ; PDF ; Pop_Directional_Format +bc ; PDI ; Pop_Directional_Isolate +bc ; R ; Right_To_Left +bc ; RLE ; Right_To_Left_Embedding +bc ; RLI ; Right_To_Left_Isolate +bc ; RLO ; Right_To_Left_Override +bc ; S ; Segment_Separator +bc ; WS ; White_Space + +# Bidi_Control (Bidi_C) + +Bidi_C; N ; No ; F ; False +Bidi_C; Y ; Yes ; T ; True + +# Bidi_Mirrored (Bidi_M) + +Bidi_M; N ; No ; F ; False +Bidi_M; Y ; Yes ; T ; True + +# Bidi_Mirroring_Glyph (bmg) + + +# Bidi_Paired_Bracket (bpb) + +# @missing: 0000..10FFFF; Bidi_Paired_Bracket; + +# Bidi_Paired_Bracket_Type (bpt) + +bpt; c ; Close +bpt; n ; None +bpt; o ; Open +# @missing: 0000..10FFFF; Bidi_Paired_Bracket_Type; n + +# Block (blk) + +blk; Adlam ; Adlam +blk; Aegean_Numbers ; Aegean_Numbers +blk; Ahom ; Ahom +blk; Alchemical ; Alchemical_Symbols +blk; Alphabetic_PF ; Alphabetic_Presentation_Forms +blk; Anatolian_Hieroglyphs ; Anatolian_Hieroglyphs +blk; Ancient_Greek_Music ; Ancient_Greek_Musical_Notation +blk; Ancient_Greek_Numbers ; Ancient_Greek_Numbers +blk; Ancient_Symbols ; Ancient_Symbols +blk; Arabic ; Arabic +blk; Arabic_Ext_A ; Arabic_Extended_A +blk; Arabic_Ext_B ; Arabic_Extended_B +blk; Arabic_Ext_C ; Arabic_Extended_C +blk; Arabic_Math ; Arabic_Mathematical_Alphabetic_Symbols +blk; Arabic_PF_A ; Arabic_Presentation_Forms_A ; Arabic_Presentation_Forms-A +blk; Arabic_PF_B ; Arabic_Presentation_Forms_B +blk; Arabic_Sup ; Arabic_Supplement +blk; Armenian ; Armenian +blk; Arrows ; Arrows +blk; ASCII ; Basic_Latin +blk; Avestan ; Avestan +blk; Balinese ; Balinese +blk; Bamum ; Bamum +blk; Bamum_Sup ; Bamum_Supplement +blk; Bassa_Vah ; Bassa_Vah +blk; Batak ; Batak +blk; Bengali ; Bengali +blk; Bhaiksuki ; Bhaiksuki +blk; Block_Elements ; Block_Elements +blk; Bopomofo ; Bopomofo +blk; Bopomofo_Ext ; Bopomofo_Extended +blk; Box_Drawing ; Box_Drawing +blk; Brahmi ; Brahmi +blk; Braille ; Braille_Patterns +blk; Buginese ; Buginese +blk; Buhid ; Buhid +blk; Byzantine_Music ; Byzantine_Musical_Symbols +blk; Carian ; Carian +blk; Caucasian_Albanian ; Caucasian_Albanian +blk; Chakma ; Chakma +blk; Cham ; Cham +blk; Cherokee ; Cherokee +blk; Cherokee_Sup ; Cherokee_Supplement +blk; Chess_Symbols ; Chess_Symbols +blk; Chorasmian ; Chorasmian +blk; CJK ; CJK_Unified_Ideographs +blk; CJK_Compat ; CJK_Compatibility +blk; CJK_Compat_Forms ; CJK_Compatibility_Forms +blk; CJK_Compat_Ideographs ; CJK_Compatibility_Ideographs +blk; CJK_Compat_Ideographs_Sup ; CJK_Compatibility_Ideographs_Supplement +blk; CJK_Ext_A ; CJK_Unified_Ideographs_Extension_A +blk; CJK_Ext_B ; CJK_Unified_Ideographs_Extension_B +blk; CJK_Ext_C ; CJK_Unified_Ideographs_Extension_C +blk; CJK_Ext_D ; CJK_Unified_Ideographs_Extension_D +blk; CJK_Ext_E ; CJK_Unified_Ideographs_Extension_E +blk; CJK_Ext_F ; CJK_Unified_Ideographs_Extension_F +blk; CJK_Ext_G ; CJK_Unified_Ideographs_Extension_G +blk; CJK_Ext_H ; CJK_Unified_Ideographs_Extension_H +blk; CJK_Ext_I ; CJK_Unified_Ideographs_Extension_I +blk; CJK_Radicals_Sup ; CJK_Radicals_Supplement +blk; CJK_Strokes ; CJK_Strokes +blk; CJK_Symbols ; CJK_Symbols_And_Punctuation +blk; Compat_Jamo ; Hangul_Compatibility_Jamo +blk; Control_Pictures ; Control_Pictures +blk; Coptic ; Coptic +blk; Coptic_Epact_Numbers ; Coptic_Epact_Numbers +blk; Counting_Rod ; Counting_Rod_Numerals +blk; Cuneiform ; Cuneiform +blk; Cuneiform_Numbers ; Cuneiform_Numbers_And_Punctuation +blk; Currency_Symbols ; Currency_Symbols +blk; Cypriot_Syllabary ; Cypriot_Syllabary +blk; Cypro_Minoan ; Cypro_Minoan +blk; Cyrillic ; Cyrillic +blk; Cyrillic_Ext_A ; Cyrillic_Extended_A +blk; Cyrillic_Ext_B ; Cyrillic_Extended_B +blk; Cyrillic_Ext_C ; Cyrillic_Extended_C +blk; Cyrillic_Ext_D ; Cyrillic_Extended_D +blk; Cyrillic_Sup ; Cyrillic_Supplement ; Cyrillic_Supplementary +blk; Deseret ; Deseret +blk; Devanagari ; Devanagari +blk; Devanagari_Ext ; Devanagari_Extended +blk; Devanagari_Ext_A ; Devanagari_Extended_A +blk; Diacriticals ; Combining_Diacritical_Marks +blk; Diacriticals_Ext ; Combining_Diacritical_Marks_Extended +blk; Diacriticals_For_Symbols ; Combining_Diacritical_Marks_For_Symbols; Combining_Marks_For_Symbols +blk; Diacriticals_Sup ; Combining_Diacritical_Marks_Supplement +blk; Dingbats ; Dingbats +blk; Dives_Akuru ; Dives_Akuru +blk; Dogra ; Dogra +blk; Domino ; Domino_Tiles +blk; Duployan ; Duployan +blk; Early_Dynastic_Cuneiform ; Early_Dynastic_Cuneiform +blk; Egyptian_Hieroglyph_Format_Controls; Egyptian_Hieroglyph_Format_Controls +blk; Egyptian_Hieroglyphs ; Egyptian_Hieroglyphs +blk; Egyptian_Hieroglyphs_Ext_A ; Egyptian_Hieroglyphs_Extended_A +blk; Elbasan ; Elbasan +blk; Elymaic ; Elymaic +blk; Emoticons ; Emoticons +blk; Enclosed_Alphanum ; Enclosed_Alphanumerics +blk; Enclosed_Alphanum_Sup ; Enclosed_Alphanumeric_Supplement +blk; Enclosed_CJK ; Enclosed_CJK_Letters_And_Months +blk; Enclosed_Ideographic_Sup ; Enclosed_Ideographic_Supplement +blk; Ethiopic ; Ethiopic +blk; Ethiopic_Ext ; Ethiopic_Extended +blk; Ethiopic_Ext_A ; Ethiopic_Extended_A +blk; Ethiopic_Ext_B ; Ethiopic_Extended_B +blk; Ethiopic_Sup ; Ethiopic_Supplement +blk; Garay ; Garay +blk; Geometric_Shapes ; Geometric_Shapes +blk; Geometric_Shapes_Ext ; Geometric_Shapes_Extended +blk; Georgian ; Georgian +blk; Georgian_Ext ; Georgian_Extended +blk; Georgian_Sup ; Georgian_Supplement +blk; Glagolitic ; Glagolitic +blk; Glagolitic_Sup ; Glagolitic_Supplement +blk; Gothic ; Gothic +blk; Grantha ; Grantha +blk; Greek ; Greek_And_Coptic +blk; Greek_Ext ; Greek_Extended +blk; Gujarati ; Gujarati +blk; Gunjala_Gondi ; Gunjala_Gondi +blk; Gurmukhi ; Gurmukhi +blk; Gurung_Khema ; Gurung_Khema +blk; Half_And_Full_Forms ; Halfwidth_And_Fullwidth_Forms +blk; Half_Marks ; Combining_Half_Marks +blk; Hangul ; Hangul_Syllables +blk; Hanifi_Rohingya ; Hanifi_Rohingya +blk; Hanunoo ; Hanunoo +blk; Hatran ; Hatran +blk; Hebrew ; Hebrew +blk; High_PU_Surrogates ; High_Private_Use_Surrogates +blk; High_Surrogates ; High_Surrogates +blk; Hiragana ; Hiragana +blk; IDC ; Ideographic_Description_Characters +blk; Ideographic_Symbols ; Ideographic_Symbols_And_Punctuation +blk; Imperial_Aramaic ; Imperial_Aramaic +blk; Indic_Number_Forms ; Common_Indic_Number_Forms +blk; Indic_Siyaq_Numbers ; Indic_Siyaq_Numbers +blk; Inscriptional_Pahlavi ; Inscriptional_Pahlavi +blk; Inscriptional_Parthian ; Inscriptional_Parthian +blk; IPA_Ext ; IPA_Extensions +blk; Jamo ; Hangul_Jamo +blk; Jamo_Ext_A ; Hangul_Jamo_Extended_A +blk; Jamo_Ext_B ; Hangul_Jamo_Extended_B +blk; Javanese ; Javanese +blk; Kaithi ; Kaithi +blk; Kaktovik_Numerals ; Kaktovik_Numerals +blk; Kana_Ext_A ; Kana_Extended_A +blk; Kana_Ext_B ; Kana_Extended_B +blk; Kana_Sup ; Kana_Supplement +blk; Kanbun ; Kanbun +blk; Kangxi ; Kangxi_Radicals +blk; Kannada ; Kannada +blk; Katakana ; Katakana +blk; Katakana_Ext ; Katakana_Phonetic_Extensions +blk; Kawi ; Kawi +blk; Kayah_Li ; Kayah_Li +blk; Kharoshthi ; Kharoshthi +blk; Khitan_Small_Script ; Khitan_Small_Script +blk; Khmer ; Khmer +blk; Khmer_Symbols ; Khmer_Symbols +blk; Khojki ; Khojki +blk; Khudawadi ; Khudawadi +blk; Kirat_Rai ; Kirat_Rai +blk; Lao ; Lao +blk; Latin_1_Sup ; Latin_1_Supplement ; Latin_1 +blk; Latin_Ext_A ; Latin_Extended_A +blk; Latin_Ext_Additional ; Latin_Extended_Additional +blk; Latin_Ext_B ; Latin_Extended_B +blk; Latin_Ext_C ; Latin_Extended_C +blk; Latin_Ext_D ; Latin_Extended_D +blk; Latin_Ext_E ; Latin_Extended_E +blk; Latin_Ext_F ; Latin_Extended_F +blk; Latin_Ext_G ; Latin_Extended_G +blk; Lepcha ; Lepcha +blk; Letterlike_Symbols ; Letterlike_Symbols +blk; Limbu ; Limbu +blk; Linear_A ; Linear_A +blk; Linear_B_Ideograms ; Linear_B_Ideograms +blk; Linear_B_Syllabary ; Linear_B_Syllabary +blk; Lisu ; Lisu +blk; Lisu_Sup ; Lisu_Supplement +blk; Low_Surrogates ; Low_Surrogates +blk; Lycian ; Lycian +blk; Lydian ; Lydian +blk; Mahajani ; Mahajani +blk; Mahjong ; Mahjong_Tiles +blk; Makasar ; Makasar +blk; Malayalam ; Malayalam +blk; Mandaic ; Mandaic +blk; Manichaean ; Manichaean +blk; Marchen ; Marchen +blk; Masaram_Gondi ; Masaram_Gondi +blk; Math_Alphanum ; Mathematical_Alphanumeric_Symbols +blk; Math_Operators ; Mathematical_Operators +blk; Mayan_Numerals ; Mayan_Numerals +blk; Medefaidrin ; Medefaidrin +blk; Meetei_Mayek ; Meetei_Mayek +blk; Meetei_Mayek_Ext ; Meetei_Mayek_Extensions +blk; Mende_Kikakui ; Mende_Kikakui +blk; Meroitic_Cursive ; Meroitic_Cursive +blk; Meroitic_Hieroglyphs ; Meroitic_Hieroglyphs +blk; Miao ; Miao +blk; Misc_Arrows ; Miscellaneous_Symbols_And_Arrows +blk; Misc_Math_Symbols_A ; Miscellaneous_Mathematical_Symbols_A +blk; Misc_Math_Symbols_B ; Miscellaneous_Mathematical_Symbols_B +blk; Misc_Pictographs ; Miscellaneous_Symbols_And_Pictographs +blk; Misc_Symbols ; Miscellaneous_Symbols +blk; Misc_Technical ; Miscellaneous_Technical +blk; Modi ; Modi +blk; Modifier_Letters ; Spacing_Modifier_Letters +blk; Modifier_Tone_Letters ; Modifier_Tone_Letters +blk; Mongolian ; Mongolian +blk; Mongolian_Sup ; Mongolian_Supplement +blk; Mro ; Mro +blk; Multani ; Multani +blk; Music ; Musical_Symbols +blk; Myanmar ; Myanmar +blk; Myanmar_Ext_A ; Myanmar_Extended_A +blk; Myanmar_Ext_B ; Myanmar_Extended_B +blk; Myanmar_Ext_C ; Myanmar_Extended_C +blk; Nabataean ; Nabataean +blk; Nag_Mundari ; Nag_Mundari +blk; Nandinagari ; Nandinagari +blk; NB ; No_Block +blk; New_Tai_Lue ; New_Tai_Lue +blk; Newa ; Newa +blk; NKo ; NKo +blk; Number_Forms ; Number_Forms +blk; Nushu ; Nushu +blk; Nyiakeng_Puachue_Hmong ; Nyiakeng_Puachue_Hmong +blk; OCR ; Optical_Character_Recognition +blk; Ogham ; Ogham +blk; Ol_Chiki ; Ol_Chiki +blk; Ol_Onal ; Ol_Onal +blk; Old_Hungarian ; Old_Hungarian +blk; Old_Italic ; Old_Italic +blk; Old_North_Arabian ; Old_North_Arabian +blk; Old_Permic ; Old_Permic +blk; Old_Persian ; Old_Persian +blk; Old_Sogdian ; Old_Sogdian +blk; Old_South_Arabian ; Old_South_Arabian +blk; Old_Turkic ; Old_Turkic +blk; Old_Uyghur ; Old_Uyghur +blk; Oriya ; Oriya +blk; Ornamental_Dingbats ; Ornamental_Dingbats +blk; Osage ; Osage +blk; Osmanya ; Osmanya +blk; Ottoman_Siyaq_Numbers ; Ottoman_Siyaq_Numbers +blk; Pahawh_Hmong ; Pahawh_Hmong +blk; Palmyrene ; Palmyrene +blk; Pau_Cin_Hau ; Pau_Cin_Hau +blk; Phags_Pa ; Phags_Pa +blk; Phaistos ; Phaistos_Disc +blk; Phoenician ; Phoenician +blk; Phonetic_Ext ; Phonetic_Extensions +blk; Phonetic_Ext_Sup ; Phonetic_Extensions_Supplement +blk; Playing_Cards ; Playing_Cards +blk; Psalter_Pahlavi ; Psalter_Pahlavi +blk; PUA ; Private_Use_Area ; Private_Use +blk; Punctuation ; General_Punctuation +blk; Rejang ; Rejang +blk; Rumi ; Rumi_Numeral_Symbols +blk; Runic ; Runic +blk; Samaritan ; Samaritan +blk; Saurashtra ; Saurashtra +blk; Sharada ; Sharada +blk; Shavian ; Shavian +blk; Shorthand_Format_Controls ; Shorthand_Format_Controls +blk; Siddham ; Siddham +blk; Sinhala ; Sinhala +blk; Sinhala_Archaic_Numbers ; Sinhala_Archaic_Numbers +blk; Small_Forms ; Small_Form_Variants +blk; Small_Kana_Ext ; Small_Kana_Extension +blk; Sogdian ; Sogdian +blk; Sora_Sompeng ; Sora_Sompeng +blk; Soyombo ; Soyombo +blk; Specials ; Specials +blk; Sundanese ; Sundanese +blk; Sundanese_Sup ; Sundanese_Supplement +blk; Sunuwar ; Sunuwar +blk; Sup_Arrows_A ; Supplemental_Arrows_A +blk; Sup_Arrows_B ; Supplemental_Arrows_B +blk; Sup_Arrows_C ; Supplemental_Arrows_C +blk; Sup_Math_Operators ; Supplemental_Mathematical_Operators +blk; Sup_PUA_A ; Supplementary_Private_Use_Area_A +blk; Sup_PUA_B ; Supplementary_Private_Use_Area_B +blk; Sup_Punctuation ; Supplemental_Punctuation +blk; Sup_Symbols_And_Pictographs ; Supplemental_Symbols_And_Pictographs +blk; Super_And_Sub ; Superscripts_And_Subscripts +blk; Sutton_SignWriting ; Sutton_SignWriting +blk; Syloti_Nagri ; Syloti_Nagri +blk; Symbols_And_Pictographs_Ext_A ; Symbols_And_Pictographs_Extended_A +blk; Symbols_For_Legacy_Computing ; Symbols_For_Legacy_Computing +blk; Symbols_For_Legacy_Computing_Sup ; Symbols_For_Legacy_Computing_Supplement +blk; Syriac ; Syriac +blk; Syriac_Sup ; Syriac_Supplement +blk; Tagalog ; Tagalog +blk; Tagbanwa ; Tagbanwa +blk; Tags ; Tags +blk; Tai_Le ; Tai_Le +blk; Tai_Tham ; Tai_Tham +blk; Tai_Viet ; Tai_Viet +blk; Tai_Xuan_Jing ; Tai_Xuan_Jing_Symbols +blk; Takri ; Takri +blk; Tamil ; Tamil +blk; Tamil_Sup ; Tamil_Supplement +blk; Tangsa ; Tangsa +blk; Tangut ; Tangut +blk; Tangut_Components ; Tangut_Components +blk; Tangut_Sup ; Tangut_Supplement +blk; Telugu ; Telugu +blk; Thaana ; Thaana +blk; Thai ; Thai +blk; Tibetan ; Tibetan +blk; Tifinagh ; Tifinagh +blk; Tirhuta ; Tirhuta +blk; Todhri ; Todhri +blk; Toto ; Toto +blk; Transport_And_Map ; Transport_And_Map_Symbols +blk; Tulu_Tigalari ; Tulu_Tigalari +blk; UCAS ; Unified_Canadian_Aboriginal_Syllabics; Canadian_Syllabics +blk; UCAS_Ext ; Unified_Canadian_Aboriginal_Syllabics_Extended +blk; UCAS_Ext_A ; Unified_Canadian_Aboriginal_Syllabics_Extended_A +blk; Ugaritic ; Ugaritic +blk; Vai ; Vai +blk; Vedic_Ext ; Vedic_Extensions +blk; Vertical_Forms ; Vertical_Forms +blk; Vithkuqi ; Vithkuqi +blk; VS ; Variation_Selectors +blk; VS_Sup ; Variation_Selectors_Supplement +blk; Wancho ; Wancho +blk; Warang_Citi ; Warang_Citi +blk; Yezidi ; Yezidi +blk; Yi_Radicals ; Yi_Radicals +blk; Yi_Syllables ; Yi_Syllables +blk; Yijing ; Yijing_Hexagram_Symbols +blk; Zanabazar_Square ; Zanabazar_Square +blk; Znamenny_Music ; Znamenny_Musical_Notation + +# Canonical_Combining_Class (ccc) + +ccc; 0; NR ; Not_Reordered +ccc; 1; OV ; Overlay +ccc; 6; HANR ; Han_Reading +ccc; 7; NK ; Nukta +ccc; 8; KV ; Kana_Voicing +ccc; 9; VR ; Virama +ccc; 10; CCC10 ; CCC10 +ccc; 11; CCC11 ; CCC11 +ccc; 12; CCC12 ; CCC12 +ccc; 13; CCC13 ; CCC13 +ccc; 14; CCC14 ; CCC14 +ccc; 15; CCC15 ; CCC15 +ccc; 16; CCC16 ; CCC16 +ccc; 17; CCC17 ; CCC17 +ccc; 18; CCC18 ; CCC18 +ccc; 19; CCC19 ; CCC19 +ccc; 20; CCC20 ; CCC20 +ccc; 21; CCC21 ; CCC21 +ccc; 22; CCC22 ; CCC22 +ccc; 23; CCC23 ; CCC23 +ccc; 24; CCC24 ; CCC24 +ccc; 25; CCC25 ; CCC25 +ccc; 26; CCC26 ; CCC26 +ccc; 27; CCC27 ; CCC27 +ccc; 28; CCC28 ; CCC28 +ccc; 29; CCC29 ; CCC29 +ccc; 30; CCC30 ; CCC30 +ccc; 31; CCC31 ; CCC31 +ccc; 32; CCC32 ; CCC32 +ccc; 33; CCC33 ; CCC33 +ccc; 34; CCC34 ; CCC34 +ccc; 35; CCC35 ; CCC35 +ccc; 36; CCC36 ; CCC36 +ccc; 84; CCC84 ; CCC84 +ccc; 91; CCC91 ; CCC91 +ccc; 103; CCC103 ; CCC103 +ccc; 107; CCC107 ; CCC107 +ccc; 118; CCC118 ; CCC118 +ccc; 122; CCC122 ; CCC122 +ccc; 129; CCC129 ; CCC129 +ccc; 130; CCC130 ; CCC130 +ccc; 132; CCC132 ; CCC132 +ccc; 133; CCC133 ; CCC133 # RESERVED +ccc; 200; ATBL ; Attached_Below_Left +ccc; 202; ATB ; Attached_Below +ccc; 214; ATA ; Attached_Above +ccc; 216; ATAR ; Attached_Above_Right +ccc; 218; BL ; Below_Left +ccc; 220; B ; Below +ccc; 222; BR ; Below_Right +ccc; 224; L ; Left +ccc; 226; R ; Right +ccc; 228; AL ; Above_Left +ccc; 230; A ; Above +ccc; 232; AR ; Above_Right +ccc; 233; DB ; Double_Below +ccc; 234; DA ; Double_Above +ccc; 240; IS ; Iota_Subscript + +# Case_Folding (cf) + +# @missing: 0000..10FFFF; Case_Folding; + +# Case_Ignorable (CI) + +CI ; N ; No ; F ; False +CI ; Y ; Yes ; T ; True + +# Cased (Cased) + +Cased; N ; No ; F ; False +Cased; Y ; Yes ; T ; True + +# Changes_When_Casefolded (CWCF) + +CWCF; N ; No ; F ; False +CWCF; Y ; Yes ; T ; True + +# Changes_When_Casemapped (CWCM) + +CWCM; N ; No ; F ; False +CWCM; Y ; Yes ; T ; True + +# Changes_When_Lowercased (CWL) + +CWL; N ; No ; F ; False +CWL; Y ; Yes ; T ; True + +# Changes_When_NFKC_Casefolded (CWKCF) + +CWKCF; N ; No ; F ; False +CWKCF; Y ; Yes ; T ; True + +# Changes_When_Titlecased (CWT) + +CWT; N ; No ; F ; False +CWT; Y ; Yes ; T ; True + +# Changes_When_Uppercased (CWU) + +CWU; N ; No ; F ; False +CWU; Y ; Yes ; T ; True + +# Composition_Exclusion (CE) + +CE ; N ; No ; F ; False +CE ; Y ; Yes ; T ; True + +# Dash (Dash) + +Dash; N ; No ; F ; False +Dash; Y ; Yes ; T ; True + +# Decomposition_Mapping (dm) + +# @missing: 0000..10FFFF; Decomposition_Mapping; + +# Decomposition_Type (dt) + +dt ; Can ; Canonical ; can +dt ; Com ; Compat ; com +dt ; Enc ; Circle ; enc +dt ; Fin ; Final ; fin +dt ; Font ; Font ; font +dt ; Fra ; Fraction ; fra +dt ; Init ; Initial ; init +dt ; Iso ; Isolated ; iso +dt ; Med ; Medial ; med +dt ; Nar ; Narrow ; nar +dt ; Nb ; Nobreak ; nb +dt ; None ; None ; none +dt ; Sml ; Small ; sml +dt ; Sqr ; Square ; sqr +dt ; Sub ; Sub ; sub +dt ; Sup ; Super ; sup +dt ; Vert ; Vertical ; vert +dt ; Wide ; Wide ; wide + +# Default_Ignorable_Code_Point (DI) + +DI ; N ; No ; F ; False +DI ; Y ; Yes ; T ; True + +# Deprecated (Dep) + +Dep; N ; No ; F ; False +Dep; Y ; Yes ; T ; True + +# Diacritic (Dia) + +Dia; N ; No ; F ; False +Dia; Y ; Yes ; T ; True + +# East_Asian_Width (ea) + +ea ; A ; Ambiguous +ea ; F ; Fullwidth +ea ; H ; Halfwidth +ea ; N ; Neutral +ea ; Na ; Narrow +ea ; W ; Wide + +# Emoji (Emoji) + +Emoji; N ; No ; F ; False +Emoji; Y ; Yes ; T ; True + +# Emoji_Component (EComp) + +EComp; N ; No ; F ; False +EComp; Y ; Yes ; T ; True + +# Emoji_Modifier (EMod) + +EMod; N ; No ; F ; False +EMod; Y ; Yes ; T ; True + +# Emoji_Modifier_Base (EBase) + +EBase; N ; No ; F ; False +EBase; Y ; Yes ; T ; True + +# Emoji_Presentation (EPres) + +EPres; N ; No ; F ; False +EPres; Y ; Yes ; T ; True + +# Equivalent_Unified_Ideograph (EqUIdeo) + + +# Expands_On_NFC (XO_NFC) + +XO_NFC; N ; No ; F ; False +XO_NFC; Y ; Yes ; T ; True + +# Expands_On_NFD (XO_NFD) + +XO_NFD; N ; No ; F ; False +XO_NFD; Y ; Yes ; T ; True + +# Expands_On_NFKC (XO_NFKC) + +XO_NFKC; N ; No ; F ; False +XO_NFKC; Y ; Yes ; T ; True + +# Expands_On_NFKD (XO_NFKD) + +XO_NFKD; N ; No ; F ; False +XO_NFKD; Y ; Yes ; T ; True + +# Extended_Pictographic (ExtPict) + +ExtPict; N ; No ; F ; False +ExtPict; Y ; Yes ; T ; True + +# Extender (Ext) + +Ext; N ; No ; F ; False +Ext; Y ; Yes ; T ; True + +# FC_NFKC_Closure (FC_NFKC) + +# @missing: 0000..10FFFF; FC_NFKC_Closure; + +# Full_Composition_Exclusion (Comp_Ex) + +Comp_Ex; N ; No ; F ; False +Comp_Ex; Y ; Yes ; T ; True + +# General_Category (gc) + +gc ; C ; Other # Cc | Cf | Cn | Co | Cs +gc ; Cc ; Control ; cntrl +gc ; Cf ; Format +gc ; Cn ; Unassigned +gc ; Co ; Private_Use +gc ; Cs ; Surrogate +gc ; L ; Letter # Ll | Lm | Lo | Lt | Lu +gc ; LC ; Cased_Letter # Ll | Lt | Lu +gc ; Ll ; Lowercase_Letter +gc ; Lm ; Modifier_Letter +gc ; Lo ; Other_Letter +gc ; Lt ; Titlecase_Letter +gc ; Lu ; Uppercase_Letter +gc ; M ; Mark ; Combining_Mark # Mc | Me | Mn +gc ; Mc ; Spacing_Mark +gc ; Me ; Enclosing_Mark +gc ; Mn ; Nonspacing_Mark +gc ; N ; Number # Nd | Nl | No +gc ; Nd ; Decimal_Number ; digit +gc ; Nl ; Letter_Number +gc ; No ; Other_Number +gc ; P ; Punctuation ; punct # Pc | Pd | Pe | Pf | Pi | Po | Ps +gc ; Pc ; Connector_Punctuation +gc ; Pd ; Dash_Punctuation +gc ; Pe ; Close_Punctuation +gc ; Pf ; Final_Punctuation +gc ; Pi ; Initial_Punctuation +gc ; Po ; Other_Punctuation +gc ; Ps ; Open_Punctuation +gc ; S ; Symbol # Sc | Sk | Sm | So +gc ; Sc ; Currency_Symbol +gc ; Sk ; Modifier_Symbol +gc ; Sm ; Math_Symbol +gc ; So ; Other_Symbol +gc ; Z ; Separator # Zl | Zp | Zs +gc ; Zl ; Line_Separator +gc ; Zp ; Paragraph_Separator +gc ; Zs ; Space_Separator +# @missing: 0000..10FFFF; General_Category; Unassigned + +# Grapheme_Base (Gr_Base) + +Gr_Base; N ; No ; F ; False +Gr_Base; Y ; Yes ; T ; True + +# Grapheme_Cluster_Break (GCB) + +GCB; CN ; Control +GCB; CR ; CR +GCB; EB ; E_Base +GCB; EBG ; E_Base_GAZ +GCB; EM ; E_Modifier +GCB; EX ; Extend +GCB; GAZ ; Glue_After_Zwj +GCB; L ; L +GCB; LF ; LF +GCB; LV ; LV +GCB; LVT ; LVT +GCB; PP ; Prepend +GCB; RI ; Regional_Indicator +GCB; SM ; SpacingMark +GCB; T ; T +GCB; V ; V +GCB; XX ; Other +GCB; ZWJ ; ZWJ + +# Grapheme_Extend (Gr_Ext) + +Gr_Ext; N ; No ; F ; False +Gr_Ext; Y ; Yes ; T ; True + +# Grapheme_Link (Gr_Link) + +Gr_Link; N ; No ; F ; False +Gr_Link; Y ; Yes ; T ; True + +# Hangul_Syllable_Type (hst) + +hst; L ; Leading_Jamo +hst; LV ; LV_Syllable +hst; LVT ; LVT_Syllable +hst; NA ; Not_Applicable +hst; T ; Trailing_Jamo +hst; V ; Vowel_Jamo + +# Hex_Digit (Hex) + +Hex; N ; No ; F ; False +Hex; Y ; Yes ; T ; True + +# Hyphen (Hyphen) + +Hyphen; N ; No ; F ; False +Hyphen; Y ; Yes ; T ; True + +# IDS_Binary_Operator (IDSB) + +IDSB; N ; No ; F ; False +IDSB; Y ; Yes ; T ; True + +# IDS_Trinary_Operator (IDST) + +IDST; N ; No ; F ; False +IDST; Y ; Yes ; T ; True + +# IDS_Unary_Operator (IDSU) + +IDSU; N ; No ; F ; False +IDSU; Y ; Yes ; T ; True + +# ID_Compat_Math_Continue (ID_Compat_Math_Continue) + +ID_Compat_Math_Continue; N ; No ; F ; False +ID_Compat_Math_Continue; Y ; Yes ; T ; True + +# ID_Compat_Math_Start (ID_Compat_Math_Start) + +ID_Compat_Math_Start; N ; No ; F ; False +ID_Compat_Math_Start; Y ; Yes ; T ; True + +# ID_Continue (IDC) + +IDC; N ; No ; F ; False +IDC; Y ; Yes ; T ; True + +# ID_Start (IDS) + +IDS; N ; No ; F ; False +IDS; Y ; Yes ; T ; True + +# ISO_Comment (isc) + +# @missing: 0000..10FFFF; ISO_Comment; + +# Ideographic (Ideo) + +Ideo; N ; No ; F ; False +Ideo; Y ; Yes ; T ; True + +# Indic_Conjunct_Break (InCB) + +InCB; Consonant ; Consonant +InCB; Extend ; Extend +InCB; Linker ; Linker +InCB; None ; None + +# Indic_Positional_Category (InPC) + +InPC; Bottom ; Bottom +InPC; Bottom_And_Left ; Bottom_And_Left +InPC; Bottom_And_Right ; Bottom_And_Right +InPC; Left ; Left +InPC; Left_And_Right ; Left_And_Right +InPC; NA ; NA +InPC; Overstruck ; Overstruck +InPC; Right ; Right +InPC; Top ; Top +InPC; Top_And_Bottom ; Top_And_Bottom +InPC; Top_And_Bottom_And_Left ; Top_And_Bottom_And_Left +InPC; Top_And_Bottom_And_Right ; Top_And_Bottom_And_Right +InPC; Top_And_Left ; Top_And_Left +InPC; Top_And_Left_And_Right ; Top_And_Left_And_Right +InPC; Top_And_Right ; Top_And_Right +InPC; Visual_Order_Left ; Visual_Order_Left + +# Indic_Syllabic_Category (InSC) + +InSC; Avagraha ; Avagraha +InSC; Bindu ; Bindu +InSC; Brahmi_Joining_Number ; Brahmi_Joining_Number +InSC; Cantillation_Mark ; Cantillation_Mark +InSC; Consonant ; Consonant +InSC; Consonant_Dead ; Consonant_Dead +InSC; Consonant_Final ; Consonant_Final +InSC; Consonant_Head_Letter ; Consonant_Head_Letter +InSC; Consonant_Initial_Postfixed ; Consonant_Initial_Postfixed +InSC; Consonant_Killer ; Consonant_Killer +InSC; Consonant_Medial ; Consonant_Medial +InSC; Consonant_Placeholder ; Consonant_Placeholder +InSC; Consonant_Preceding_Repha ; Consonant_Preceding_Repha +InSC; Consonant_Prefixed ; Consonant_Prefixed +InSC; Consonant_Subjoined ; Consonant_Subjoined +InSC; Consonant_Succeeding_Repha ; Consonant_Succeeding_Repha +InSC; Consonant_With_Stacker ; Consonant_With_Stacker +InSC; Gemination_Mark ; Gemination_Mark +InSC; Invisible_Stacker ; Invisible_Stacker +InSC; Joiner ; Joiner +InSC; Modifying_Letter ; Modifying_Letter +InSC; Non_Joiner ; Non_Joiner +InSC; Nukta ; Nukta +InSC; Number ; Number +InSC; Number_Joiner ; Number_Joiner +InSC; Other ; Other +InSC; Pure_Killer ; Pure_Killer +InSC; Register_Shifter ; Register_Shifter +InSC; Reordering_Killer ; Reordering_Killer +InSC; Syllable_Modifier ; Syllable_Modifier +InSC; Tone_Letter ; Tone_Letter +InSC; Tone_Mark ; Tone_Mark +InSC; Virama ; Virama +InSC; Visarga ; Visarga +InSC; Vowel ; Vowel +InSC; Vowel_Dependent ; Vowel_Dependent +InSC; Vowel_Independent ; Vowel_Independent + +# Jamo_Short_Name (JSN) + +JSN; A ; A +JSN; AE ; AE +JSN; B ; B +JSN; BB ; BB +JSN; BS ; BS +JSN; C ; C +JSN; D ; D +JSN; DD ; DD +JSN; E ; E +JSN; EO ; EO +JSN; EU ; EU +JSN; G ; G +JSN; GG ; GG +JSN; GS ; GS +JSN; H ; H +JSN; I ; I +JSN; J ; J +JSN; JJ ; JJ +JSN; K ; K +JSN; L ; L +JSN; LB ; LB +JSN; LG ; LG +JSN; LH ; LH +JSN; LM ; LM +JSN; LP ; LP +JSN; LS ; LS +JSN; LT ; LT +JSN; M ; M +JSN; N ; N +JSN; NG ; NG +JSN; NH ; NH +JSN; NJ ; NJ +JSN; O ; O +JSN; OE ; OE +JSN; P ; P +JSN; R ; R +JSN; S ; S +JSN; SS ; SS +JSN; T ; T +JSN; U ; U +JSN; WA ; WA +JSN; WAE ; WAE +JSN; WE ; WE +JSN; WEO ; WEO +JSN; WI ; WI +JSN; YA ; YA +JSN; YAE ; YAE +JSN; YE ; YE +JSN; YEO ; YEO +JSN; YI ; YI +JSN; YO ; YO +JSN; YU ; YU +# @missing: 0000..10FFFF; Jamo_Short_Name; + +# Join_Control (Join_C) + +Join_C; N ; No ; F ; False +Join_C; Y ; Yes ; T ; True + +# Joining_Group (jg) + +jg ; African_Feh ; African_Feh +jg ; African_Noon ; African_Noon +jg ; African_Qaf ; African_Qaf +jg ; Ain ; Ain +jg ; Alaph ; Alaph +jg ; Alef ; Alef +jg ; Beh ; Beh +jg ; Beth ; Beth +jg ; Burushaski_Yeh_Barree ; Burushaski_Yeh_Barree +jg ; Dal ; Dal +jg ; Dalath_Rish ; Dalath_Rish +jg ; E ; E +jg ; Farsi_Yeh ; Farsi_Yeh +jg ; Fe ; Fe +jg ; Feh ; Feh +jg ; Final_Semkath ; Final_Semkath +jg ; Gaf ; Gaf +jg ; Gamal ; Gamal +jg ; Hah ; Hah +jg ; Hanifi_Rohingya_Kinna_Ya ; Hanifi_Rohingya_Kinna_Ya +jg ; Hanifi_Rohingya_Pa ; Hanifi_Rohingya_Pa +jg ; He ; He +jg ; Heh ; Heh +jg ; Heh_Goal ; Heh_Goal +jg ; Heth ; Heth +jg ; Kaf ; Kaf +jg ; Kaph ; Kaph +jg ; Kashmiri_Yeh ; Kashmiri_Yeh +jg ; Khaph ; Khaph +jg ; Knotted_Heh ; Knotted_Heh +jg ; Lam ; Lam +jg ; Lamadh ; Lamadh +jg ; Malayalam_Bha ; Malayalam_Bha +jg ; Malayalam_Ja ; Malayalam_Ja +jg ; Malayalam_Lla ; Malayalam_Lla +jg ; Malayalam_Llla ; Malayalam_Llla +jg ; Malayalam_Nga ; Malayalam_Nga +jg ; Malayalam_Nna ; Malayalam_Nna +jg ; Malayalam_Nnna ; Malayalam_Nnna +jg ; Malayalam_Nya ; Malayalam_Nya +jg ; Malayalam_Ra ; Malayalam_Ra +jg ; Malayalam_Ssa ; Malayalam_Ssa +jg ; Malayalam_Tta ; Malayalam_Tta +jg ; Manichaean_Aleph ; Manichaean_Aleph +jg ; Manichaean_Ayin ; Manichaean_Ayin +jg ; Manichaean_Beth ; Manichaean_Beth +jg ; Manichaean_Daleth ; Manichaean_Daleth +jg ; Manichaean_Dhamedh ; Manichaean_Dhamedh +jg ; Manichaean_Five ; Manichaean_Five +jg ; Manichaean_Gimel ; Manichaean_Gimel +jg ; Manichaean_Heth ; Manichaean_Heth +jg ; Manichaean_Hundred ; Manichaean_Hundred +jg ; Manichaean_Kaph ; Manichaean_Kaph +jg ; Manichaean_Lamedh ; Manichaean_Lamedh +jg ; Manichaean_Mem ; Manichaean_Mem +jg ; Manichaean_Nun ; Manichaean_Nun +jg ; Manichaean_One ; Manichaean_One +jg ; Manichaean_Pe ; Manichaean_Pe +jg ; Manichaean_Qoph ; Manichaean_Qoph +jg ; Manichaean_Resh ; Manichaean_Resh +jg ; Manichaean_Sadhe ; Manichaean_Sadhe +jg ; Manichaean_Samekh ; Manichaean_Samekh +jg ; Manichaean_Taw ; Manichaean_Taw +jg ; Manichaean_Ten ; Manichaean_Ten +jg ; Manichaean_Teth ; Manichaean_Teth +jg ; Manichaean_Thamedh ; Manichaean_Thamedh +jg ; Manichaean_Twenty ; Manichaean_Twenty +jg ; Manichaean_Waw ; Manichaean_Waw +jg ; Manichaean_Yodh ; Manichaean_Yodh +jg ; Manichaean_Zayin ; Manichaean_Zayin +jg ; Meem ; Meem +jg ; Mim ; Mim +jg ; No_Joining_Group ; No_Joining_Group +jg ; Noon ; Noon +jg ; Nun ; Nun +jg ; Nya ; Nya +jg ; Pe ; Pe +jg ; Qaf ; Qaf +jg ; Qaph ; Qaph +jg ; Reh ; Reh +jg ; Reversed_Pe ; Reversed_Pe +jg ; Rohingya_Yeh ; Rohingya_Yeh +jg ; Sad ; Sad +jg ; Sadhe ; Sadhe +jg ; Seen ; Seen +jg ; Semkath ; Semkath +jg ; Shin ; Shin +jg ; Straight_Waw ; Straight_Waw +jg ; Swash_Kaf ; Swash_Kaf +jg ; Syriac_Waw ; Syriac_Waw +jg ; Tah ; Tah +jg ; Taw ; Taw +jg ; Teh_Marbuta ; Teh_Marbuta +jg ; Teh_Marbuta_Goal ; Teh_Marbuta_Goal ; Hamza_On_Heh_Goal +jg ; Teth ; Teth +jg ; Thin_Yeh ; Thin_Yeh +jg ; Vertical_Tail ; Vertical_Tail +jg ; Waw ; Waw +jg ; Yeh ; Yeh +jg ; Yeh_Barree ; Yeh_Barree +jg ; Yeh_With_Tail ; Yeh_With_Tail +jg ; Yudh ; Yudh +jg ; Yudh_He ; Yudh_He +jg ; Zain ; Zain +jg ; Zhain ; Zhain + +# Joining_Type (jt) + +jt ; C ; Join_Causing +jt ; D ; Dual_Joining +jt ; L ; Left_Joining +jt ; R ; Right_Joining +jt ; T ; Transparent +jt ; U ; Non_Joining + +# Line_Break (lb) + +lb ; AI ; Ambiguous +lb ; AK ; Aksara +lb ; AL ; Alphabetic +lb ; AP ; Aksara_Prebase +lb ; AS ; Aksara_Start +lb ; B2 ; Break_Both +lb ; BA ; Break_After +lb ; BB ; Break_Before +lb ; BK ; Mandatory_Break +lb ; CB ; Contingent_Break +lb ; CJ ; Conditional_Japanese_Starter +lb ; CL ; Close_Punctuation +lb ; CM ; Combining_Mark +lb ; CP ; Close_Parenthesis +lb ; CR ; Carriage_Return +lb ; EB ; E_Base +lb ; EM ; E_Modifier +lb ; EX ; Exclamation +lb ; GL ; Glue +lb ; H2 ; H2 +lb ; H3 ; H3 +lb ; HL ; Hebrew_Letter +lb ; HY ; Hyphen +lb ; ID ; Ideographic +lb ; IN ; Inseparable ; Inseperable +lb ; IS ; Infix_Numeric +lb ; JL ; JL +lb ; JT ; JT +lb ; JV ; JV +lb ; LF ; Line_Feed +lb ; NL ; Next_Line +lb ; NS ; Nonstarter +lb ; NU ; Numeric +lb ; OP ; Open_Punctuation +lb ; PO ; Postfix_Numeric +lb ; PR ; Prefix_Numeric +lb ; QU ; Quotation +lb ; RI ; Regional_Indicator +lb ; SA ; Complex_Context +lb ; SG ; Surrogate +lb ; SP ; Space +lb ; SY ; Break_Symbols +lb ; VF ; Virama_Final +lb ; VI ; Virama +lb ; WJ ; Word_Joiner +lb ; XX ; Unknown +lb ; ZW ; ZWSpace +lb ; ZWJ ; ZWJ + +# Logical_Order_Exception (LOE) + +LOE; N ; No ; F ; False +LOE; Y ; Yes ; T ; True + +# Lowercase (Lower) + +Lower; N ; No ; F ; False +Lower; Y ; Yes ; T ; True + +# Lowercase_Mapping (lc) + +# @missing: 0000..10FFFF; Lowercase_Mapping; + +# Math (Math) + +Math; N ; No ; F ; False +Math; Y ; Yes ; T ; True + +# Modifier_Combining_Mark (MCM) + +MCM; N ; No ; F ; False +MCM; Y ; Yes ; T ; True + +# NFC_Quick_Check (NFC_QC) + +NFC_QC; M ; Maybe +NFC_QC; N ; No +NFC_QC; Y ; Yes + +# NFD_Quick_Check (NFD_QC) + +NFD_QC; N ; No +NFD_QC; Y ; Yes + +# NFKC_Casefold (NFKC_CF) + + +# NFKC_Quick_Check (NFKC_QC) + +NFKC_QC; M ; Maybe +NFKC_QC; N ; No +NFKC_QC; Y ; Yes + +# NFKC_Simple_Casefold (NFKC_SCF) + + +# NFKD_Quick_Check (NFKD_QC) + +NFKD_QC; N ; No +NFKD_QC; Y ; Yes + +# Name (na) + +# @missing: 0000..10FFFF; Name; + +# Name_Alias (Name_Alias) + +# @missing: 0000..10FFFF; Name_Alias; + +# Noncharacter_Code_Point (NChar) + +NChar; N ; No ; F ; False +NChar; Y ; Yes ; T ; True + +# Numeric_Type (nt) + +nt ; De ; Decimal +nt ; Di ; Digit +nt ; None ; None +nt ; Nu ; Numeric + +# Numeric_Value (nv) + +# @missing: 0000..10FFFF; Numeric_Value; NaN + +# Other_Alphabetic (OAlpha) + +OAlpha; N ; No ; F ; False +OAlpha; Y ; Yes ; T ; True + +# Other_Default_Ignorable_Code_Point (ODI) + +ODI; N ; No ; F ; False +ODI; Y ; Yes ; T ; True + +# Other_Grapheme_Extend (OGr_Ext) + +OGr_Ext; N ; No ; F ; False +OGr_Ext; Y ; Yes ; T ; True + +# Other_ID_Continue (OIDC) + +OIDC; N ; No ; F ; False +OIDC; Y ; Yes ; T ; True + +# Other_ID_Start (OIDS) + +OIDS; N ; No ; F ; False +OIDS; Y ; Yes ; T ; True + +# Other_Lowercase (OLower) + +OLower; N ; No ; F ; False +OLower; Y ; Yes ; T ; True + +# Other_Math (OMath) + +OMath; N ; No ; F ; False +OMath; Y ; Yes ; T ; True + +# Other_Uppercase (OUpper) + +OUpper; N ; No ; F ; False +OUpper; Y ; Yes ; T ; True + +# Pattern_Syntax (Pat_Syn) + +Pat_Syn; N ; No ; F ; False +Pat_Syn; Y ; Yes ; T ; True + +# Pattern_White_Space (Pat_WS) + +Pat_WS; N ; No ; F ; False +Pat_WS; Y ; Yes ; T ; True + +# Prepended_Concatenation_Mark (PCM) + +PCM; N ; No ; F ; False +PCM; Y ; Yes ; T ; True + +# Quotation_Mark (QMark) + +QMark; N ; No ; F ; False +QMark; Y ; Yes ; T ; True + +# Radical (Radical) + +Radical; N ; No ; F ; False +Radical; Y ; Yes ; T ; True + +# Regional_Indicator (RI) + +RI ; N ; No ; F ; False +RI ; Y ; Yes ; T ; True + +# Script (sc) + +sc ; Adlm ; Adlam +sc ; Aghb ; Caucasian_Albanian +sc ; Ahom ; Ahom +sc ; Arab ; Arabic +sc ; Armi ; Imperial_Aramaic +sc ; Armn ; Armenian +sc ; Avst ; Avestan +sc ; Bali ; Balinese +sc ; Bamu ; Bamum +sc ; Bass ; Bassa_Vah +sc ; Batk ; Batak +sc ; Beng ; Bengali +sc ; Bhks ; Bhaiksuki +sc ; Bopo ; Bopomofo +sc ; Brah ; Brahmi +sc ; Brai ; Braille +sc ; Bugi ; Buginese +sc ; Buhd ; Buhid +sc ; Cakm ; Chakma +sc ; Cans ; Canadian_Aboriginal +sc ; Cari ; Carian +sc ; Cham ; Cham +sc ; Cher ; Cherokee +sc ; Chrs ; Chorasmian +sc ; Copt ; Coptic ; Qaac +sc ; Cpmn ; Cypro_Minoan +sc ; Cprt ; Cypriot +sc ; Cyrl ; Cyrillic +sc ; Deva ; Devanagari +sc ; Diak ; Dives_Akuru +sc ; Dogr ; Dogra +sc ; Dsrt ; Deseret +sc ; Dupl ; Duployan +sc ; Egyp ; Egyptian_Hieroglyphs +sc ; Elba ; Elbasan +sc ; Elym ; Elymaic +sc ; Ethi ; Ethiopic +sc ; Gara ; Garay +sc ; Geor ; Georgian +sc ; Glag ; Glagolitic +sc ; Gong ; Gunjala_Gondi +sc ; Gonm ; Masaram_Gondi +sc ; Goth ; Gothic +sc ; Gran ; Grantha +sc ; Grek ; Greek +sc ; Gujr ; Gujarati +sc ; Gukh ; Gurung_Khema +sc ; Guru ; Gurmukhi +sc ; Hang ; Hangul +sc ; Hani ; Han +sc ; Hano ; Hanunoo +sc ; Hatr ; Hatran +sc ; Hebr ; Hebrew +sc ; Hira ; Hiragana +sc ; Hluw ; Anatolian_Hieroglyphs +sc ; Hmng ; Pahawh_Hmong +sc ; Hmnp ; Nyiakeng_Puachue_Hmong +sc ; Hrkt ; Katakana_Or_Hiragana +sc ; Hung ; Old_Hungarian +sc ; Ital ; Old_Italic +sc ; Java ; Javanese +sc ; Kali ; Kayah_Li +sc ; Kana ; Katakana +sc ; Kawi ; Kawi +sc ; Khar ; Kharoshthi +sc ; Khmr ; Khmer +sc ; Khoj ; Khojki +sc ; Kits ; Khitan_Small_Script +sc ; Knda ; Kannada +sc ; Krai ; Kirat_Rai +sc ; Kthi ; Kaithi +sc ; Lana ; Tai_Tham +sc ; Laoo ; Lao +sc ; Latn ; Latin +sc ; Lepc ; Lepcha +sc ; Limb ; Limbu +sc ; Lina ; Linear_A +sc ; Linb ; Linear_B +sc ; Lisu ; Lisu +sc ; Lyci ; Lycian +sc ; Lydi ; Lydian +sc ; Mahj ; Mahajani +sc ; Maka ; Makasar +sc ; Mand ; Mandaic +sc ; Mani ; Manichaean +sc ; Marc ; Marchen +sc ; Medf ; Medefaidrin +sc ; Mend ; Mende_Kikakui +sc ; Merc ; Meroitic_Cursive +sc ; Mero ; Meroitic_Hieroglyphs +sc ; Mlym ; Malayalam +sc ; Modi ; Modi +sc ; Mong ; Mongolian +sc ; Mroo ; Mro +sc ; Mtei ; Meetei_Mayek +sc ; Mult ; Multani +sc ; Mymr ; Myanmar +sc ; Nagm ; Nag_Mundari +sc ; Nand ; Nandinagari +sc ; Narb ; Old_North_Arabian +sc ; Nbat ; Nabataean +sc ; Newa ; Newa +sc ; Nkoo ; Nko +sc ; Nshu ; Nushu +sc ; Ogam ; Ogham +sc ; Olck ; Ol_Chiki +sc ; Onao ; Ol_Onal +sc ; Orkh ; Old_Turkic +sc ; Orya ; Oriya +sc ; Osge ; Osage +sc ; Osma ; Osmanya +sc ; Ougr ; Old_Uyghur +sc ; Palm ; Palmyrene +sc ; Pauc ; Pau_Cin_Hau +sc ; Perm ; Old_Permic +sc ; Phag ; Phags_Pa +sc ; Phli ; Inscriptional_Pahlavi +sc ; Phlp ; Psalter_Pahlavi +sc ; Phnx ; Phoenician +sc ; Plrd ; Miao +sc ; Prti ; Inscriptional_Parthian +sc ; Rjng ; Rejang +sc ; Rohg ; Hanifi_Rohingya +sc ; Runr ; Runic +sc ; Samr ; Samaritan +sc ; Sarb ; Old_South_Arabian +sc ; Saur ; Saurashtra +sc ; Sgnw ; SignWriting +sc ; Shaw ; Shavian +sc ; Shrd ; Sharada +sc ; Sidd ; Siddham +sc ; Sind ; Khudawadi +sc ; Sinh ; Sinhala +sc ; Sogd ; Sogdian +sc ; Sogo ; Old_Sogdian +sc ; Sora ; Sora_Sompeng +sc ; Soyo ; Soyombo +sc ; Sund ; Sundanese +sc ; Sunu ; Sunuwar +sc ; Sylo ; Syloti_Nagri +sc ; Syrc ; Syriac +sc ; Tagb ; Tagbanwa +sc ; Takr ; Takri +sc ; Tale ; Tai_Le +sc ; Talu ; New_Tai_Lue +sc ; Taml ; Tamil +sc ; Tang ; Tangut +sc ; Tavt ; Tai_Viet +sc ; Telu ; Telugu +sc ; Tfng ; Tifinagh +sc ; Tglg ; Tagalog +sc ; Thaa ; Thaana +sc ; Thai ; Thai +sc ; Tibt ; Tibetan +sc ; Tirh ; Tirhuta +sc ; Tnsa ; Tangsa +sc ; Todr ; Todhri +sc ; Toto ; Toto +sc ; Tutg ; Tulu_Tigalari +sc ; Ugar ; Ugaritic +sc ; Vaii ; Vai +sc ; Vith ; Vithkuqi +sc ; Wara ; Warang_Citi +sc ; Wcho ; Wancho +sc ; Xpeo ; Old_Persian +sc ; Xsux ; Cuneiform +sc ; Yezi ; Yezidi +sc ; Yiii ; Yi +sc ; Zanb ; Zanabazar_Square +sc ; Zinh ; Inherited ; Qaai +sc ; Zyyy ; Common +sc ; Zzzz ; Unknown + +# Script_Extensions (scx) + + +# Sentence_Break (SB) + +SB ; AT ; ATerm +SB ; CL ; Close +SB ; CR ; CR +SB ; EX ; Extend +SB ; FO ; Format +SB ; LE ; OLetter +SB ; LF ; LF +SB ; LO ; Lower +SB ; NU ; Numeric +SB ; SC ; SContinue +SB ; SE ; Sep +SB ; SP ; Sp +SB ; ST ; STerm +SB ; UP ; Upper +SB ; XX ; Other + +# Sentence_Terminal (STerm) + +STerm; N ; No ; F ; False +STerm; Y ; Yes ; T ; True + +# Simple_Case_Folding (scf) + +# @missing: 0000..10FFFF; Simple_Case_Folding; + +# Simple_Lowercase_Mapping (slc) + +# @missing: 0000..10FFFF; Simple_Lowercase_Mapping; + +# Simple_Titlecase_Mapping (stc) + +# @missing: 0000..10FFFF; Simple_Titlecase_Mapping; + +# Simple_Uppercase_Mapping (suc) + +# @missing: 0000..10FFFF; Simple_Uppercase_Mapping; + +# Soft_Dotted (SD) + +SD ; N ; No ; F ; False +SD ; Y ; Yes ; T ; True + +# Terminal_Punctuation (Term) + +Term; N ; No ; F ; False +Term; Y ; Yes ; T ; True + +# Titlecase_Mapping (tc) + +# @missing: 0000..10FFFF; Titlecase_Mapping; + +# Unicode_1_Name (na1) + +# @missing: 0000..10FFFF; Unicode_1_Name; + +# Unified_Ideograph (UIdeo) + +UIdeo; N ; No ; F ; False +UIdeo; Y ; Yes ; T ; True + +# Uppercase (Upper) + +Upper; N ; No ; F ; False +Upper; Y ; Yes ; T ; True + +# Uppercase_Mapping (uc) + +# @missing: 0000..10FFFF; Uppercase_Mapping; + +# Variation_Selector (VS) + +VS ; N ; No ; F ; False +VS ; Y ; Yes ; T ; True + +# Vertical_Orientation (vo) + +vo ; R ; Rotated +vo ; Tr ; Transformed_Rotated +vo ; Tu ; Transformed_Upright +vo ; U ; Upright + +# White_Space (WSpace) + +WSpace; N ; No ; F ; False +WSpace; Y ; Yes ; T ; True + +# Word_Break (WB) + +WB ; CR ; CR +WB ; DQ ; Double_Quote +WB ; EB ; E_Base +WB ; EBG ; E_Base_GAZ +WB ; EM ; E_Modifier +WB ; EX ; ExtendNumLet +WB ; Extend ; Extend +WB ; FO ; Format +WB ; GAZ ; Glue_After_Zwj +WB ; HL ; Hebrew_Letter +WB ; KA ; Katakana +WB ; LE ; ALetter +WB ; LF ; LF +WB ; MB ; MidNumLet +WB ; ML ; MidLetter +WB ; MN ; MidNum +WB ; NL ; Newline +WB ; NU ; Numeric +WB ; RI ; Regional_Indicator +WB ; SQ ; Single_Quote +WB ; WSegSpace ; WSegSpace +WB ; XX ; Other +WB ; ZWJ ; ZWJ + +# XID_Continue (XIDC) + +XIDC; N ; No ; F ; False +XIDC; Y ; Yes ; T ; True + +# XID_Start (XIDS) + +XIDS; N ; No ; F ; False +XIDS; Y ; Yes ; T ; True + +# cjkAccountingNumeric (cjkAccountingNumeric) + +# @missing: 0000..10FFFF; cjkAccountingNumeric; NaN + +# cjkCompatibilityVariant (cjkCompatibilityVariant) + +# @missing: 0000..10FFFF; cjkCompatibilityVariant; + +# cjkIICore (cjkIICore) + +# @missing: 0000..10FFFF; cjkIICore; + +# cjkIRG_GSource (cjkIRG_GSource) + +# @missing: 0000..10FFFF; cjkIRG_GSource; + +# cjkIRG_HSource (cjkIRG_HSource) + +# @missing: 0000..10FFFF; cjkIRG_HSource; + +# cjkIRG_JSource (cjkIRG_JSource) + +# @missing: 0000..10FFFF; cjkIRG_JSource; + +# cjkIRG_KPSource (cjkIRG_KPSource) + +# @missing: 0000..10FFFF; cjkIRG_KPSource; + +# cjkIRG_KSource (cjkIRG_KSource) + +# @missing: 0000..10FFFF; cjkIRG_KSource; + +# cjkIRG_MSource (cjkIRG_MSource) + +# @missing: 0000..10FFFF; cjkIRG_MSource; + +# cjkIRG_SSource (cjkIRG_SSource) + +# @missing: 0000..10FFFF; cjkIRG_SSource; + +# cjkIRG_TSource (cjkIRG_TSource) + +# @missing: 0000..10FFFF; cjkIRG_TSource; + +# cjkIRG_UKSource (cjkIRG_UKSource) + +# @missing: 0000..10FFFF; cjkIRG_UKSource; + +# cjkIRG_USource (cjkIRG_USource) + +# @missing: 0000..10FFFF; cjkIRG_USource; + +# cjkIRG_VSource (cjkIRG_VSource) + +# @missing: 0000..10FFFF; cjkIRG_VSource; + +# cjkOtherNumeric (cjkOtherNumeric) + +# @missing: 0000..10FFFF; cjkOtherNumeric; NaN + +# cjkPrimaryNumeric (cjkPrimaryNumeric) + +# @missing: 0000..10FFFF; cjkPrimaryNumeric; NaN + +# cjkRSUnicode (cjkRSUnicode) + +# @missing: 0000..10FFFF; cjkRSUnicode; + +# kEH_Cat (kEH_Cat) + +# @missing: 0000..10FFFF; kEH_Cat; + +# kEH_Desc (kEH_Desc) + +# @missing: 0000..10FFFF; kEH_Desc; + +# kEH_HG (kEH_HG) + +# @missing: 0000..10FFFF; kEH_HG; + +# kEH_IFAO (kEH_IFAO) + +# @missing: 0000..10FFFF; kEH_IFAO; + +# kEH_JSesh (kEH_JSesh) + +# @missing: 0000..10FFFF; kEH_JSesh; + +# kEH_NoMirror (kEH_NoMirror) + +kEH_NoMirror; N ; No ; F ; False +kEH_NoMirror; Y ; Yes ; T ; True + +# kEH_NoRotate (kEH_NoRotate) + +kEH_NoRotate; N ; No ; F ; False +kEH_NoRotate; Y ; Yes ; T ; True + +# EOF diff --git a/scripts/gen_regex_sets.mts b/scripts/gen_regex_sets.mts new file mode 100644 index 0000000..2920a77 --- /dev/null +++ b/scripts/gen_regex_sets.mts @@ -0,0 +1,136 @@ +import { + opendir, readFile, stat, writeFile, +} from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; +import * as path from 'path'; + +const nodeModules = path.resolve(path.resolve(import.meta.dirname, '..'), 'node_modules'); +const unicodeDir = path.resolve(nodeModules, '@unicode', 'unicode-16.0.0'); + +async function writeUnicodePropertyMapping() { + async function* scan(d: string): AsyncGenerator { + for await (const dirent of await opendir(d)) { + if (dirent.isDirectory()) { + const p = path.join(d, dirent.name); + const test = path.join(p, 'code-points.js'); + try { + await stat(test); + yield p; + } catch { + yield* scan(p); + } + } + } + } + + type Range = readonly [from: number, to: number] + + const data: Record = {}; + + for await (const item of scan(unicodeDir)) { + const category = path.relative(unicodeDir, item).replace(/\\/g, '/'); + const { default: cps } = await import(`@unicode/unicode-16.0.0/${category}/code-points.js`); + if (!Array.isArray(cps)) { + continue; + } + if (!category.startsWith('General_Category/') && !category.startsWith('Script/') && !category.startsWith('Script_Extensions/') && !category.startsWith('Binary_Property/')) { + continue; + } + const ranges: Range[] = []; + let from = 0; + let to = 0; + cps.forEach((cp, i) => { + if (i === 0) { + from = cp; + to = cp; + } else { + if (to + 1 === cp) { + to += 1; + } else { + ranges.push([from, to]); + from = cp; + to = cp; + } + } + }); + ranges.push([from, to]); + data[category] = ranges; + } + await writeFile(path.resolve(path.resolve(import.meta.dirname, '..'), 'src/unicode/CodePointProperties.json'), JSON.stringify(data)); +} + +async function writeUnicodeStringsMapping() { + const data: Record = {}; + for (const cat of [ + 'Basic_Emoji', + 'Emoji_Keycap_Sequence', + 'RGI_Emoji_Modifier_Sequence', + 'RGI_Emoji_Flag_Sequence', + 'RGI_Emoji_Tag_Sequence', + 'RGI_Emoji_ZWJ_Sequence', + 'RGI_Emoji', + ]) { + const file = path.resolve(unicodeDir, 'Sequence_Property', cat, 'index.js'); + // eslint-disable-next-line no-await-in-loop + const { default: strings } = await import(pathToFileURL(file).href); + data[cat] = strings.join(','); + } + await writeFile(path.resolve(path.resolve(import.meta.dirname, '..'), 'src', 'unicode/SequenceProperties.json'), JSON.stringify(data)); +} + +async function writeUnicodePropertyAliasMapping() { + const file = readFile(new URL('./Unicode/PropertyValueAliases.txt', import.meta.url), 'utf-8'); + const lines = (await file).split('\n').filter((line) => line.length > 0 && !line.startsWith('#')); + + const gc: Record = {}; + const sc: Record = {}; + const scx: Record = {}; + // gc ; M ; Mark ; Combining_Mark + // where Mark is the official name, I guess? + for (const line of lines) { + const [cat, alias, formalName, ...moreAlias] = line + .split('#')[0] + .split(';') + .map((s) => s.trim()); + if (cat === 'gc') { + gc[alias] = formalName; + gc[formalName] = formalName; + moreAlias.forEach((name) => { + gc[name] = formalName; + }); + } else if (cat === 'sc') { + sc[alias] = formalName; + sc[formalName] = formalName; + moreAlias.forEach((name) => { + sc[name] = formalName; + }); + } else if (cat === 'scx') { + scx[alias] = formalName; + scx[formalName] = formalName; + moreAlias.forEach((name) => { + scx[name] = formalName; + }); + } + } + await writeFile( + new URL('../src/unicode/PropertyValueAliases.json', import.meta.url), + JSON.stringify( + { + description: + 'Unicode Property Value Aliases, generated from https://unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt', + General_Category: gc, + Script: sc, + Script_Extensions: scx, + }, + undefined, + 2, + ), + 'utf-8', + ); +} + +await Promise.all([ + writeUnicodePropertyMapping(), + writeUnicodeStringsMapping(), + writeUnicodePropertyAliasMapping(), +]); diff --git a/scripts/generate_error_message_hint.mts b/scripts/generate_error_message_hint.mts new file mode 100644 index 0000000..a584005 --- /dev/null +++ b/scripts/generate_error_message_hint.mts @@ -0,0 +1,103 @@ +/* eslint-disable no-console */ +import { opendir, readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { + createSourceFile, isCallExpression, isIdentifier, isPropertyAccessExpression, isStringLiteral, ScriptTarget, +} from 'typescript'; + +async function* readdir(dir: string): AsyncGenerator { + for await (const dirent of await opendir(dir)) { + const p = join(dir, dirent.name); + if (dirent.isDirectory()) { + yield* readdir(p); + } else { + yield p; + } + } +} + +const list = ['EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'Error', 'AggregateError']; +const messages = new Set(); +const promises: Promise[] = []; +for await (const filePath of readdir(join(import.meta.dirname, '../src/'))) { + if (!filePath.endsWith('.mts')) { + continue; + } + promises.push(readFile(filePath, 'utf8').then((content) => { + const sourceFile = createSourceFile(filePath, content, { + languageVersion: ScriptTarget.ESNext, + }); + sourceFile.forEachChild(function visitor(node) { + if ( + isCallExpression(node) + && isPropertyAccessExpression(node.expression) + && isIdentifier(node.expression.expression) + && node.expression.expression.escapedText === 'Throw' + && isIdentifier(node.expression.name) + && list.includes(node.expression.name.escapedText as string) + && node.arguments.length >= 1 + ) { + if (!isStringLiteral(node.arguments[0])) { + console.warn(`Non-literal error message in ${filePath}`); + } else { + messages.add(node.arguments[0].text); + } + } + node.forEachChild(visitor); + }); + })); +} + +await Promise.all(promises); + +const sortedMessages = Array.from(messages).sort(); + +const old = await readFile(join(import.meta.dirname, '../src/host-defined/error-messages.mts'), 'utf8'); + +const autoGenStart = '// auto-generate start'; +const autoGenEnd = '// auto-generate end'; + +const beforeAutoGen = old.slice(0, old.indexOf(autoGenStart) + autoGenStart.length); +const afterAutoGen = old.slice(old.indexOf(autoGenEnd)); + +const messagesByParameterCount: string[][] = []; +sortedMessages.forEach((m) => { + // const paramCount = (m.match(/\$\d+/g) || []).length; + // const params = Array.from({ length: paramCount }, (_, i) => `$${i + 1}: Formattable`).join(', '); + // return ` (m: '${m}'${params ? `, ${params}` : ''}): ThrowCompletion;`; + if (m.includes('$3')) { + messagesByParameterCount[3] ??= []; + messagesByParameterCount[3].push(m); + } else if (m.includes('$2')) { + messagesByParameterCount[2] ??= []; + messagesByParameterCount[2].push(m); + } else if (m.includes('$1')) { + messagesByParameterCount[1] ??= []; + messagesByParameterCount[1].push(m); + } else { + messagesByParameterCount[0] ??= []; + messagesByParameterCount[0].push(m); + } +}); + +const generatedLines: string[] = []; +messagesByParameterCount.forEach((group, index) => { + const args: string[] = [group.sort().map((m) => (m.includes("'") ? `"${m}"` : `'${m}'`)).join('\n | '), ...Array(index).fill('Formattable').map((t, i) => `$${i + 1}: ${t}`)]; + args[0] += '\n '; + generatedLines.push(` (m:\n${args.join(', ')}): ThrowCompletion;`); +}); +const generated = generatedLines.join('\n'); + +const newFileContent = `${beforeAutoGen} +${generated} + ${afterAutoGen}`; + +if (newFileContent !== old) { + console.log('Updating error-messages.mts'); + await writeFile( + join(import.meta.dirname, '../src/host-defined/error-messages.mts'), + newFileContent, + ); +} else { + console.log('error-messages.mts is up to date'); +} diff --git a/scripts/rollup.config.mts b/scripts/rollup.config.mts new file mode 100644 index 0000000..027db6c --- /dev/null +++ b/scripts/rollup.config.mts @@ -0,0 +1,177 @@ +import { readFileSync } from 'node:fs'; +import { execSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { babel, type RollupBabelInputPluginOptions } from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import { defineConfig, type Plugin } from 'rollup'; +import packageJson from '../package.json' with { type: 'json' }; + +const commitHash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + +const banner = `/*! + * engine262 ${packageJson.version} ${commitHash} + * + * ${readFileSync('./LICENSE', 'utf8').trim().split('\n').join('\n * ')} + */ +`; + +const babelOptions: RollupBabelInputPluginOptions = { + babelHelpers: 'bundled', + exclude: 'node_modules/**', + generatorOpts: { + importAttributesKeyword: 'with', + }, + presets: [[ + '@babel/preset-env', + { + // this includes at least 1 LTS for Node.js + targets: ['last 2 node versions'], + spec: true, + bugfixes: true, + }, + ], [ + '@babel/preset-typescript', + { + allowDeclareFields: true, + }, + ]], + extensions: ['.mts'], +}; + +export default defineConfig([ + { + input: 'lib-src/inspector/index.mts', + plugins: [ + babel(babelOptions), + { + name: 'resolve-self', + resolveId(source, _importer, _options) { + if (source === '#self') { + return { id: './engine262.mjs' }; + } + return undefined; + }, + }, + { + name: 'dts', + buildStart() { + this.emitFile({ + type: 'asset', + fileName: 'inspector.d.ts', + source: 'export * from "../lib/inspector/index.d.mts";', + }); + this.emitFile({ + type: 'asset', + fileName: 'inspector.d.mts', + source: 'export * from "../lib/inspector/index.d.mts";', + }); + }, + }, + ], + external: ['./engine262.mjs'], + output: [ + { + file: 'lib/inspector.js', + format: 'umd', + sourcemap: true, + name: `${packageJson.name}/inspector`, + banner, + globals: { './engine262.mjs': '@engine262/engine262' }, + }, + { + file: 'lib/inspector.mjs', + format: 'es', + sourcemap: true, + banner, + }, + ], + }, + { + input: './src/index.mts', + plugins: [ + importUnicodeLib(), + (json.default || json)({ compact: true }), + (commonjs.default || commonjs)(), + nodeResolve({ exportConditions: ['rollup'], extensions: ['.mts'] }), + babel({ + ...babelOptions, + plugins: [ + './scripts/transform.mts', + ['@babel/plugin-proposal-decorators', { + 'version': '2023-11', + }], + ], + }), + { + name: 'dts', + buildStart() { + this.emitFile({ + type: 'asset', + fileName: 'engine262.d.ts', + source: 'export * from "../declaration/index.d.mjs";', + }); + this.emitFile({ + type: 'asset', + fileName: 'engine262.d.mts', + source: 'export * from "../declaration/index.d.mjs";', + }); + }, + }, + ], + output: [ + { + file: 'lib/engine262.js', + format: 'umd', + sourcemap: true, + name: packageJson.name, + banner, + }, + { + file: 'lib/engine262.mjs', + format: 'es', + sourcemap: true, + banner, + }, + ], + onwarn(warning, warn) { + if (warning.code === 'CIRCULAR_DEPENDENCY' || warning.code === 'SOURCEMAP_BROKEN') { + // Squelch. + return; + } + process.exitCode = 1; + warn(warning); + }, + }]); + +/** + * Special handle of the following modules so we don't need to import the whole zlib polyfill. + */ +function importUnicodeLib(): Plugin { + const canImport = ['@unicode/unicode-16.0.0/Case_Folding/C/symbols.js', '@unicode/unicode-16.0.0/Case_Folding/S/symbols.js']; + return { + name: '@unicode lib import', + async transform(code, id) { + if (!id.includes('node_modules/@unicode')) { + return { code, map: this.getCombinedSourcemap() }; + } + if (canImport.some((i) => id.endsWith(i))) { + const module = createRequire(import.meta.url)(id) as Map; + const codePointsInArray = Array.from(module.entries()).map(([str, str2]) => { + const it1 = str[Symbol.iterator](); + const it2 = str2[Symbol.iterator](); + it1.next(); + it2.next(); + if (!it1.next().done || !it2.next().done) { + throw new Error(`TODO: handle something strange: ${str} ${str2}`); + } + return [str.codePointAt(0), str2.codePointAt(0)]; + }); + const str = JSON.stringify(JSON.stringify(codePointsInArray)); + return `export default new Map(JSON.parse(${str}).map(([cp1, cp2]) => [String.fromCodePoint(cp1), String.fromCodePoint(cp2)]));`; + } + return code; + }, + }; +} diff --git a/scripts/tag_version_with_git_hash.mts b/scripts/tag_version_with_git_hash.mts new file mode 100644 index 0000000..aec202e --- /dev/null +++ b/scripts/tag_version_with_git_hash.mts @@ -0,0 +1,17 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; +import json from '../package.json' with { type: 'json' }; + +const jsonPath = new URL('../package.json', import.meta.url); + +process.stdout.write('Checking package.json for git revision...\n'); + +if (!json.version.includes('-')) { + process.stdout.write('Inserting git revision into package.json...\n'); + + const hash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + json.version = `${json.version}-${hash}`; + fs.writeFileSync(jsonPath, `${JSON.stringify(json, null, 2)}\n`); +} + +process.stdout.write('Done!\n'); diff --git a/scripts/transform.mts b/scripts/transform.mts new file mode 100644 index 0000000..1a10371 --- /dev/null +++ b/scripts/transform.mts @@ -0,0 +1,456 @@ +import { + type NodePath, + traverse, + type Node, + type PluginObj, type PluginPass, + type types as t, +} from '@babel/core'; +import type { PublicReplacements } from '@babel/template'; + +function __ts_cast__(_value: unknown): asserts _value is T { } + +function findParentStatementPath(path: NodePath): NodePath | null { + while (path && !path.isStatement()) { + path = path.parentPath!; + } + return path; +} + +function getEnclosingConditionalExpression(path: NodePath) { + while (path && !path.isStatement()) { + if (path.isConditionalExpression()) { + return path; + } + path = path.parentPath!; + } + return null; +} + +type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | 'skipDebugger'; + +interface State extends PluginPass { + needed: Partial>; +} + +interface Macro> { + template(sourceLocation: Node, replacements: Readonly): t.Statement | t.Statement[]; + readonly imports: readonly NeededNames[]; + readonly allowAnyExpression?: boolean; +} + +interface Macros { + [m: string]: Macro; + Q: Macro<{ value: t.Identifier, checkYieldStar: t.Statement | null }>; + X: Macro<{ value: t.Identifier, checkYieldStar: t.Statement | null, source: t.StringLiteral }>; + ReturnIfAbrupt: Macro<{ value: t.Identifier, checkYieldStar: t.Statement | null }>; + IfAbruptCloseIterator: Macro<{ value: t.Identifier, iteratorRecord: t.Identifier }>; + IfAbruptCloseAsyncIterator: Macro<{ value: t.Identifier, iteratorRecord: t.Identifier }>; + IfAbruptRejectPromise: Macro<{ value: t.Identifier, capability: t.Identifier }>; +} + +export default ({ types: t, template }: typeof import('@babel/core')): PluginObj => { + const parseOptions = { preserveComments: true }; + function createImportCompletion() { + return template.ast(` + import { Completion } from "#self"; + `); + } + + function createImportSkipDebugger() { + return template.ast(` + import { skipDebugger } from "#self"; + `); + } + + function createImportAbruptCompletion() { + return template.ast(` + import { AbruptCompletion } from "#self"; + `); + } + + function createImportAssert() { + return template.ast(` + import { Assert } from "#self"; + `); + } + + function createImportCall() { + return template.ast(` + import { Call } from "#self"; + `); + } + + function createImportIteratorClose() { + return template.statement.ast` + import { IteratorClose } from "#self"; + `; + } + + function createImportAsyncIteratorClose() { + return template.statement.ast` + import { AsyncIteratorClose } from "#self"; + `; + } + + function createImportValue() { + return template.ast(` + import { Value } from "#self"; + `); + } + + function addSectionFromComments(path: NodePath | NodePath | NodePath) { + if (path.node.leadingComments) { + for (const c of path.node.leadingComments) { + let name: string; + switch (path.type) { + case 'FunctionDeclaration': + name = path.node.id!.name; + break; + case 'ExportNamedDeclaration': + name = (path.node.declaration as t.FunctionDeclaration).id!.name; + break; + case 'VariableDeclaration': + name = (path.node.declarations[0].id as t.Identifier).name; + break; + default: + throw (path as NodePath).buildCodeFrameError('Internal error: Unsupported path to addSectionFromComments'); + } + 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}`; + const result = path.insertAfter(withSource(c, template.ast(`${name}.section = '${url}';`))); + if (path.node.trailingComments) { + result[result.length - 1].node.trailingComments = path.node.trailingComments; + path.node.trailingComments = null; + } + return; + } + } + } + } + } + + + const maybeSkipDebugger = (value: t.Identifier, callee: Node) => withSource(callee, template.statement(` + /* node:coverage ignore next */ if (%%value%% && typeof %%value%% === 'object' && 'next' in %%value%%) %%value%% = skipDebugger(%%value%%); + `, { preserveComments: true })({ value }))[0]; + + type NodeWithLocation = Pick; + + function setSource(source: NodeWithLocation, n: t.Node) { + if (n.loc) { + return; + } + n.start = source.start; + n.end = source.end; + n.loc = source.loc; + n.leadingComments?.forEach((comment) => { + comment.start = source.start || undefined; + comment.end = source.end || undefined; + comment.loc = source.loc || undefined; + }); + } + + function withSource(source: NodeWithLocation, node: t.Statement | t.Statement[]): t.Statement[] { + if (!Array.isArray(node)) { + node = [node]; + } + for (const n of node) { + setSource(source, n); + traverse(n, { + noScope: true, + enter(path) { + setSource(source, path.node); + }, + }); + } + return node; + } + + const MACROS: Macros = { + Q: { + template: (source, code) => withSource(source, template(` + /* ReturnIfAbrupt */ + %%checkYieldStar%% + /* node:coverage ignore next */ if (%%value%% instanceof AbruptCompletion) return %%value%%; + /* node:coverage ignore next */ if (%%value%% instanceof Completion) %%value%% = %%value%%.Value; + `, parseOptions)(code)), + imports: ['AbruptCompletion', 'Completion', 'Assert'], + allowAnyExpression: true, + }, + X: { + template: (source, code) => withSource(source, template(` + /* X */ + %%checkYieldStar%% + /* node:coverage ignore next */ if (%%value%% instanceof AbruptCompletion) throw new Assert.Error(%%source%%, { cause: %%value%% }); + /* node:coverage ignore next */ if (%%value%% instanceof Completion) %%value%% = %%value%%.Value; + `, parseOptions)(code)), + imports: ['Assert', 'Completion', 'AbruptCompletion', 'skipDebugger'], + allowAnyExpression: true, + }, + IfAbruptCloseIterator: { + template: (source, code) => withSource(source, template(` + /* IfAbruptCloseIterator */ + /* node:coverage ignore next */ + if (%%value%% instanceof AbruptCompletion) return skipDebugger(IteratorClose(%%iteratorRecord%%, %%value%%)); + /* node:coverage ignore next */ + if (%%value%% instanceof Completion) %%value%% = %%value%%.Value; + `, parseOptions)(code)), + imports: ['IteratorClose', 'AbruptCompletion', 'Completion', 'skipDebugger'], + }, + IfAbruptCloseAsyncIterator: { + template: (source, code) => withSource(source, template(` + /* IfAbruptCloseAsyncIterator */ + /* node:coverage ignore next */ + if (%%value%% instanceof AbruptCompletion) return yield* AsyncIteratorClose(%%iteratorRecord%%, %%value%%); + /* node:coverage ignore next */ + if (%%value%% instanceof Completion) %%value%% = %%value%%.Value; + `, parseOptions)(code)), + imports: ['Assert', 'AsyncIteratorClose', 'AbruptCompletion', 'Completion', 'skipDebugger'], + }, + IfAbruptRejectPromise: { + template: (source, code) => withSource(source, template(` + /* IfAbruptRejectPromise */ + /* node:coverage disable */ + if (%%value%% instanceof AbruptCompletion) { + const callRejectCompletion = skipDebugger(Call(%%capability%%.Reject, Value.undefined, [%%value%%.Value])); + if (callRejectCompletion instanceof AbruptCompletion) return callRejectCompletion; + return %%capability%%.Promise; + } + if (%%value%% instanceof Completion) %%value%% = %%value%%.Value; + /* node:coverage enable */ + `, parseOptions)(code)), + imports: ['Call', 'Value', 'AbruptCompletion', 'Completion', 'skipDebugger'], + }, + ReturnIfAbrupt: null!, + }; + __ts_cast__(MACROS); + MACROS.ReturnIfAbrupt = MACROS.Q; + const MACRO_NAMES = Object.keys(MACROS); + + // For frequently used Record-like classes, inline them to get a better debug experience. + const Completions = { + NormalCompletion: (source: Node, code: PublicReplacements) => withSource(source, template('({ __proto__: NormalCompletion.prototype, Value: %%value%% })', parseOptions)(code))[0], + ThrowCompletion: (source: Node, code: PublicReplacements) => withSource(source, template('({ __proto__: ThrowCompletion.prototype, Value: %%value%% })', parseOptions)(code))[0], + }; + const Structs = [ + 'AsyncGeneratorRequestRecord', + 'ClassElementDefinitionRecord', + 'ClassFieldDefinitionRecord', + 'ClassStaticBlockDefinitionRecord', + 'PrivateElementRecord', + ]; + + function tryRemove(path: NodePath) { + try { + path.remove(); + } catch (e) { + throw path.get('arguments.0').buildCodeFrameError(`Macros error: ${(e as Error).message}`); + } + } + + return { + visitor: { + Program: { + enter(_path, state) { + state.needed = {}; + }, + exit(path, state) { + if (state.needed.skipDebugger) { + path.unshiftContainer('body', createImportSkipDebugger()); + } + if (state.needed.Completion) { + path.unshiftContainer('body', createImportCompletion()); + } + if (state.needed.AbruptCompletion) { + path.unshiftContainer('body', createImportAbruptCompletion()); + } + if (state.needed.Assert) { + path.unshiftContainer('body', createImportAssert()); + } + if (state.needed.Call) { + path.unshiftContainer('body', createImportCall()); + } + if (state.needed.IteratorClose) { + path.unshiftContainer('body', createImportIteratorClose()); + } + if (state.needed.AsyncIteratorClose) { + path.unshiftContainer('body', createImportAsyncIteratorClose()); + } + if (state.needed.Value) { + path.unshiftContainer('body', createImportValue()); + } + }, + }, + CallExpression(path, state) { + const callee = path.node.callee; + if (!t.isIdentifier(callee)) { + return; + } + + if (callee.name && callee.name in Completions) { + const template = Completions[callee.name as keyof typeof Completions]; + path.replaceWith(template(callee, { value: path.node.arguments[0] })); + return; + } + + if (Structs.includes(callee.name) && path.node.arguments.length === 1) { + const arg0 = path.node.arguments[0]; + if (t.isObjectExpression(arg0)) { + path.replaceWith(t.objectExpression([ + t.objectProperty(t.identifier('__proto__'), t.memberExpression(t.identifier(callee.name), t.identifier('prototype'))), + ...arg0.properties, + ])); + return; + } + } + + const macroName = 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 && (path.parentPath.isReturnStatement() || 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); + if (!statementPath) { + throw path.buildCodeFrameError('Internal error: no parent statement found'); + } + + 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 as t.VariableDeclaration).kind = 'let'; + statementPath.insertBefore(withSource(callee, template(` + /* ReturnIfAbrupt */ + /* node:coverage ignore next */ if (%%value%% && typeof %%value%% === 'object' && 'next' in %%value%%) throw new Assert.Error('Forgot to yield* on the completion.'); + /* node:coverage ignore next */ if (%%value%% instanceof AbruptCompletion) return %%value%%; + /* node:coverage ignore next */ if (%%value%% instanceof Completion) %%value%% = %%value%%.Value; + `, parseOptions)({ value: 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 as t.VariableDeclaration).kind = 'let'; + statementPath.insertBefore(macro.template(callee, { value: argument, capability })); + tryRemove(path); + } else if (macro === MACROS.IfAbruptCloseIterator || macro === MACROS.IfAbruptCloseAsyncIterator) { + if (!t.isIdentifier(argument)) { + throw path.get('arguments.0').buildCodeFrameError('First argument to IfAbruptCloseIterator should be an identifier'); + } + const iteratorRecord = path.get('arguments.1'); + if (!iteratorRecord.isIdentifier()) { + throw iteratorRecord.buildCodeFrameError('Second argument to IfAbruptCloseIterator should be an identifier'); + } + const binding = path.scope.getBinding(argument.name)!; + (binding.path.parent as t.VariableDeclaration).kind = 'let'; + statementPath.insertBefore( + macro.template(callee, { + value: argument, + iteratorRecord: iteratorRecord.node, + }), + ); + tryRemove(path); + } else { + let id; + if (!macro.allowAnyExpression) { + if (!t.isIdentifier(argument)) { + throw path.get('arguments.0').buildCodeFrameError(`First argument to ${macroName} should be an identifier`); + } + id = argument; + } else { + id = statementPath.scope.generateUidIdentifier(); + statementPath.insertBefore(withSource(callee, template(` + /* ${macroName !== 'Q' ? macroName : 'ReturnIfAbrupt'} */ + let %%id%% = %%argument%%; + `, parseOptions)({ id, argument }))); + } + + const replacement: { value: typeof id, checkYieldStar: t.Statement | null, source?: t.StringLiteral } = { + checkYieldStar: null, + value: id, + }; + if (macro === MACROS.X) { + replacement.source = t.stringLiteral(`! ${path.get('arguments.0').getSource()} returned an abrupt completion`); + if (!t.isYieldExpression(argument, { delegate: true })) { + replacement.checkYieldStar = maybeSkipDebugger(id, callee); + } + } + statementPath.insertBefore(macro.template(callee, replacement)); + path.replaceWith(id); + } + } + } else if (macroName === 'Assert') { + if (!path.node.arguments[1]) { + path.node.arguments.push(t.stringLiteral(path.get('arguments.0').getSource())); + } + } + }, + ThrowStatement(path) { + const arg = path.get('argument'); + if (arg.isNewExpression()) { + const callee = arg.get('callee'); + if (callee.isIdentifier() && callee.node.name === 'OutOfRange') { + path.addComment('leading', ' node:coverage ignore next ', false); + + const { parentPath } = path; + if (parentPath.isSwitchCase() && parentPath.node.consequent[0] === path.node) { + parentPath.addComment('leading', ' node:coverage ignore next ', false); + } + } + } + }, + FunctionDeclaration(path) { + addSectionFromComments(path); + }, + VariableDeclaration(path) { + if (path.get('declarations.0.init').isArrowFunctionExpression() || path.get('declarations.0.init').isFunctionExpression()) { + addSectionFromComments(path); + } + }, + ExportNamedDeclaration(path) { + if (path.get('declaration').isFunctionDeclaration()) { + addSectionFromComments(path); + } + }, + }, + }; +}; diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000..9979398 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["."], + "compilerOptions": { + "noEmit": true, + "erasableSyntaxOnly": true, + "types": ["node"] + } +} diff --git a/src/abstract-ops/all.mts b/src/abstract-ops/all.mts new file mode 100644 index 0000000..7259115 --- /dev/null +++ b/src/abstract-ops/all.mts @@ -0,0 +1,37 @@ +export * from './arguments-operations.mts'; +export * from './array-objects.mts'; +export * from './arraybuffer-objects.mts'; +export * from './async-function-operations.mts'; +export * from './async-generator-objects.mts'; +export * from './data-types-and-values.mts'; +export * from './dataview-objects.mts'; +export * from './date-objects.mts'; +export * from './error-objects.mts'; +export * from './execution-contexts.mts'; +export * from './function-operations.mts'; +export * from './generator-operations.mts'; +export * from './global-object.mts'; +export * from './immutable-prototype-objects.mts'; +export * from './import-calls.mts'; +export * from './iterator-operations.mts'; +export * from './keyed-collections.mts'; +export * from './module-namespace-exotic-objects.mts'; +export * from './module-records.mts'; +export * from './notational-conventions.mts'; +export * from './object-operations.mts'; +export * from './objects.mts'; +export * from './private-names.mts'; +export * from './promise-operations.mts'; +export * from './proxy-objects.mts'; +export * from './realms.mts'; +export * from './reference-operations.mts'; +export * from './regexp-objects.mts'; +export * from './shadow-realm.mts'; +export * from './spec-types.mts'; +export * from './string-objects.mts'; +export * from './symbol-objects.mts'; +export * from './temporal/all.mts'; +export * from './testing-comparison.mts'; +export * from './type-conversion.mts'; +export * from './typedarray-objects.mts'; +export * from './weak-operations.mts'; diff --git a/src/abstract-ops/arguments-operations.mts b/src/abstract-ops/arguments-operations.mts new file mode 100644 index 0000000..affaca3 --- /dev/null +++ b/src/abstract-ops/arguments-operations.mts @@ -0,0 +1,245 @@ +import { + Q, X, BoundNames, surroundingAgent, + JSStringSet, type Mutable, type ParseNode, + Assert, + CreateBuiltinFunction, + CreateDataProperty, + DefinePropertyOrThrow, + ToString, + SameValue, + MakeBasicObject, + OrdinaryObjectCreate, + OrdinaryGetOwnProperty, + OrdinaryDefineOwnProperty, + OrdinaryGet, + OrdinarySet, + OrdinaryDelete, + Get, + Set, + HasOwnProperty, + IsAccessorDescriptor, + IsDataDescriptor, + F, + type OrdinaryObject, + Descriptor, + JSStringValue, + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, + type ObjectInternalMethods, + EnvironmentRecord, +} from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-arguments-exotic-objects */ +export interface MappedArgumentsObject extends OrdinaryObject { + readonly ParameterMap: ObjectValue; +} +export interface UnmappedArgumentsObject extends OrdinaryObject { + readonly ParameterMap: UndefinedValue; +} + +export function isArgumentExoticObject(value: Value): value is MappedArgumentsObject | UnmappedArgumentsObject { + return 'ParameterMap' in value; +} + +const ArgumentExoticObject = { + * GetOwnProperty(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) { + return Descriptor({ ...desc, Value: Q(yield* Get(map, P)) }); + } + return desc; + }, + * DefineOwnProperty(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, Value: X(Get(map, P)) }); + } + } + const allowed = Q(yield* OrdinaryDefineOwnProperty(args, P, newArgDesc)); + if (allowed === Value.false) { + return Value.false; + } + if (isMapped === Value.true) { + if (IsAccessorDescriptor(Desc) === true) { + yield* map.Delete(P); + } else { + if (Desc.Value !== undefined) { + const setStatus = yield* Set(map, P, Desc.Value, Value.false); + Assert(setStatus === Value.true); + } + if (Desc.Writable !== undefined && Desc.Writable === Value.false) { + yield* map.Delete(P); + } + } + } + return Value.true; + }, + * Get(P, Receiver) { + const args = this; + const map = args.ParameterMap; + const isMapped = X(HasOwnProperty(map, P)); + if (isMapped === Value.false) { + return Q(yield* OrdinaryGet(args, P, Receiver)); + } else { + return yield* Get(map, P); + } + }, + * Set(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 = yield* Set(map!, P, V, Value.false); + Assert(setStatus === Value.true); + } + return Q(yield* OrdinarySet(args, P, V, Receiver)); + }, + * Delete(P) { + const args = this; + const map = args.ParameterMap; + const isMapped = X(HasOwnProperty(map, P)); + const result = Q(yield* OrdinaryDelete(args, P)); + if (result === Value.true && isMapped === Value.true) { + yield* map.Delete(P); + } + return result; + }, +} satisfies Partial>; + +/** https://tc39.es/ecma262/#sec-createunmappedargumentsobject */ +export function CreateUnmappedArgumentsObject(argumentsList: Arguments) { + const len = argumentsList.length; + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'), ['ParameterMap']) as Mutable; + obj.ParameterMap = Value.undefined; + X(DefinePropertyOrThrow(obj, Value('length'), Descriptor({ + Value: F(len), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + let index = 0; + while (index < len) { + const val = argumentsList[index]; + X(CreateDataProperty(obj, X(ToString(F(index))), 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, Value('callee'), Descriptor({ + Get: surroundingAgent.intrinsic('%ThrowTypeError%'), + Set: surroundingAgent.intrinsic('%ThrowTypeError%'), + Enumerable: Value.false, + Configurable: Value.false, + }))); + return obj; +} + +/** https://tc39.es/ecma262/#sec-makearggetter */ +function MakeArgGetter(name: JSStringValue, env: EnvironmentRecord) { + // 1. Let getterClosure be a new Abstract Closure with no parameters that captures name and env and performs the following steps when called: + // a. Return env.GetBindingValue(name, false). + const getterClosure = () => env.GetBindingValue(name, Value.false); + // 2. Let getter be ! CreateBuiltinFunction(getterClosure, 0, "", « »). + const getter = X(CreateBuiltinFunction(getterClosure, 0, Value(''), ['Name', 'Env'])); + // 3. NOTE: getter is never directly accessible to ECMAScript code. + // 4. Return getter. + return getter; +} + +/** https://tc39.es/ecma262/#sec-makeargsetter */ +function MakeArgSetter(name: JSStringValue, env: EnvironmentRecord) { + // 1. Let setterClosure be a new Abstract Closure with parameters (value) that captures name and env and performs the following steps when called: + // a. Return env.SetMutableBinding(name, value, false). + const setterClosure = ([value = Value.undefined]: Arguments) => env.SetMutableBinding(name, value, Value.false); + // 2. Let setter be ! CreateBuiltinFunction(setterClosure, 1, "", « »). + const setter = X(CreateBuiltinFunction(setterClosure, 1, Value(''), ['Name', 'Env'])); + // 3. NOTE: setter is never directly accessible to ECMAScript code. + // 4. Return setter. + return setter; +} + +/** https://tc39.es/ecma262/#sec-createmappedargumentsobject */ +export function CreateMappedArgumentsObject(func: ObjectValue, formals: ParseNode.FormalParameters, argumentsList: Arguments, env: EnvironmentRecord) { + // 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 = ArgumentExoticObject.GetOwnProperty; + obj.DefineOwnProperty = ArgumentExoticObject.DefineOwnProperty; + obj.Get = ArgumentExoticObject.Get; + obj.Set = ArgumentExoticObject.Set; + obj.Delete = ArgumentExoticObject.Delete; + 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]!; + X(CreateDataProperty(obj, X(ToString(F(index))), val)); + index += 1; + } + X(DefinePropertyOrThrow(obj, Value('length'), Descriptor({ + Value: F(len), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + const mappedNames = new JSStringSet(); + 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(F(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, Value('callee'), Descriptor({ + Value: func, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + return obj; +} diff --git a/src/abstract-ops/array-objects.mts b/src/abstract-ops/array-objects.mts new file mode 100644 index 0000000..3ac0aeb --- /dev/null +++ b/src/abstract-ops/array-objects.mts @@ -0,0 +1,315 @@ +import { + surroundingAgent, Descriptor, ObjectValue, JSStringValue, Value, wellKnownSymbols, type ObjectInternalMethods, + NumberValue, UndefinedValue, + BooleanValue, + Q, X, type ValueCompletion, type ValueEvaluator, + type Mutable, type YieldEvaluator, + AbstractRelationalComparison, + Assert, + Call, + Construct, + CreateArrayFromList, + CreateIteratorFromClosure, + Get, + GetFunctionRealm, + IsDataDescriptor, + IsArray, + IsConstructor, + OrdinaryDefineOwnProperty, + OrdinaryGetOwnProperty, + LengthOfArrayLike, + MakeBasicObject, + SameValue, + ToBoolean, + ToNumber, + ToString, + ToUint32, + IsPropertyKey, + isArrayIndex, + isNonNegativeInteger, + F, R, + type OrdinaryObject, + type FunctionObject, + type GeneratorObject, + MakeTypedArrayWithBufferWitnessRecord, + IsTypedArrayOutOfBounds, + TypedArrayLength, + CreateIteratorResultObject, + GeneratorYield, + Throw, +} from '#self'; +import { isTypedArrayObject } from '#self'; + +const InternalMethods = { + /** https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc */ + * DefineOwnProperty(P, Desc): ValueEvaluator { + const A = this; + + Assert(IsPropertyKey(P)); + if (P instanceof JSStringValue && P.stringValue() === 'length') { + return Q(yield* ArraySetLength(A, Desc)); + } else if (isArrayIndex(P)) { + let lengthDesc = OrdinaryGetOwnProperty(A, Value('length')); + Assert(!(lengthDesc instanceof UndefinedValue)); + Assert(IsDataDescriptor(lengthDesc)); + Assert(lengthDesc.Configurable === Value.false); + const length = lengthDesc.Value; + Assert(length instanceof NumberValue && isNonNegativeInteger(R(length))); + const index = X(ToUint32(P)); + if (R(index) >= R(length) && lengthDesc.Writable === Value.false) { + return Value.false; + } + let succeeded = X(OrdinaryDefineOwnProperty(A, P, Desc)); + if (succeeded === Value.false) { + return Value.false; + } + if (R(index) >= R(length)) { + lengthDesc = Descriptor({ ...lengthDesc, Value: F(R(index) + 1) }); + succeeded = X(OrdinaryDefineOwnProperty(A, Value('length'), lengthDesc)); + Assert(succeeded === Value.true); + } + return Value.true; + } + return yield* OrdinaryDefineOwnProperty(A, P, Desc); + }, +} satisfies Partial>; + +export { InternalMethods as ArrayExoticObjectInternalMethods }; + +export function isArrayExoticObject(O: Value) { + return O instanceof ObjectValue && O.DefineOwnProperty === InternalMethods.DefineOwnProperty; +} + +/** https://tc39.es/ecma262/#sec-arraycreate */ +export function ArrayCreate(length: number, proto?: ObjectValue): ValueCompletion { + Assert(isNonNegativeInteger(length)); + if (Object.is(length, -0)) { + length = +0; + } + if (length > (2 ** 32) - 1) { + return Throw.RangeError('Array length too big.'); + } + if (proto === undefined) { + proto = surroundingAgent.intrinsic('%Array.prototype%'); + } + const A = X(MakeBasicObject(['Prototype', 'Extensible'])) as Mutable; + A.Prototype = proto; + A.DefineOwnProperty = InternalMethods.DefineOwnProperty; + + X(OrdinaryDefineOwnProperty(A, Value('length'), Descriptor({ + Value: F(length), + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + return A; +} + +/** https://tc39.es/ecma262/#sec-arrayspeciescreate */ +export function* ArraySpeciesCreate(originalArray: ObjectValue, length: number): ValueEvaluator { + Assert(typeof length === 'number' && Number.isInteger(length) && length >= 0); + if (Object.is(length, -0)) { + length = +0; + } + const isArray = Q(IsArray(originalArray)); + if (isArray === Value.false) { + return Q(ArrayCreate(length)); + } + let C = Q(yield* Get(originalArray, Value('constructor'))); + if (IsConstructor(C)) { + const thisRealm = surroundingAgent.currentRealmRecord; + const realmC = Q(GetFunctionRealm(C)); + if (thisRealm !== realmC) { + if (SameValue(C, realmC.Intrinsics['%Array%']) === Value.true) { + C = Value.undefined; + } + } + } + if (C instanceof ObjectValue) { + C = Q(yield* Get(C, wellKnownSymbols.species)); + if (C === Value.null) { + C = Value.undefined; + } + } + if (C === Value.undefined) { + return Q(ArrayCreate(length)); + } + if (!IsConstructor(C)) { + return Throw.TypeError('$1 is not a constructor', C); + } + return Q(yield* Construct(C, [F(length)])); +} + +/** https://tc39.es/ecma262/#sec-arraysetlength */ +export function* ArraySetLength(A: OrdinaryObject, Desc: Descriptor): ValueEvaluator { + if (Desc.Value === undefined) { + return yield* OrdinaryDefineOwnProperty(A, Value('length'), Desc); + } + let newLenDesc = Desc; + const newLen = R(Q(yield* ToUint32(Desc.Value))); + const numberLen = R(Q(yield* ToNumber(Desc.Value))); + if (newLen !== numberLen) { + return Throw.RangeError('Array length must be uint32.'); + } + newLenDesc = Descriptor({ ...Desc, Value: F(newLen) }); + const oldLenDesc = OrdinaryGetOwnProperty(A, Value('length')); + Assert(!(oldLenDesc instanceof UndefinedValue)); + Assert(IsDataDescriptor(oldLenDesc)); + Assert(oldLenDesc.Configurable === Value.false); + const oldLen = R(oldLenDesc.Value as NumberValue); + if (newLen >= oldLen) { + return yield* OrdinaryDefineOwnProperty(A, 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 = Descriptor({ ...newLenDesc, Writable: Value.true }); + } + const succeeded = X(OrdinaryDefineOwnProperty(A, Value('length'), newLenDesc)); + if (succeeded === Value.false) { + return Value.false; + } + const keys: JSStringValue[] = []; + A.properties.forEach((_value, key) => { + if (isArrayIndex(key) && Number((key as JSStringValue).stringValue()) >= newLen) { + keys.push(key as JSStringValue); + } + }); + 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 = Descriptor({ ...newLenDesc, Value: F(R(X(ToUint32(P))) + 1) }); + if (newWritable === false) { + newLenDesc = Descriptor({ ...newLenDesc, Writable: Value.false }); + } + X(OrdinaryDefineOwnProperty(A, Value('length'), newLenDesc)); + return Value.false; + } + } + if (newWritable === false) { + const s = yield* OrdinaryDefineOwnProperty(A, Value('length'), Descriptor({ Writable: Value.false })); + Assert(s === Value.true); + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-isconcatspreadable */ +export function* IsConcatSpreadable(O: Value): ValueEvaluator { + if (!(O instanceof ObjectValue)) { + return Value.false; + } + const spreadable = Q(yield* Get(O, wellKnownSymbols.isConcatSpreadable)); + if (spreadable !== Value.undefined) { + return ToBoolean(spreadable); + } + return Q(IsArray(O)); +} + +/** https://tc39.es/ecma262/#sec-comparearrayelements */ +export function* CompareArrayElements(x: Value, y: Value, comparefn: FunctionObject | UndefinedValue): ValueEvaluator { + // 1. If x and y are both undefined, return +0𝔽. + if (x === Value.undefined && y === Value.undefined) { + return F(+0); + } + // 2. If x is undefined, return 1𝔽. + if (x === Value.undefined) { + return F(1); + } + // 3. If y is undefined, return -1𝔽. + if (y === Value.undefined) { + return F(-1); + } + // 4. If comparefn is not undefined, then + if (comparefn !== Value.undefined) { + // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). + const v = Q(yield* ToNumber(Q(yield* Call(comparefn, Value.undefined, [x, y])))); + // b. If v is NaN, return +0𝔽. + if (v.isNaN()) { + return F(+0); + } + // c. Return v. + return v; + } + // 5. Let xString be ? ToString(x). + const xString = Q(yield* ToString(x)); + // 6. Let yString be ? ToString(y). + const yString = Q(yield* ToString(y)); + // 7. Let xSmaller be the result of performing Abstract Relational Comparison xString < yString. + const xSmaller = yield* AbstractRelationalComparison(xString, yString); + // 8. If xSmaller is true, return -1𝔽. + if (xSmaller === Value.true) { + return F(-1); + } + // 9. Let ySmaller be the result of performing Abstract Relational Comparison yString < xString. + const ySmaller = yield* AbstractRelationalComparison(yString, xString); + // 10. If ySmaller is true, return 1𝔽. + if (ySmaller === Value.true) { + return F(1); + } + // 11. Return +0𝔽. + return F(+0); +} + +/** https://tc39.es/ecma262/#sec-createarrayiterator */ +export function CreateArrayIterator(array: ObjectValue, kind: 'key+value' | 'key' | 'value'): ValueCompletion { + // 3. Let closure be a new Abstract Closure with no parameters that captures kind and array and performs the following steps when called: + const closure = function* closure(): YieldEvaluator { + // a. Let index be 0. + let index = 0; + // b. Repeat, + while (true) { + let len; + let result; + // i. If array has a [[TypedArrayName]] internal slot, then + if (isTypedArrayObject(array)) { + const taRecord = MakeTypedArrayWithBufferWitnessRecord(array, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return Throw.TypeError('TypedArray out of bounds'); + } + // 2. Let len be array.[[ArrayLength]]. + len = TypedArrayLength(taRecord); + } else { // ii. Else, + // 1. Let len be ? LengthOfArrayLike(array). + len = Q(yield* LengthOfArrayLike(array)); + } + // iii. If index ≥ len, return undefined. + if (index >= len) { + // NON_SPEC + generator.HostCapturedValues = undefined; + return Value.undefined; + } + const indexNumber = F(index); + // iv. If kind is key, + if (kind === 'key') { + result = indexNumber; + } else { // v. Else, + // 1. Let elementKey be ! ToString(indexNumber). + const elementKey = X(ToString(indexNumber)); + // 2. Let elementValue be ? Get(array, elementKey). + const elementValue = Q(yield* Get(array, elementKey)); + // 3. If kind is value, perform ? Yield(elementValue). + if (kind === 'value') { + result = elementValue; + } else { // 4. Else, + // a. Assert: kind is key+value. + Assert(kind === 'key+value'); + // b. Perform ? Yield(! CreateArrayFromList(« 𝔽(index), elementValue »)). + result = CreateArrayFromList([indexNumber, elementValue]); + } + } + Q(yield* GeneratorYield(CreateIteratorResultObject(result, Value.false))); + // vi. Set index to index + 1. + index += 1; + } + }; + // 4. Return CreateIteratorFromClosure(closure, "%ArrayIteratorPrototype%", %ArrayIteratorPrototype%). + const generator = CreateIteratorFromClosure(closure, Value('%ArrayIteratorPrototype%'), surroundingAgent.intrinsic('%ArrayIteratorPrototype%'), ['HostCapturedValues'], [array]); + return generator; +} diff --git a/src/abstract-ops/arraybuffer-objects.mts b/src/abstract-ops/arraybuffer-objects.mts new file mode 100644 index 0000000..26174be --- /dev/null +++ b/src/abstract-ops/arraybuffer-objects.mts @@ -0,0 +1,245 @@ +import { typedArrayInfoByType, type TypedArrayTypes } from '../intrinsics/TypedArray.mts'; +import { IsGrowableSharedArrayBuffer, sharedArrayBufferNotSupported } from './shared-arraybuffer.mts'; +import { + surroundingAgent, + NumberValue, BigIntValue, Value, + DataBlock, + UndefinedValue, + NullValue, + Q, X, NormalCompletion, type ValueEvaluator, + type Mutable, + Assert, OrdinaryCreateFromConstructor, + isNonNegativeInteger, CreateByteDataBlock, + SameValue, CopyDataBlockBytes, + F, + Z, R, + type FunctionObject, + type OrdinaryObject, + Throw, +} from '#self'; + +export interface ArrayBufferObject extends OrdinaryObject { + readonly ArrayBufferData: DataBlock | NullValue; + readonly ArrayBufferByteLength: number; + readonly ArrayBufferDetachKey: Value; +} + +export interface ResizableArrayBufferObject extends ArrayBufferObject { + readonly ArrayBufferMaxByteLength: number; +} + +export function isArrayBufferObject(o: Value): o is ArrayBufferObject { + return 'ArrayBufferDetachKey' in o; +} + +/** https://tc39.es/ecma262/#sec-allocatearraybuffer */ +export function* AllocateArrayBuffer(constructor: FunctionObject, byteLength: number, maxByteLength?: number): ValueEvaluator { + const slots = ['ArrayBufferData', 'ArrayBufferByteLength', 'ArrayBufferDetachKey']; + let allocatingResizableBuffer; + if (maxByteLength !== undefined) { + allocatingResizableBuffer = true; + } else { + allocatingResizableBuffer = false; + } + if (allocatingResizableBuffer) { + if (byteLength > maxByteLength!) { + return Throw.RangeError('Cannot resize ArrayBuffer to bigger than maxByteLength'); + } + slots.push('ArrayBufferMaxByteLength'); + } + const obj = Q(yield* OrdinaryCreateFromConstructor(constructor, '%ArrayBuffer.prototype%', slots)) as Mutable; + // 2. Assert: byteLength is a non-negative integer. + Assert(isNonNegativeInteger(byteLength)); + // 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. + if (allocatingResizableBuffer) { + (obj as Mutable).ArrayBufferMaxByteLength = maxByteLength!; + } + return obj; +} + +/** https://tc39.es/ecma262/#sec-isdetachedbuffer */ +export function IsDetachedBuffer(arrayBuffer: ArrayBufferObject) { + if (arrayBuffer.ArrayBufferData === Value.null) { + return true; + } + return false; +} + +/** https://tc39.es/ecma262/#sec-detacharraybuffer */ +export function DetachArrayBuffer(arrayBuffer: Mutable, key?: Value) { + // 2. Assert: IsSharedArrayBuffer(arrayBuffer) is false. + Assert(!IsSharedArrayBuffer(arrayBuffer)); + // 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 Throw.TypeError('$1 is not the [[ArrayBufferDetachKey]] of the given ArrayBuffer', key); + } + Q(surroundingAgent.debugger_tryTouchDuringPreview(arrayBuffer)); + // 5. Set arrayBuffer.[[ArrayBufferData]] to null. + arrayBuffer.ArrayBufferData = Value.null; + // 6. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. + arrayBuffer.ArrayBufferByteLength = 0; + return undefined; +} + +/** https://tc39.es/ecma262/#sec-issharedarraybuffer */ +export function IsSharedArrayBuffer(_obj: Value) { + return false; +} + +export function* CloneArrayBuffer(srcBuffer: ArrayBufferObject, srcByteOffset: number, srcLength: number): ValueEvaluator { + Assert(!IsDetachedBuffer(srcBuffer)); + const targetBuffer = Q(yield* AllocateArrayBuffer(surroundingAgent.intrinsic('%ArrayBuffer%'), srcLength)); + const srcBlock = srcBuffer.ArrayBufferData as DataBlock; + const targetBlock = targetBuffer.ArrayBufferData as DataBlock; + CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength); + return targetBuffer; +} + +/** https://tc39.es/ecma262/#sec-isbigintelementtype */ +export function IsBigIntElementType(type: TypedArrayTypes) { + // 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); + +/** https://tc39.es/ecma262/#sec-rawbytestonumeric */ +export function RawBytesToNumeric(type: TypedArrayTypes, rawBytes: number[], isLittleEndian: boolean) { + // 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); + const result = throwawayDataView[`get${dataViewType}`](0, isLittleEndian); + return IsBigIntElementType(type) === Value.true ? Z(result as bigint) : F(result as number); +} + +/** https://tc39.es/ecma262/#sec-getvaluefrombuffer */ +export function GetValueFromBuffer(arrayBuffer: ArrayBufferObject, byteIndex: number, type: TypedArrayTypes, _isTypedArray: boolean, _order: 'unordered', isLittleEndian?: boolean) { + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + Assert(!IsDetachedBuffer(arrayBuffer)); + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Assert: byteIndex is a non-negative integer. + Assert(isNonNegativeInteger(byteIndex)); + // 4. Let block be arrayBuffer.[[ArrayBufferData]]. + const block = arrayBuffer.ArrayBufferData as DataBlock; + // 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)) { + sharedArrayBufferNotSupported(); + } + // 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, byteIndex + 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) { + const AR = surroundingAgent.AgentRecord; + isLittleEndian = AR.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]); + +/** https://tc39.es/ecma262/#sec-numerictorawbytes */ +export function NumericToRawBytes(type: TypedArrayTypes, value: NumberValue | BigIntValue, isLittleEndian: boolean) { + let rawBytes; + // One day, we will write our own IEEE 754 and two's complement encoder… + if (type === 'Float32') { + if (Number.isNaN(R(value))) { + rawBytes = isLittleEndian ? [...float32NaNLE] : [...float32NaNBE]; + } else { + throwawayDataView.setFloat32(0, R(value as NumberValue), isLittleEndian); + rawBytes = [...throwawayArray.subarray(0, 4)]; + } + } else if (type === 'Float64') { + if (Number.isNaN(R(value))) { + rawBytes = isLittleEndian ? [...float64NaNLE] : [...float64NaNBE]; + } else { + throwawayDataView.setFloat64(0, R(value as 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 as (argument: Value) => ValueEvaluator; + // 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, R(intValue) as bigint & number, isLittleEndian); + rawBytes = [...throwawayArray.subarray(0, n)]; + } + return rawBytes; +} + +/** https://tc39.es/ecma262/#sec-setvalueinbuffer */ +export function* SetValueInBuffer(arrayBuffer: ArrayBufferObject, byteIndex: number, type: TypedArrayTypes, value: BigIntValue | NumberValue, _isTypedArray: boolean, _order: 'seq-cst' | 'unordered' | 'init', isLittleEndian?: boolean): ValueEvaluator { + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + Assert(!IsDetachedBuffer(arrayBuffer)); + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Assert: byteIndex is a non-negative integer. + Assert(isNonNegativeInteger(byteIndex)); + // 4. Assert: Type(value) is BigInt if IsBigIntElementType(type) is true; otherwise, Type(value) is Number. + if (IsBigIntElementType(type) === Value.true) { + Assert(value instanceof BigIntValue); + } else { + Assert(value instanceof NumberValue); + } + // 5. Let block be arrayBuffer.[[ArrayBufferData]]. + const block = arrayBuffer.ArrayBufferData as DataBlock; + // 6. Let elementSize be the Element Size value specified in Table 61 for Element Type type. + // const elementSize = typedArrayInfoByType[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) { + const AR = surroundingAgent.AgentRecord; + isLittleEndian = AR.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)) { + sharedArrayBufferNotSupported(); + } + // 10. Else, store the individual bytes of rawBytes into block, in order, starting at block[byteIndex]. + Q(surroundingAgent.debugger_tryTouchDuringPreview(arrayBuffer)); + rawBytes.forEach((byte, i) => { + block[byteIndex + i] = byte; + }); + // 11. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} + +/** https://tc39.es/ecma262/#sec-arraybufferbytelength */ +export function ArrayBufferByteLength(arrayBuffer: ArrayBufferObject, _order: 'seq-cst' | 'unordered'): number { + if (IsGrowableSharedArrayBuffer(arrayBuffer)) { + sharedArrayBufferNotSupported(); + } + Assert(!IsDetachedBuffer(arrayBuffer)); + return arrayBuffer.ArrayBufferByteLength; +} + +/** https://tc39.es/ecma262/#sec-isfixedlengtharraybuffer */ +export function IsFixedLengthArrayBuffer(arrayBuffer: ArrayBufferObject) { + return !('ArrayBufferMaxByteLength' in arrayBuffer); +} diff --git a/src/abstract-ops/async-function-operations.mts b/src/abstract-ops/async-function-operations.mts new file mode 100644 index 0000000..291ee05 --- /dev/null +++ b/src/abstract-ops/async-function-operations.mts @@ -0,0 +1,46 @@ +import { resume } from '../helpers.mts'; +import { + EnsureCompletion, X, ExecutionContext, surroundingAgent, Evaluate, Value, type ParseNode, Assert, Call, PromiseCapabilityRecord, + type AsyncBuiltinSteps, +} from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-async-function-objects */ + +/** https://tc39.es/ecma262/#sec-asyncblockstart */ +export function* AsyncBlockStart(promiseCapability: PromiseCapabilityRecord, asyncBody: ParseNode.AsyncBody | ParseNode.ExpressionBody | ParseNode.Module | AsyncBuiltinSteps, asyncContext: ExecutionContext) { + asyncContext.promiseCapability = promiseCapability; + + const runningContext = surroundingAgent.runningExecutionContext; + asyncContext.codeEvaluationState = (function* resumer() { + let result; + if (typeof asyncBody === 'function') { + result = EnsureCompletion(yield* asyncBody()); + } else { + 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(yield* resume(asyncContext, { type: 'await-resume', value: Value.undefined })); + Assert(surroundingAgent.runningExecutionContext === runningContext); + Assert(result.Type === 'normal' && result.Value === Value.undefined); + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start */ +export function* AsyncFunctionStart(promiseCapability: PromiseCapabilityRecord, asyncFunctionBody: ParseNode.AsyncBody | ParseNode.ExpressionBody | AsyncBuiltinSteps) { + const runningContext = surroundingAgent.runningExecutionContext; + const asyncContext = runningContext.copy(); + X(yield* AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext)); +} diff --git a/src/abstract-ops/async-generator-objects.mts b/src/abstract-ops/async-generator-objects.mts new file mode 100644 index 0000000..96abda4 --- /dev/null +++ b/src/abstract-ops/async-generator-objects.mts @@ -0,0 +1,343 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { ExecutionContext } from '../execution-context/ExecutionContext.mts'; +import { + Q, X, + Await, + EnsureCompletion, + NormalCompletion, + AbruptCompletion, + ThrowCompletion, + type YieldCompletion, + ReturnCompletion, +} from '../completion.mts'; +import { Evaluate, type PlainEvaluator, type YieldEvaluator } from '../evaluator.mts'; +import { + BooleanValue, JSStringValue, Value, type Arguments, + type NativeSteps, +} from '../value.mts'; +import { + resume, __ts_cast__, +} from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + Call, + CreateBuiltinFunction, + CreateIteratorResultObject, + generatorBrandToErrorMessageType, + GetGeneratorKind, + PerformPromiseThen, + PromiseCapabilityRecord, + PromiseResolve, + RequireInternalSlot, + SameValue, + type OrdinaryObject, +} from './all.mts'; +import { Throw, type Realm } from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-asyncgenerator-objects */ + +/** https://tc39.es/ecma262/#sec-asyncgeneratorrequest-records */ +export interface AsyncGeneratorRequestRecord { + readonly Completion: YieldCompletion; + readonly Capability: PromiseCapabilityRecord; +} +export const AsyncGeneratorRequestRecord = function AsyncGeneratorRequestRecord(value: AsyncGeneratorRequestRecord) { + Object.setPrototypeOf(value, AsyncGeneratorRequestRecord.prototype); + return value; +} as { + (value: AsyncGeneratorRequestRecord): AsyncGeneratorRequestRecord; + [Symbol.hasInstance](instance: unknown): instance is AsyncGeneratorRequestRecord; +}; + +export interface AsyncGeneratorObject extends OrdinaryObject { + AsyncGeneratorState: 'suspendedStart' | 'suspendedYield' | 'executing' | 'completed' | 'draining-queue'; + AsyncGeneratorContext: ExecutionContext; + AsyncGeneratorQueue: AsyncGeneratorRequestRecord[]; + GeneratorBrand: JSStringValue | undefined; +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorstart */ +export function AsyncGeneratorStart(generator: AsyncGeneratorObject, generatorBody: ParseNode.AsyncGeneratorBody | (() => YieldEvaluator)) { + // 1. Assert: generator.[[AsyncGeneratorState]] is 'suspendedStart'. + Assert(generator.AsyncGeneratorState === 'suspendedStart'); + // 2. Let genContext be the running execution context. + const genContext = surroundingAgent.runningExecutionContext; + // 3. Set the Generator component of genContext to generator. + genContext.Generator = generator; + const closure = function* resumer(): YieldEvaluator { + const acGenContext = surroundingAgent.runningExecutionContext; + const acGenerator = acGenContext.Generator as AsyncGeneratorObject; + // a. If generatorBody is a Parse Node, then + // i. Let result be the result of evaluating generatorBody. + // b. Else, + // i. Assert: generatorBody is an Abstract Closure. + // ii. Let result be generatorBody(). + let result = EnsureCompletion( + // Note: Engine262 can only perform the "If generatorBody is an Abstract Closure" check: + yield* typeof generatorBody === 'function' + ? generatorBody() + : Evaluate(generatorBody), + ) as YieldCompletion; + // c. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return. + // d. 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(acGenContext); + // e. Set generator.[[AsyncGeneratorState]] to completed. + acGenerator.AsyncGeneratorState = 'draining-queue'; + // f. If result.[[Type]] is normal, set result to NormalCompletion(undefined). + if (result instanceof NormalCompletion) { + result = NormalCompletion(Value.undefined); + } + // g. If result.[[Type]] is return, set result to NormalCompletion(result.[[Value]]). + if (result instanceof ReturnCompletion) { + result = NormalCompletion(result.Value); + } + // h. Perform AsyncGeneratorCompleteStep(generator, result, true). + AsyncGeneratorCompleteStep(acGenerator, result, Value.true); + // i. Perform AsyncGeneratorDrainQueue(generator). + yield* AsyncGeneratorDrainQueue(acGenerator); + // j. Return undefined. + return Value.undefined; + }; + // 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 = (closure()); + // 5. Set generator.[[AsyncGeneratorContext]] to genContext. + generator.AsyncGeneratorContext = genContext; + // 7. Set generator.[[AsyncGeneratorQueue]] to a new empty List. + generator.AsyncGeneratorQueue = []; + // 8. Return undefined. +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorvalidate */ +export function AsyncGeneratorValidate(generator: Value, generatorBrand: JSStringValue | undefined) { + // 1. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorContext]]). + Q(RequireInternalSlot(generator, 'AsyncGeneratorContext')); + // 2. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorState]]). + Q(RequireInternalSlot(generator, 'AsyncGeneratorState')); + // 3. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorQueue]]). + Q(RequireInternalSlot(generator, 'AsyncGeneratorQueue')); + __ts_cast__(generator); + // 4. If generator.[[GeneratorBrand]] is not the same value as generatorBrand, throw a TypeError exception. + const brand = generator.GeneratorBrand; + if ( + brand === undefined || generatorBrand === undefined + ? brand !== generatorBrand + : SameValue(brand, generatorBrand) === Value.false + ) { + return Throw.TypeError('$1 is not a $2', generator, generatorBrandToErrorMessageType(generatorBrand) || 'AsyncGenerator'); + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorenqueue */ +export function AsyncGeneratorEnqueue(generator: AsyncGeneratorObject, completion: YieldCompletion, promiseCapability: PromiseCapabilityRecord) { + // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. + const request = AsyncGeneratorRequestRecord({ Completion: completion, Capability: promiseCapability }); + // 2. Append request to the end of generator.[[AsyncGeneratorQueue]]. + generator.AsyncGeneratorQueue.push(request); +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep */ +function AsyncGeneratorCompleteStep(generator: AsyncGeneratorObject, completion: YieldCompletion, done: BooleanValue, realm?: Realm) { + // 1. Let queue be generator.[[AsyncGeneratorQueue]]. + const queue = generator.AsyncGeneratorQueue; + // 2. Assert: queue is not empty. + Assert(queue.length > 0); + // 3. Let next be the first element of queue. + // 4. Remove the first element from queue. + const next = queue.shift()!; + // 5. Let promiseCapability be next.[[Capability]]. + const promiseCapability = next.Capability; + // 6. Let value be completion.[[Value]]. + const value = completion.Value; + // 7. If completion.[[Type]] is throw, then + if (completion instanceof ThrowCompletion) { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). + X(Call(promiseCapability.Reject, Value.undefined, [value])); + } else { // 8. Else, + // a. Assert: completion.[[Type]] is normal. + Assert(completion instanceof NormalCompletion); + let iteratorResult; + // b. If realm is present, then + if (realm !== undefined) { + // i. Let oldRealm be the running execution context's Realm. + const oldRealm = surroundingAgent.runningExecutionContext.Realm; + // ii. Set the running execution context's Realm to realm. + surroundingAgent.runningExecutionContext.Realm = realm; + // iii. Let iteratorResult be CreateIteratorResultObject(value, done). + iteratorResult = CreateIteratorResultObject(value, done); + // iv. Set the running execution context's Realm to oldRealm. + surroundingAgent.runningExecutionContext.Realm = oldRealm; + } else { // c. Else, + // i. Let iteratorResult be CreateIteratorResultObject(value, done). + iteratorResult = CreateIteratorResultObject(value, done); + } + // d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). + X(Call(promiseCapability.Resolve, Value.undefined, [iteratorResult])); + } +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorresume */ +export function* AsyncGeneratorResume(generator: AsyncGeneratorObject, completion: YieldCompletion) { + // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield. + Assert(generator.AsyncGeneratorState === 'suspendedStart' || generator.AsyncGeneratorState === 'suspendedYield'); + // 2. Let genContext be generator.[[AsyncGeneratorContext]]. + const genContext = generator.AsyncGeneratorContext; + // 3. Let callerContext be the running execution context. + const callerContext = surroundingAgent.runningExecutionContext; + // 4. Suspend callerContext. + // 5. Set generator.[[AsyncGeneratorState]] to executing. + generator.AsyncGeneratorState = 'executing'; + // 6. Push genContext onto the execution context stack; genContext is now the running execution context. + surroundingAgent.executionContextStack.push(genContext); + // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation. + const result = yield* resume(genContext, { type: 'async-generator-resume', value: completion }); + // 8. Assert: result is never an abrupt completion. + Assert(!(result instanceof AbruptCompletion)); + // 9. Assert: When we return here, genContext has already been removed from the execution context stack and callerContext is the currently running execution context. + Assert(surroundingAgent.runningExecutionContext === callerContext); +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption */ +function* AsyncGeneratorUnwrapYieldResumption(resumptionValue: YieldCompletion): YieldEvaluator { + // 1. If resumptionValue.[[Type]] is not return, return Completion(resumptionValue). + if (!(resumptionValue instanceof ReturnCompletion)) { + return Q(resumptionValue); + } + // 2. Let awaited be Await(resumptionValue.[[Value]]). + const awaited = EnsureCompletion(yield* Await(resumptionValue.Value)); + // 3. If awaited.[[Type]] is throw, return Completion(awaited). + if (awaited instanceof ThrowCompletion) { + return Q(awaited); + } + // 4. Assert: awaited.[[Type]] is normal. + Assert(awaited instanceof NormalCompletion); + // 5. Return Completion { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }. + return ReturnCompletion(awaited.Value); +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratoryield */ +export function* AsyncGeneratorYield(value: Value): YieldEvaluator { + // 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); + // 3. Let generator be the value of the Generator component of genContext. + const generator = genContext.Generator as AsyncGeneratorObject; + // 4. Assert: GetGeneratorKind() is async. + Assert(GetGeneratorKind() === 'async'); + // 5. Let completion be NormalCompletion(value). + const completion = NormalCompletion(value); + // 6. Assert: The execution context stack has at least two elements. + Assert(surroundingAgent.executionContextStack.length >= 2); + // 7. Let previousContext be the second to top element of the execution context stack. + const previousContext = surroundingAgent.executionContextStack[surroundingAgent.executionContextStack.length - 2]; + // 8. Let previousRealm be previousContext's Realm. + const previousRealm = previousContext.Realm; + // 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm). + AsyncGeneratorCompleteStep(generator, completion, Value.false, previousRealm); + // 10. Let queue be generator.[[AsyncGeneratorQueue]]. + const queue = generator.AsyncGeneratorQueue; + // 11. If queue is not empty, then + if (queue.length > 0) { + // a. NOTE: Execution continues without suspending the generator. + // b. Let toYield be the first element of queue. + const toYield = queue[0]; + // c. Let resumptionValue be toYield.[[Completion]]. + const resumptionValue = toYield.Completion; + // d. Return AsyncGeneratorUnwrapYieldResumption(resumptionValue). + return yield* AsyncGeneratorUnwrapYieldResumption(resumptionValue); + } else { // 12. Else, + // a. Set generator.[[AsyncGeneratorState]] to suspendedYield. + generator.AsyncGeneratorState = 'suspendedYield'; + // b. 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); + // c. 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 = yield { type: 'async-generator-yield' }; + Assert(resumptionValue.type === 'async-generator-resume'); + // i. Return AsyncGeneratorUnwrapYieldResumption(resumptionValue). + return yield* AsyncGeneratorUnwrapYieldResumption(EnsureCompletion(resumptionValue.value)); + // ii. NOTE: When the above step returns, it returns to the evaluation of the YieldExpression production that originally called this abstract operation. + + // d. Return undefined. + // e. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of genContext. + } +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn */ +export function* AsyncGeneratorAwaitReturn(generator: AsyncGeneratorObject): PlainEvaluator { + Assert(generator.AsyncGeneratorState === 'draining-queue'); + // 1. Let queue be generator.[[AsyncGeneratorQueue]]. + const queue = generator.AsyncGeneratorQueue; + // 2. Assert: queue is not empty. + Assert(queue.length > 0); + // 3. Let next be the first element of queue. + const next = queue[0]; + // 4. Let completion be next.[[Completion]]. + const completion = next.Completion; + // 5. Assert: completion.[[Type]] is return. + Assert(completion instanceof ReturnCompletion); + // 6. Let promise be PromiseResolve(%Promise%, completion.[[Value]]). + const promiseCompletion = yield* PromiseResolve(surroundingAgent.intrinsic('%Promise%'), completion.Value); + if (promiseCompletion instanceof AbruptCompletion) { + AsyncGeneratorCompleteStep(generator, promiseCompletion, Value.true); + yield* AsyncGeneratorDrainQueue(generator); + return; + } + const promise = X(promiseCompletion); + // 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: + const fulfilledClosure: NativeSteps = function* fulfilledClosure([value = Value.undefined]: Arguments) { + Assert(generator.AsyncGeneratorState === 'draining-queue'); + // b. Let result be NormalCompletion(value). + const result = NormalCompletion(value); + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + AsyncGeneratorCompleteStep(generator, result, Value.true); + // d. Perform AsyncGeneratorDrainQueue(generator). + yield* AsyncGeneratorDrainQueue(generator); + // e. Return undefined. + return Value.undefined; + }; + // 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). + const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 1, Value(''), []); + // 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: + const rejectedClosure: NativeSteps = function* rejectedClosure([reason = Value.undefined]: Arguments) { + Assert(generator.AsyncGeneratorState === 'draining-queue'); + // b. Let result be ThrowCompletion(reason). + const result = ThrowCompletion(reason); + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + AsyncGeneratorCompleteStep(generator, result, Value.true); + // d. Perform AsyncGeneratorDrainQueue(generator). + yield* AsyncGeneratorDrainQueue(generator); + // e. Return undefined. + return Value.undefined; + }; + // 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + const onRejected = CreateBuiltinFunction(rejectedClosure, 1, Value(''), []); + // 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected). + PerformPromiseThen(promise, onFulfilled, onRejected); +} + +/** https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue */ +function* AsyncGeneratorDrainQueue(generator: AsyncGeneratorObject) { + // 1. Assert: generator.[[AsyncGeneratorState]] is completed. + Assert(generator.AsyncGeneratorState === 'draining-queue'); + // 2. Let queue be generator.[[AsyncGeneratorQueue]]. + const queue = generator.AsyncGeneratorQueue; + while (queue.length) { + const next = queue[0]; + let completion = next.Completion; + if (completion instanceof ReturnCompletion) { + yield* AsyncGeneratorAwaitReturn(generator); + return; + } else { + if (completion instanceof NormalCompletion) { + completion = NormalCompletion(Value.undefined); + } + AsyncGeneratorCompleteStep(generator, completion, Value.true); + } + } + generator.AsyncGeneratorState = 'completed'; +} diff --git a/src/abstract-ops/data-types-and-values.mts b/src/abstract-ops/data-types-and-values.mts new file mode 100644 index 0000000..302cc2b --- /dev/null +++ b/src/abstract-ops/data-types-and-values.mts @@ -0,0 +1,43 @@ +import { JSStringValue, UndefinedValue, Value } from '../value.mts'; +import { X } from '../completion.mts'; +import { CanonicalNumericIndexString, R } from './all.mts'; + +// This file covers predicates defined in +/** https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values */ + +// 6.1.7 #integer-index +export function isIntegerIndex(V: Value) { + if (!(V instanceof JSStringValue)) { + return false; + } + const numeric = X(CanonicalNumericIndexString(V)); + if (numeric instanceof UndefinedValue) { + return false; + } + if (Object.is(R(numeric), +0)) { + return true; + } + return R(numeric) > 0 && Number.isSafeInteger(R(numeric)); +} + +// 6.1.7 #array-index +export function isArrayIndex(V: Value) { + if (!(V instanceof JSStringValue)) { + return false; + } + const numeric = X(CanonicalNumericIndexString(V)); + if (numeric instanceof UndefinedValue) { + return false; + } + if (!Number.isInteger(R(numeric))) { + return false; + } + if (Object.is(R(numeric), +0)) { + return true; + } + return R(numeric) > 0 && R(numeric) < (2 ** 32) - 1; +} + +export function isNonNegativeInteger(argument: number) { + return Number.isInteger(argument) && argument >= 0; +} diff --git a/src/abstract-ops/dataview-objects.mts b/src/abstract-ops/dataview-objects.mts new file mode 100644 index 0000000..8adebc0 --- /dev/null +++ b/src/abstract-ops/dataview-objects.mts @@ -0,0 +1,149 @@ +import { Q } from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import type { DataViewObject } from '../intrinsics/DataView.mts'; +import { type TypedArrayTypes, typedArrayInfoByType } from '../intrinsics/TypedArray.mts'; +import { Value } from '../value.mts'; +import { + Assert, + GetValueFromBuffer, + IsDetachedBuffer, + IsBigIntElementType, + SetValueInBuffer, + ToBoolean, + ToIndex, + ToNumber, + ToBigInt, + RequireInternalSlot, + type ArrayBufferObject, + ArrayBufferByteLength, + IsFixedLengthArrayBuffer, +} from './all.mts'; +import { Throw } from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-dataview-objects */ + +/** https://tc39.es/ecma262/#sec-dataview-with-buffer-witness-records */ +export interface DataViewWithBufferWitnessRecord { + readonly Object: DataViewObject; + CachedBufferByteLength: number | 'detached'; +} + +/** https://tc39.es/ecma262/#sec-makedataviewwithbufferwitnessrecord */ +export function MakeDataViewWithBufferWitnessRecord(obj: DataViewObject, order: 'seq-cst' | 'unordered'): DataViewWithBufferWitnessRecord { + const buffer = obj.ViewedArrayBuffer as ArrayBufferObject; + let byteLength: DataViewWithBufferWitnessRecord['CachedBufferByteLength']; + if (IsDetachedBuffer(buffer)) { + byteLength = 'detached'; + } else { + byteLength = ArrayBufferByteLength(buffer, order); + } + return { Object: obj, CachedBufferByteLength: byteLength }; +} + +/** https://tc39.es/ecma262/#sec-getviewbytelength */ +export function GetViewByteLength(viewRecord: DataViewWithBufferWitnessRecord): number { + Assert(!IsViewOutOfBounds(viewRecord)); + const view = viewRecord.Object; + // @ts-expect-error + if (view.ByteLength !== 'auto') { + return view.ByteLength; + } + Assert(!IsFixedLengthArrayBuffer(view.ViewedArrayBuffer as ArrayBufferObject)); + const byteOffset = view.ByteOffset; + const byteLength = viewRecord.CachedBufferByteLength; + Assert(byteLength !== 'detached'); + return byteLength - byteOffset; +} + +/** https://tc39.es/ecma262/#sec-isviewoutofbounds */ +export function IsViewOutOfBounds(viewRecord: DataViewWithBufferWitnessRecord): boolean { + const view = viewRecord.Object; + const bufferByteLength = viewRecord.CachedBufferByteLength; + if (IsDetachedBuffer(view.ViewedArrayBuffer as ArrayBufferObject)) { + Assert(bufferByteLength === 'detached'); + return true; + } + Assert(typeof bufferByteLength === 'number' && bufferByteLength >= 0); + const byteOffsetStart = view.ByteOffset; + let byteOffsetEnd; + // @ts-expect-error + if (view.ByteLength === 'auto') { + byteOffsetEnd = bufferByteLength; + } else { + byteOffsetEnd = byteOffsetStart + view.ByteLength; + } + if (byteOffsetStart > bufferByteLength || byteOffsetEnd > bufferByteLength) { + return true; + } + return false; +} + +/** https://tc39.es/ecma262/#sec-getviewvalue */ +export function* GetViewValue(view: Value, requestIndex: Value, isLittleEndian: Value, type: TypedArrayTypes) { + // 1. Perform ? RequireInternalSlot(view, [[DataView]]). + Q(RequireInternalSlot(view, 'DataView')); + __ts_cast__(view); + // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in view); + // 3. Let getIndex be ? ToIndex(requestIndex). + const getIndex = Q(yield* ToIndex(requestIndex)); + // 4. Set isLittleEndian to ToBoolean(isLittleEndian). + isLittleEndian = ToBoolean(isLittleEndian); + // 7. Let viewOffset be view.[[ByteOffset]]. + const viewOffset = view.ByteOffset; + const viewRecord = MakeDataViewWithBufferWitnessRecord(view, 'unordered'); + if (IsViewOutOfBounds(viewRecord)) { + return Throw.TypeError('Offset is out of bound'); + } + const viewSize = GetViewByteLength(viewRecord); + // 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 Throw.RangeError('Offset is out of bound'); + } + // 11. Let bufferIndex be getIndex + viewOffset. + const bufferIndex = getIndex + viewOffset; + // 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian). + return GetValueFromBuffer(view.ViewedArrayBuffer as ArrayBufferObject, bufferIndex, type, false, 'unordered', isLittleEndian.booleanValue()); +} + +/** https://tc39.es/ecma262/#sec-setviewvalue */ +export function* SetViewValue(view: Value, requestIndex: Value, isLittleEndian: Value, type: TypedArrayTypes, value: Value) { + // 1. Perform ? RequireInternalSlot(view, [[DataView]]). + Q(RequireInternalSlot(view, 'DataView')); + // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in view); + __ts_cast__(view); + // 3. Let getIndex be ? ToIndex(requestIndex). + const getIndex = Q(yield* ToIndex(requestIndex)); + // 4. If IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). + // 5. Otherwise, let numberValue be ? ToNumber(value). + let numberValue; + if (IsBigIntElementType(type) === Value.true) { + numberValue = Q(yield* ToBigInt(value)); + } else { + numberValue = Q(yield* ToNumber(value)); + } + // 6. Set isLittleEndian to ToBoolean(isLittleEndian). + isLittleEndian = ToBoolean(isLittleEndian); + // 9. Let viewOffset be view.[[ByteOffset]]. + const viewOffset = view.ByteOffset; + const viewRecord = MakeDataViewWithBufferWitnessRecord(view, 'unordered'); + if (IsViewOutOfBounds(viewRecord)) { + return Throw.TypeError('Offset is out of bound'); + } + const viewSize = GetViewByteLength(viewRecord); + // 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 Throw.RangeError('Offset is out of bound'); + } + // 13. Let bufferIndex be getIndex + viewOffset. + const bufferIndex = getIndex + viewOffset; + // 14. Perform ? SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian). + Q(yield* SetValueInBuffer(view.ViewedArrayBuffer as ArrayBufferObject, bufferIndex, type, numberValue, false, 'unordered', isLittleEndian.booleanValue())); + return Value.undefined; +} diff --git a/src/abstract-ops/date-objects.mts b/src/abstract-ops/date-objects.mts new file mode 100644 index 0000000..08e47b3 --- /dev/null +++ b/src/abstract-ops/date-objects.mts @@ -0,0 +1,242 @@ +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-date-objects */ + +import { X } from '../completion.mts'; +import { + ToIntegerOrInfinity, + F, R, + Assert, +} from './all.mts'; +import type { NumberValue } from '#self'; + +const mod = (n: number, m: number) => { + 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; + +/** https://tc39.es/ecma262/#sec-day-number-and-time-within-day */ +export function Day(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(Math.floor(t / msPerDay)); +} + +export function TimeWithinDay(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(mod(t, msPerDay)); +} + +/** https://tc39.es/ecma262/#sec-year-number */ +export function DaysInYear(y: NumberValue) { + const ry = R(y); + if (mod(ry, 400) === 0) { + return F(366); + } + if (mod(ry, 100) === 0) { + return F(365); + } + if (mod(ry, 4) === 0) { + return F(366); + } + return F(365); +} + +export function DayFromYear(_y: NumberValue) { + const y = R(_y); + return F(365 * (y - 1970) + Math.floor((y - 1969) / 4) - Math.floor((y - 1901) / 100) + Math.floor((y - 1601) / 400)); +} + +export function TimeFromYear(y: NumberValue) { + return F(msPerDay * R(DayFromYear(y))); +} + +export const msPerAverageYear = 12 * 30.436875 * msPerDay; + +export function YearFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + let year = Math.floor((t + msPerAverageYear / 2) / msPerAverageYear) + 1970; + if (R(TimeFromYear(F(year))) > t) { + year -= 1; + } + return F(year); +} + +export function InLeapYear(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + if (R(DaysInYear(YearFromTime(t))) === 366) { + return F(1); + } + return F(0); +} + +/** https://tc39.es/ecma262/#sec-month-number */ +export function MonthFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + const inLeapYear = R(InLeapYear(t)); + const dayWithinYear = R(DayWithinYear(t)); + if (dayWithinYear < 31) { + return F(+0); + } + if (dayWithinYear < 59 + inLeapYear) { + return F(1); + } + if (dayWithinYear < 90 + inLeapYear) { + return F(2); + } + if (dayWithinYear < 120 + inLeapYear) { + return F(3); + } + if (dayWithinYear < 151 + inLeapYear) { + return F(4); + } + if (dayWithinYear < 181 + inLeapYear) { + return F(5); + } + if (dayWithinYear < 212 + inLeapYear) { + return F(6); + } + if (dayWithinYear < 243 + inLeapYear) { + return F(7); + } + if (dayWithinYear < 273 + inLeapYear) { + return F(8); + } + if (dayWithinYear < 304 + inLeapYear) { + return F(9); + } + if (dayWithinYear < 334 + inLeapYear) { + return F(10); + } + Assert(dayWithinYear < 365 + inLeapYear); + return F(11); +} + +export function DayWithinYear(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(R(Day(t)) - R(DayFromYear(YearFromTime(t)))); +} + +/** https://tc39.es/ecma262/#sec-date-number */ +export function DateFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + const inLeapYear = R(InLeapYear(t)); + const dayWithinYear = R(DayWithinYear(t)); + const month = R(MonthFromTime(t)); + switch (month) { + case 0: return F(dayWithinYear + 1); + case 1: return F(dayWithinYear - 30); + case 2: return F(dayWithinYear - 58 - inLeapYear); + case 3: return F(dayWithinYear - 89 - inLeapYear); + case 4: return F(dayWithinYear - 119 - inLeapYear); + case 5: return F(dayWithinYear - 150 - inLeapYear); + case 6: return F(dayWithinYear - 180 - inLeapYear); + case 7: return F(dayWithinYear - 211 - inLeapYear); + case 8: return F(dayWithinYear - 242 - inLeapYear); + case 9: return F(dayWithinYear - 272 - inLeapYear); + case 10: return F(dayWithinYear - 303 - inLeapYear); + default: + } + Assert(month === 11); + return F(dayWithinYear - 333 - inLeapYear); +} + +/** https://tc39.es/ecma262/#sec-week-day */ +export function WeekDay(t: NumberValue) { + return F(mod(R(Day(t)) + 4, 7)); +} + +/** https://tc39.es/ecma262/#sec-local-time-zone-adjustment */ +export function LocalTZA(_t: NumberValue, _isUTC: boolean) { + // TODO: implement this function properly. + return 0; +} + +/** https://tc39.es/ecma262/#sec-localtime */ +export function LocalTime(t: NumberValue) { + return F(R(t) + LocalTZA(t, true)); +} + +/** https://tc39.es/ecma262/#sec-utc-t */ +export function UTC(t: NumberValue) { + return F(R(t) - LocalTZA(t, false)); +} + +/** https://tc39.es/ecma262/#sec-hours-minutes-second-and-milliseconds */ +export function HourFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(mod(Math.floor(t / msPerHour), HoursPerDay)); +} + +export function MinFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(mod(Math.floor(t / msPerMinute), MinutesPerHour)); +} + +export function SecFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(mod(Math.floor(t / msPerSecond), SecondsPerMinute)); +} + +export function msFromTime(_t: NumberValue | number) { + const t = typeof _t === 'number' ? _t : R(_t); + return F(mod(t, msPerSecond)); +} + +/** https://tc39.es/ecma262/#sec-maketime */ +export function MakeTime(hour: NumberValue, min: NumberValue, sec: NumberValue, ms: NumberValue) { + if (!Number.isFinite(R(hour)) || !Number.isFinite(R(min)) || !Number.isFinite(R(sec)) || !Number.isFinite(R(ms))) { + return F(NaN); + } + const h = X(ToIntegerOrInfinity(hour)); + const m = X(ToIntegerOrInfinity(min)); + const s = X(ToIntegerOrInfinity(sec)); + const milli = X(ToIntegerOrInfinity(ms)); + const t = h * msPerHour + m * msPerMinute + s * msPerSecond + milli; + return F(t); +} + +const daysWithinYearToEndOfMonth = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + +/** https://tc39.es/ecma262/#sec-makeday */ +export function MakeDay(year: NumberValue, month: NumberValue, date: NumberValue) { + if (!Number.isFinite(R(year)) || !Number.isFinite(R(month)) || !Number.isFinite(R(date))) { + return F(NaN); + } + const y = X(ToIntegerOrInfinity(year)); + const m = X(ToIntegerOrInfinity(month)); + const dt = X(ToIntegerOrInfinity(date)); + const ym = y + Math.floor(m / 12); + const mn = mod(m, 12); + const ymday = R(DayFromYear(F(ym + (mn > 1 ? 1 : 0)))) - 365 * (mn > 1 ? 1 : 0) + daysWithinYearToEndOfMonth[mn]; + const t = F(ymday * msPerDay); + return F(R(Day(t)) + dt - 1); +} + +/** https://tc39.es/ecma262/#sec-makedate */ +export function MakeDate(day: NumberValue, time: NumberValue) { + if (!Number.isFinite(R(day)) || !Number.isFinite(R(time))) { + return F(NaN); + } + return F(R(day) * msPerDay + R(time)); +} + +/** https://tc39.es/ecma262/#sec-timeclip */ +export function TimeClip(time: NumberValue) { + // 1. If time is not finite, return NaN. + if (!time.isFinite()) { + return F(NaN); + } + // 2. If abs(ℝ(time)) > 8.64 × 1015, return NaN. + if (Math.abs(R(time)) > 8.64e15) { + return F(NaN); + } + // 3. Return 𝔽(! ToIntegerOrInfinity(time)). + return F(X(ToIntegerOrInfinity(time))); +} diff --git a/src/abstract-ops/error-objects.mts b/src/abstract-ops/error-objects.mts new file mode 100644 index 0000000..cb09521 --- /dev/null +++ b/src/abstract-ops/error-objects.mts @@ -0,0 +1,38 @@ +import { ObjectValue, Value, Descriptor } from '../value.mts'; +import { + Q, X, NormalCompletion, type ValueEvaluator, +} from '../completion.mts'; +import type { ErrorObject } from '../intrinsics/Error.mts'; +import { HasProperty, Get, DefinePropertyOrThrow } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-errorobjects-install-error-cause */ +export function* InstallErrorCause(O: ObjectValue, options: Value): ValueEvaluator { + // 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then + if (options instanceof ObjectValue) { + // nested if statement due to macro expansion + if (Q(yield* HasProperty(options, Value('cause'))) === Value.true) { + // a. Let cause be ? Get(options, "cause"). + const cause = Q(yield* Get(options, Value('cause'))); + // b. Perform ! CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause). + X(DefinePropertyOrThrow(O, Value('cause'), Descriptor({ + Value: cause, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + } + // 2. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} + +/** https://tc39.es/proposal-is-error/#sec-iserror */ +export function IsError(argument: Value): argument is ErrorObject { + if (!(argument instanceof ObjectValue)) { + return false; + } + if ('ErrorData' in argument) { + return true; + } + return false; +} diff --git a/src/abstract-ops/execution-contexts.mts b/src/abstract-ops/execution-contexts.mts new file mode 100644 index 0000000..6c60f82 --- /dev/null +++ b/src/abstract-ops/execution-contexts.mts @@ -0,0 +1,21 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + NullValue, +} from '../value.mts'; + +/** Used in the inspector infrastructure to track the real source (or compiled) */ +export function getActiveScriptId(): string | undefined { + for (let i = surroundingAgent.executionContextStack.length - 1; i >= 0; i -= 1) { + const e = surroundingAgent.executionContextStack[i]; + if (e.HostDefined?.scriptId) { + return e.HostDefined.scriptId; + } + if (!(e.ScriptOrModule instanceof NullValue)) { + const fromScript = e.ScriptOrModule.HostDefined.scriptId; + if (fromScript) { + return fromScript; + } + } + } + return undefined; +} diff --git a/src/abstract-ops/function-operations.mts b/src/abstract-ops/function-operations.mts new file mode 100644 index 0000000..51d393c --- /dev/null +++ b/src/abstract-ops/function-operations.mts @@ -0,0 +1,759 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { ExecutionContext } from '../execution-context/ExecutionContext.mts'; +import { + Descriptor, + SymbolValue, + ObjectValue, + UndefinedValue, + Value, + PrivateName, + type Arguments, + BooleanValue, type PropertyKeyValue, NullValue, JSStringValue, + type NativeSteps, + NumberValue, +} from '../value.mts'; +import { + EnsureCompletion, + NormalCompletion, + AbruptCompletion, + Completion, + Q, X, + type PlainCompletion, + ReturnCompletion, + ThrowCompletion, +} from '../completion.mts'; +import { ExpectedArgumentCount } from '../static-semantics/all.mts'; +import { + ClassFieldDefinitionRecord, EvaluateBody, PrivateElementRecord, +} from '../runtime-semantics/all.mts'; +import { skipDebugger, type Mutable } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { FunctionProto_toString } from '../intrinsics/FunctionPrototype.mts'; +import { + Assert, + Call, + CreateDataPropertyOrThrow, + DefinePropertyOrThrow, + HasOwnProperty, + IsConstructor, + IsExtensible, + MakeBasicObject, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + ToObject, + PrivateMethodOrAccessorAdd, + PrivateFieldAdd, + IsPropertyKey, + isNonNegativeInteger, + isStrictModeCode, + F as toNumberValue, + type OrdinaryObject, + NewPromiseCapability, + AsyncFunctionStart, + Get, + R, + ToIntegerOrInfinity, + InitializePrivateMethods, + getActiveScriptId, +} from './all.mts'; +import { + GetActiveScriptOrModule, + Realm, + EnvironmentRecord, + FunctionEnvironmentRecord, + GlobalEnvironmentRecord, + ClassElementDefinitionRecord, + type AbstractModuleRecord, type CanBeNativeSteps, type DefaultConstructorBuiltinFunction, type DescriptorInit, type FunctionCallContext, type ModuleRecord, type PrivateEnvironmentRecord, type ScriptRecord, +} from '#self'; + +interface BaseFunctionObject extends OrdinaryObject { + readonly Realm: Realm; + readonly InitialName: JSStringValue | NullValue; + readonly Async: boolean; + // https://github.com/tc39/ecma262/pull/3212/ + readonly IsClassConstructor: BooleanValue; + Call(thisValue: Value, args: Arguments): ValueEvaluator; + Construct(args: Arguments, newTarget: FunctionObject | UndefinedValue): ValueEvaluator; +} +export type Body = ParseNode.AsyncGeneratorBody | ParseNode.GeneratorBody | ParseNode.AsyncBody | ParseNode.FunctionBody | ParseNode.AsyncConciseBodyLike | ParseNode.ConciseBodyLike | ParseNode.ClassStaticBlockBody | ParseNode.AssignmentExpressionOrHigher; +export interface ECMAScriptFunctionObject extends BaseFunctionObject { + readonly Environment: EnvironmentRecord; + readonly PrivateEnvironment: PrivateEnvironmentRecord | NullValue; + readonly FormalParameters: ParseNode.FormalParameters; + readonly ECMAScriptCode: Body | null; + readonly ConstructorKind: 'base' | 'derived'; + readonly ScriptOrModule: ScriptRecord | AbstractModuleRecord; + readonly scriptId?: string; + readonly ThisMode: 'lexical' | 'strict' | 'global'; + readonly Strict: boolean; + readonly HomeObject: ObjectValue | UndefinedValue; + readonly SourceText: string; + // -decorator + readonly Fields: readonly ClassFieldDefinitionRecord[]; + readonly PrivateMethods: readonly PrivateElementRecord[]; + // +decorator (Fields => Elements, PrivateMethods => Initializers) + readonly Elements: readonly ClassElementDefinitionRecord[]; + readonly Initializers: readonly FunctionObject[]; + readonly ClassFieldInitializerName: undefined | PropertyKeyValue | PrivateName; + /** + * Note: this is different than InitialName, which is used and observable in Function.prototype.toString. + * This is only used in the inspector. + */ + readonly HostInitialName: PropertyKeyValue | PrivateName; +} +export interface BuiltinFunctionObject extends BaseFunctionObject { + readonly nativeFunction: NativeSteps; + // NON-SPEC + HostCapturedValues?: readonly Value[]; +} +export type FunctionObject = ECMAScriptFunctionObject | BuiltinFunctionObject; +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-ecmascript-function-objects */ +/** https://tc39.es/ecma262/#sec-built-in-function-objects */ +// and +/** https://tc39.es/ecma262/#sec-tail-position-calls */ + +export function hasSourceTextInternalSlot(O: undefined | null | Value): O is FunctionObject & { readonly SourceText:string } { + return !!O && 'SourceText' in O && typeof O.SourceText === 'string'; +} + +export function isECMAScriptFunctionObject(O: undefined | null | Value): O is ECMAScriptFunctionObject { + return !!O && 'ECMAScriptCode' in O; +} + +export function isBuiltinFunctionObject(O: undefined | null | Value): O is BuiltinFunctionObject { + return !!O && 'nativeFunction' in O; +} + +export function isFunctionObject(O: Value): O is FunctionObject { + return 'Call' in O; +} + +/** https://tc39.es/ecma262/#sec-prepareforordinarycall */ +export function PrepareForOrdinaryCall(F: ECMAScriptFunctionObject, newTarget: ObjectValue | UndefinedValue) { + // 1. Assert: Type(newTarget) is Undefined or Object. + Assert(newTarget instanceof UndefinedValue || newTarget instanceof ObjectValue); + // 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; + calleeContext.HostDefined ??= {}; + calleeContext.HostDefined.scriptId = F.scriptId; + // 8. Let localEnv be NewFunctionEnvironment(F, newTarget). + const localEnv = new FunctionEnvironmentRecord(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 PrivateEnvironment of calleeContext to F.[[PrivateEnvironment]]. + calleeContext.PrivateEnvironment = F.PrivateEnvironment; + // 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; +} + +/** https://tc39.es/ecma262/#sec-ordinarycallbindthis */ +export function OrdinaryCallBindThis(F: ECMAScriptFunctionObject, calleeContext: ExecutionContext, thisArgument: Value): PlainCompletion { + // 1. Let thisMode be F.[[ThisMode]]. + const thisMode = F.ThisMode; + // 2. If thisMode is lexical, return NormalCompletion(undefined). + if (thisMode === 'lexical') { + return NormalCompletion(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). + Q(localEnv.BindThisValue(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-ordinarycallevaluatebody */ +export function* OrdinaryCallEvaluateBody(F: ECMAScriptFunctionObject, argumentsList: Arguments) { + // 1. Return the result of EvaluateBody of the parsed code that is F.[[ECMAScriptCode]] passing F and argumentsList as the arguments. + return EnsureCompletion(yield* (EvaluateBody(F.ECMAScriptCode!, F, argumentsList))); +} + +// -decorator (removed in the decorator proposal) +/** https://tc39.es/ecma262/#sec-definefield */ +export function* DefineField(receiver: ObjectValue, fieldRecord: ClassFieldDefinitionRecord): PlainEvaluator { + // 1. Let fieldName be fieldRecord.[[Name]]. + const fieldName = fieldRecord.Name; + // 2. Let initializer be fieldRecord.[[Initializer]]. + const initializer = fieldRecord.Initializer; + // 3. If initializer is not empty, then + let initValue; + if (initializer !== undefined) { + // a. Let initValue be ? Call(initializer, receiver). + initValue = Q(yield* Call(initializer, receiver)); + } else { // 4. Else, let initValue be undefined. + initValue = Value.undefined; + } + // 5. If fieldName is a Private Name, then + if (fieldName instanceof PrivateName) { + // a. Perform ? PrivateFieldAdd(fieldName, receiver, initValue). + Q(yield* PrivateFieldAdd(receiver, fieldName, initValue)); + } else { // 6. Else, + // a. Assert: ! IsPropertyKey(fieldName) is true. + Assert(X(IsPropertyKey(fieldName))); + // b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName, initValue). + Q(yield* CreateDataPropertyOrThrow(receiver, fieldName, initValue)); + } +} + +/** https://tc39.es/ecma262/#sec-initializeinstanceelements */ +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-initializeinstanceelements */ +export function* InitializeInstanceElements(O: ObjectValue, constructor: ECMAScriptFunctionObject | DefaultConstructorBuiltinFunction): PlainEvaluator { + if (surroundingAgent.feature('decorators')) { + const elements = constructor.Elements; + Q(yield* InitializePrivateMethods(O, elements)); + for (const initializer of constructor.Initializers) { + Q(yield* Call(initializer, O)); + } + for (const e of elements) { + if (e instanceof ClassElementDefinitionRecord && (e.Kind === 'field' || e.Kind === 'accessor')) { + Q(yield* InitializeFieldOrAccessor(O, e)); + } + } + } else { + // 1. Let methods be the value of constructor.[[PrivateMethods]]. + const methods = constructor.PrivateMethods; + // 2. For each PrivateElement method of methods, do + for (const method of methods) { + // a. Perform ? PrivateMethodOrAccessorAdd(method, O). + Q(yield* PrivateMethodOrAccessorAdd(O, method)); + } + // 3. Let fields be the value of constructor.[[Fields]]. + const fields = constructor.Fields; + // 4. For each element fieldRecord of fields, do + for (const fieldRecord of fields) { + // a. Perform ? DefineField(O, fieldRecord). + Q(yield* DefineField(O, fieldRecord)); + } + } + // https://tc39.es/proposal-pattern-matching/#sec-initializeinstance + // 5. Append constructor to O.[[ConstructedBy]]. + O.ConstructedBy.push(constructor); +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-initializefieldoraccessor */ +export function* InitializeFieldOrAccessor(receiver: ObjectValue, elementRecord: ClassElementDefinitionRecord): PlainEvaluator { + Assert(elementRecord.Kind === 'field' || elementRecord.Kind === 'accessor'); + const fieldName = elementRecord.Kind === 'accessor' ? elementRecord.BackingStorageKey : elementRecord.Key; + let initValue: Value; + // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) + if (!surroundingAgent.feature('decorators.no-bugfix.1') && elementRecord.Initializers[-1]) { + initValue = Q(yield* Call(elementRecord.Initializers[-1], receiver)); + } else { + initValue = Value.undefined; + } + + for (const initializer of elementRecord.Initializers) { + initValue = Q(yield* Call(initializer, receiver, [initValue])); + } + if (fieldName instanceof PrivateName) { + Q(yield* PrivateFieldAdd(receiver, fieldName, initValue)); + } else { + Assert(IsPropertyKey(fieldName)); + Q(yield* CreateDataPropertyOrThrow(receiver, fieldName, initValue)); + } + for (const initializer of elementRecord.ExtraInitializers) { + Q(yield* Call(initializer, receiver)); + } +} + +/** https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist */ +function* FunctionCallSlot(this: FunctionObject, thisArgument: Value, argumentsList: Arguments): ValueEvaluator { + const F = this; + + // 1. Assert: F is an ECMAScript function object. + Assert(isECMAScriptFunctionObject(F)); + // 2. Let callerContext be the running execution context. + // 3. Let calleeContext be PrepareForOrdinaryCall(F, undefined). + const calleeContext = PrepareForOrdinaryCall(F, Value.undefined); + // 4. Assert: calleeContext is now the running execution context. + Assert(surroundingAgent.runningExecutionContext === calleeContext); + // 5. If F.[[IsClassConstructor]] is true, then + if (F.IsClassConstructor === Value.true) { + // a. Let error be a newly created TypeError object. + const error = surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', F); + // b. NOTE: _error_ is created in _calleeContext_ with _F_'s associated Realm Record. + // c. Remove _calleeContext_ from the execution context stack and restore _callerContext_ as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + // d. Return ThrowCompletion(_error_). + return error; + } + // 6. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). + OrdinaryCallBindThis(F, calleeContext, thisArgument); + // 7. Let result be OrdinaryCallEvaluateBody(F, argumentsList). + const result = yield* 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); + } + Q(result); + // 11. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +} + +/** https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget */ +function* FunctionConstructSlot(this: FunctionObject, argumentsList: Arguments, newTarget: FunctionObject): ValueEvaluator { + const F = this; + + // 1. Assert: F is an ECMAScript function object. + Assert(isECMAScriptFunctionObject(F)); + // 2. Assert: Type(newTarget) is Object. + Assert(newTarget instanceof ObjectValue); + // 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(yield* 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, then + if (kind === 'base') { + // a. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). + OrdinaryCallBindThis(F, calleeContext, thisArgument!); + // b. Let initializeResult be InitializeInstanceElements(thisArgument, F). + const initializeResult = yield* InitializeInstanceElements(thisArgument!, F); + // c. If initializeResult is an abrupt completion, then + if (initializeResult instanceof AbruptCompletion) { + // i. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + // ii. Return Completion(initializeResult). + return Completion(initializeResult); + } + } + // 9. Let constructorEnv be the LexicalEnvironment of calleeContext. + const constructorEnv = calleeContext.LexicalEnvironment; + // 10. Let result be OrdinaryCallEvaluateBody(F, argumentsList). + const result = yield* 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 (result.Value instanceof ObjectValue) { + 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 { + Q(result); + } + // 14. Return ? constructorEnv.GetThisBinding(). + return Q((constructorEnv as FunctionEnvironmentRecord).GetThisBinding() as ObjectValue); +} + +/** https://tc39.es/ecma262/#sec-functionallocate */ +export function OrdinaryFunctionCreate(functionPrototype: ObjectValue, sourceText: string, ParameterList: ParseNode.FormalParameters, Body: Body, thisMode: 'lexical-this' | 'non-lexical-this', Scope: EnvironmentRecord, PrivateEnv: PrivateEnvironmentRecord | NullValue) { + // 1. Assert: Type(functionPrototype) is Object. + Assert(functionPrototype instanceof ObjectValue); + // 2. Let internalSlotsList be the internal slots listed in Table 33. + const internalSlotsList = [ + 'Environment', + 'PrivateEnvironment', + 'FormalParameters', + 'ECMAScriptCode', + 'ConstructorKind', + 'Realm', + 'ScriptOrModule', + 'ThisMode', + 'Strict', + 'HomeObject', + 'SourceText', + surroundingAgent.feature('decorators') ? 'Elements' : 'Fields', + surroundingAgent.feature('decorators') ? 'Initializers' : 'PrivateMethods', + 'ClassFieldInitializerName', + 'IsClassConstructor', + 'HostInitialName', + ]; + // 3. Let F be ! OrdinaryObjectCreate(functionPrototype, internalSlotsList). + const F = X(OrdinaryObjectCreate(functionPrototype, internalSlotsList)) as Mutable; + // 4. Set F.[[Call]] to the definition specified in 10.2.1. + F.Call = FunctionCallSlot; + // 5. Set F.[[SourceText]] to sourceText. + F.SourceText = sourceText; + // 6. Set F.[[FormalParameters]] to ParameterList. + F.FormalParameters = ParameterList; + // 7. Set F.[[ECMAScriptCode]] to Body. + F.ECMAScriptCode = Body; + // 8. If the source text matching Body is strict mode code, let Strict be true; else let Strict be false. + const Strict = isStrictModeCode(Body); + // 9. Set F.[[Strict]] to Strict. + F.Strict = Strict; + // 10. If thisMode is lexical-this, set F.[[ThisMode]] to lexical. + if (thisMode === 'lexical-this') { + F.ThisMode = 'lexical'; + } else if (Strict) { // 11. Else if Strict is true, set F.[[ThisMode]] to strict. + F.ThisMode = 'strict'; + } else { // 12. Else, set F.[[ThisMode]] to global. + F.ThisMode = 'global'; + } + // 13. Set F.[[IsClassConstructor]] to false. + F.IsClassConstructor = Value.false; + // 14. Set F.[[Environment]] to Scope. + F.Environment = Scope; + // 15. Set F.[[PrivateEnvironment]] to PrivateScope. + Assert(!!PrivateEnv); + F.PrivateEnvironment = PrivateEnv; + // 16. Set F.[[ScriptOrModule]] to GetActiveScriptOrModule(). + F.ScriptOrModule = GetActiveScriptOrModule() as ScriptRecord | ModuleRecord; + F.scriptId = getActiveScriptId(); + // 17. Set F.[[Realm]] to the current Realm Record. + F.Realm = surroundingAgent.currentRealmRecord; + // 18. Set F.[[HomeObject]] to undefined. + F.HomeObject = Value.undefined; + // 19. Set F.[[ClassFieldInitializerName]] to empty. + F.ClassFieldInitializerName = undefined; + if (surroundingAgent.feature('decorators')) { + F.Initializers = []; + F.Elements = []; + } else { + F.PrivateMethods = []; + F.Fields = []; + } + // 20. Let len be the ExpectedArgumentCount of ParameterList. + const len = ExpectedArgumentCount(ParameterList); + // 21. Perform ! SetFunctionLength(F, len). + X(SetFunctionLength(F, len)); + // 22. Return F. + return F; +} + +/** https://tc39.es/ecma262/#sec-makeconstructor */ +export function MakeConstructor(F: Mutable | BuiltinFunctionObject, writablePrototype?: BooleanValue, prototype?: ObjectValue): void { + Assert(isECMAScriptFunctionObject(F) || F.Call === BuiltinFunctionCall); + if (isECMAScriptFunctionObject(F)) { + // Assert(!IsConstructor(F)); but not applying type assertion + Assert(![IsConstructor(F)][0]); + Assert(X(IsExtensible(F)) === Value.true && X(HasOwnProperty(F, Value('prototype'))) === Value.false); + F.Construct = FunctionConstructSlot; + } + (F as Mutable).ConstructorKind = 'base'; + if (writablePrototype === undefined) { + writablePrototype = Value.true; + } + if (prototype === undefined) { + prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(DefinePropertyOrThrow(prototype, Value('constructor'), Descriptor({ + Value: F, + Writable: writablePrototype, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + X(DefinePropertyOrThrow(F, Value('prototype'), Descriptor({ + Value: prototype, + Writable: writablePrototype, + Enumerable: Value.false, + Configurable: Value.false, + }))); +} + +/** https://tc39.es/ecma262/#sec-makeclassconstructor */ +export function MakeClassConstructor(F: Mutable): void { + Assert(F.IsClassConstructor === Value.false); + F.IsClassConstructor = Value.true; +} + +/** https://tc39.es/ecma262/#sec-makemethod */ +export function MakeMethod(F: Mutable, homeObject: ObjectValue): void { + Assert(isECMAScriptFunctionObject(F)); + Assert(homeObject instanceof ObjectValue); + F.HomeObject = homeObject; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-definemethodproperty */ +export function* DefineMethodProperty(homeObject: ObjectValue, methodDefinition: ClassElementDefinitionRecord, enumerable: boolean): PlainEvaluator { + // TODO(decorator): spec bug or our bug? + // Assert(isOrdinaryObject(homeObject) && homeObject.Extensible === Value.true && [...homeObject.properties.values()].every((desc) => desc.Configurable === Value.true)); + Assert(methodDefinition.Kind === 'method' || methodDefinition.Kind === 'getter' || methodDefinition.Kind === 'setter' || methodDefinition.Kind === 'accessor'); + const key = methodDefinition.Key; + if (!(key instanceof PrivateName)) { + const desc: Mutable = { Enumerable: Value(enumerable), Configurable: Value.true }; + if (methodDefinition.Kind === 'getter' || methodDefinition.Kind === 'accessor') { + desc.Get = methodDefinition.Get; + } + if (methodDefinition.Kind === 'setter' || methodDefinition.Kind === 'accessor') { + desc.Set = methodDefinition.Set; + } + if (methodDefinition.Kind === 'method') { + desc.Value = methodDefinition.Value; + desc.Writable = Value.true; + } + Q(yield* DefinePropertyOrThrow(homeObject, key, new Descriptor(desc))); + } +} + +/** https://tc39.es/ecma262/#sec-setfunctionname */ +export function SetFunctionName(F: FunctionObject, name: PropertyKeyValue | PrivateName, prefix?: JSStringValue): void { + // 1. Assert: F is an extensible object that does not have a "name" own property. + Assert(skipDebugger(IsExtensible(F)) === Value.true && skipDebugger(HasOwnProperty(F, Value('name'))) === Value.false); + // 2. If Type(name) is Symbol, then + if (name instanceof SymbolValue) { + // 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 = Value(''); + } else { + // c. Else, set name to the string-concatenation of "[", description, and "]". + name = Value(`[${(description as JSStringValue).stringValue()}]`); + } + } else if (name instanceof PrivateName) { // 3. Else if name is a Private Name, then + // a. Set name to name.[[Description]]. + name = name.Description; + } + // 4. If F has an [[InitialName]] internal slot, then + if ('InitialName' in F) { + // a. Set F.[[InitialName]] to name. + (F as Mutable).InitialName = name; + } + if ('HostInitialName' in F) { + // a. Set F.[[InitialName]] to name. + (F as Mutable).HostInitialName = name; + } + // 5. 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 = 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. + } + } + // 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor { [[Value]]: name, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). + X(DefinePropertyOrThrow(F, Value('name'), Descriptor({ + Value: name, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); +} + +/** https://tc39.es/ecma262/#sec-setfunctionlength */ +export function SetFunctionLength(F: FunctionObject, length: number): void { + Assert(isNonNegativeInteger(length) || length === Infinity); + // 1. Assert: F is an extensible object that does not have a "length" own property. + Assert(skipDebugger(IsExtensible(F)) === Value.true && skipDebugger(HasOwnProperty(F, Value('length'))) === Value.false); + // 2. Return ! DefinePropertyOrThrow(F, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). + X(DefinePropertyOrThrow(F, Value('length'), Descriptor({ + Value: toNumberValue(length), + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); +} + +function BuiltinFunctionCall(this: BuiltinFunctionObject, thisArgument: Value, argumentsList: Arguments): ValueEvaluator { + return BuiltinCallOrConstruct(this, thisArgument, argumentsList, Value.undefined); +} + +function BuiltinFunctionConstruct(this: BuiltinFunctionObject, argumentsList: Arguments, newTarget: FunctionObject): ValueEvaluator { + // Assert in the BuiltinCallOrConstruct + return BuiltinCallOrConstruct(this, 'uninitialized', argumentsList, newTarget) as ValueEvaluator; +} + +const { apply } = Reflect; +/** https://tc39.es/ecma262/#sec-builtincallorconstruct */ +function* BuiltinCallOrConstruct(F: BuiltinFunctionObject, thisArgument: Value | 'uninitialized', argumentsList: Arguments, newTarget: FunctionObject | UndefinedValue): ValueEvaluator { + const calleeContext = new ExecutionContext(); + calleeContext.Function = F; + const calleeRealm = F.Realm; + calleeContext.Realm = calleeRealm; + calleeContext.ScriptOrModule = Value.null; + surroundingAgent.executionContextStack.push(calleeContext); + + const isNew = thisArgument === 'uninitialized'; + const thisValue = thisArgument === 'uninitialized' ? Value.undefined : thisArgument; + // Perform any necessary implementation-defined initialization of calleeContext. + surroundingAgent.runningExecutionContext.callSite.constructCall = isNew; + const functionCallContext: FunctionCallContext = { + thisValue, + NewTarget: newTarget, + }; + if (F.Async) { + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + const resultClosure = function* asyncFunctionPrologue() { + let result = apply(F.nativeFunction, F, [argumentsList, functionCallContext]); + if (result && 'next' in result) { + result = yield* result; + } + return ReturnCompletion(Q(result) || Value.undefined); + }; + yield* AsyncFunctionStart(promiseCapability, resultClosure); + surroundingAgent.executionContextStack.pop(calleeContext); + return NormalCompletion(promiseCapability.Promise); + } else { + let result = apply(F.nativeFunction, F, [argumentsList, functionCallContext]); + if (result && 'next' in result) { + result = yield* result; + } + if (result instanceof Completion) { + Assert(result instanceof NormalCompletion || result instanceof ThrowCompletion); + } + + surroundingAgent.executionContextStack.pop(calleeContext); + const value = Q(result); + if (isNew && !(result instanceof ThrowCompletion)) { + Assert(result instanceof ObjectValue); + } + return NormalCompletion(value || Value.undefined); + } +} + +/** https://tc39.es/ecma262/#sec-createbuiltinfunction */ +export function CreateBuiltinFunction(behaviour: NativeSteps, length: number, name: string | PropertyKeyValue | PrivateName, additionalInternalSlotsList: readonly string[], realm?: Realm, prototype?: ObjectValue | NullValue, prefix?: JSStringValue, async = false): BuiltinFunctionObject { + if (typeof name === 'string') { + name = Value(name); + } + // 1. Assert: steps is either a set of algorithm steps or other definition of a function's behaviour provided in this specification. + Assert(typeof behaviour === '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', 'IsClassConstructor'].concat(additionalInternalSlotsList))) as Mutable; + func.Call = BuiltinFunctionCall; + if (behaviour.isConstructor) { + func.Construct = BuiltinFunctionConstruct; + } + func.nativeFunction = behaviour; + func.Async = async; + // 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; + // 10. Set func.[[InitialName]] to null. + func.InitialName = Value.null; + // https://github.com/tc39/ecma262/pull/3212/ + func.IsClassConstructor = Value.false; + // 11. Perform ! SetFunctionLength(func, length). + X(SetFunctionLength(func, length)); + // 12. If prefix is not present, then + if (prefix === undefined) { + // a. Perform ! SetFunctionName(func, name). + X(SetFunctionName(func, name)); + } else { // 13. Else + // a. Perform ! SetFunctionName(func, name, prefix). + X(SetFunctionName(func, name, prefix)); + } + // 13. Return func. + return func; +} + +/** This is a helper function to define non-spec host functions. */ +CreateBuiltinFunction.from = (steps: CanBeNativeSteps, name = steps.name, async = false) => CreateBuiltinFunction(Reflect.apply.bind(null, steps, null), steps.length, name, [], surroundingAgent.currentRealmRecord, undefined, undefined, async); + +export function markBuiltinFunctionAsConstructor(steps: NativeSteps) { + steps.isConstructor = true; + return steps; +} + +/** https://tc39.es/ecma262/#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; +} + +/** https://tc39.es/proposal-shadowrealm/#sec-copynameandlength */ +export function* CopyNameAndLength(F: FunctionObject, Target: FunctionObject, prefix?: string, argCount = 0): PlainEvaluator { + let L = 0; + const targetHasLength = Q(yield* HasOwnProperty(Target, Value('length'))); + if (targetHasLength === Value.true) { + const targetLen = Q(yield* Get(Target, Value('length'))); + if (targetLen instanceof NumberValue) { + if (R(targetLen) === Infinity) { + L = Infinity; + } else if (R(targetLen) === -Infinity) { + L = 0; + } else { + const targetLenAsInt = X(ToIntegerOrInfinity(targetLen)); + Assert(Number.isFinite(targetLenAsInt)); + L = Math.max(targetLenAsInt - argCount, 0); + } + } + } + SetFunctionLength(F, L); + let targetName = Q(yield* Get(Target, Value('name'))); + if (!(targetName instanceof JSStringValue)) { + targetName = Value(''); + } + if (prefix !== undefined) { + SetFunctionName(F, targetName, Value(prefix)); + } else { + SetFunctionName(F, targetName); + } +} + +/** NON-SPEC */ +export function IntrinsicsFunctionToString(F: FunctionObject) { + return X(FunctionProto_toString([], { thisValue: F, NewTarget: Value.undefined })).stringValue(); +} diff --git a/src/abstract-ops/generator-operations.mts b/src/abstract-ops/generator-operations.mts new file mode 100644 index 0000000..0fcf052 --- /dev/null +++ b/src/abstract-ops/generator-operations.mts @@ -0,0 +1,329 @@ +import { + Await, + Completion, + NormalCompletion, + Q, X, + EnsureCompletion, + ReturnCompletion, + ThrowCompletion, +} from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { ExecutionContext } from '../execution-context/ExecutionContext.mts'; +import { + JSStringValue, ObjectValue, UndefinedValue, Value, +} from '../value.mts'; +import { + Evaluate, type ValueEvaluator, type YieldEvaluator, +} from '../evaluator.mts'; +import { __ts_cast__, resume, type Mutable } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + AsyncGeneratorYield, + CreateIteratorResultObject, + OrdinaryObjectCreate, + RequireInternalSlot, + SameValue, + type IteratorRecord, + type OrdinaryObject, +} from './all.mts'; + +/** https://tc39.es/ecma262/#sec-generator-objects */ +export interface GeneratorObject extends OrdinaryObject { + GeneratorState: 'suspendedStart' | 'suspendedYield' | 'executing' | 'completed' | UndefinedValue; + GeneratorContext: ExecutionContext | null; + readonly GeneratorBrand: JSStringValue | undefined; + UnderlyingIterators?: IteratorRecord[]; + // NON-SPEC + HostCapturedValues?: readonly Value[]; +} + +/** https://tc39.es/ecma262/#sec-generatorstart */ +export function GeneratorStart(generator: GeneratorObject, generatorBody: ParseNode.GeneratorBody | (() => YieldEvaluator)): undefined { + // 1. Assert: The value of generator.[[GeneratorState]] is suspended-start. + Assert(generator.GeneratorState === 'suspendedStart'); + // 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. Let closure be a new Abstract Closure with no parameters that captures generatorBody + // and performs the following steps when called: + const closure = function* closure(): ValueEvaluator { + // a. Let acGenContext be the running execution context. + const acGenContext = surroundingAgent.runningExecutionContext; + // b. Let acGenerator be the Generator component of acGenContext. + const acGenerator = acGenContext.Generator as GeneratorObject; + // c. If generatorBody is a Parse Node, then + // i. Let result be Completion(Evaluation of generatorBody). + // d. Else, + // i. Assert: generatorBody is an Abstract Closure with no parameters. + // ii. Let result be generatorBody(). + const result = EnsureCompletion( + // Note: Engine262 can only perform the "If generatorBody is an Abstract Closure" check: + yield* typeof generatorBody === 'function' + ? generatorBody() + : Evaluate(generatorBody), + ); + // e. Assert: If we return here, the generator either threw an exception or performed either + // an implicit or explicit return. + // f. Remove acGenContext 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(acGenContext); + // g. Set acGenerator.[[GeneratorState]] to completed. + acGenerator.GeneratorState = 'completed'; + // h. NOTE: Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with acGenerator can be discarded at this point. + + let resultValue: Value; + if (result instanceof NormalCompletion) { + // i. If result is a normal completion, then + // i. Let resultValue be undefined. + resultValue = Value.undefined; + } else if (result instanceof ReturnCompletion) { + // j. Else if result is a return completion, then + // i. Let resultValue be result.[[Value]]. + resultValue = result.Value; + } else { + // k. Else, + // i. Assert: result is a throw completion. + // ii. Return ? result. + Assert(result instanceof ThrowCompletion); + return Q(result); + } + // l. Return CreateIteratorResultObject(resultValue, true). + return CreateIteratorResultObject(resultValue, Value.true); + }; + + // 5. Set the code evaluation state of genContext such that when evaluation is resumed + // for that execution context, closure will be called with no arguments. + genContext.codeEvaluationState = (function* resumer() { + return yield* closure(); + }()); + + // 6. Set generator.[[GeneratorContext]] to genContext. + generator.GeneratorContext = genContext; + // 7. Return unused. +} + +export function generatorBrandToErrorMessageType(generatorBrand: JSStringValue | undefined) { + let expectedType; + if (generatorBrand !== undefined) { + expectedType = generatorBrand.stringValue(); + if (expectedType.startsWith('%') && expectedType.endsWith('Prototype%')) { + expectedType = expectedType.slice(1, -10).trim(); + if (expectedType.endsWith('Iterator')) { + expectedType = `${expectedType.slice(0, -8).trim()} Iterator`; + } + } + } + return expectedType; +} + +/** https://tc39.es/ecma262/#sec-generatorvalidate */ +export function GeneratorValidate(generator: Value, generatorBrand: JSStringValue | undefined) { + // 1. Perform ? RequireInternalSlot(generator, [[GeneratorState]]). + Q(RequireInternalSlot(generator, 'GeneratorState')); + // 2. Perform ? RequireInternalSlot(generator, [[GeneratorBrand]]). + Q(RequireInternalSlot(generator, 'GeneratorBrand')); + __ts_cast__(generator); + // 3. If generator.[[GeneratorBrand]] is not the same value as generatorBrand, throw a TypeError exception. + const brand = generator.GeneratorBrand; + if ( + brand === undefined || generatorBrand === undefined + ? brand !== generatorBrand + : SameValue(brand, generatorBrand) === Value.false + ) { + return surroundingAgent.Throw( + 'TypeError', + 'NotATypeObject', + generatorBrandToErrorMessageType(generatorBrand) || 'Generator', + generator, + ); + } + // 4. Assert: generator also has a [[GeneratorContext]] internal slot. + Assert('GeneratorContext' in generator); + // 5. Let state be generator.[[GeneratorState]]. + const state = generator.GeneratorState; + // 6. If state is executing, throw a TypeError exception. + if (state === 'executing') { + return surroundingAgent.Throw('TypeError', 'GeneratorRunning'); + } + // 7. Return state. + return state; +} + +/** https://tc39.es/ecma262/#sec-generatorresume */ +export function* GeneratorResume(generator: Value, value: Value | void, generatorBrand: JSStringValue | undefined) { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + const state = Q(GeneratorValidate(generator, generatorBrand)); + __ts_cast__(generator); + // 2. If state is completed, return CreateIteratorResultObject(undefined, true). + if (state === 'completed') { + return X(CreateIteratorResultObject(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(yield* resume(genContext, { type: 'generator-resume', value: NormalCompletion(value || Value.undefined) })); + // 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); +} + +/** https://tc39.es/ecma262/#sec-generatorresumeabrupt */ +export function* GeneratorResumeAbrupt(generator: Value, abruptCompletion: ThrowCompletion | ReturnCompletion, generatorBrand: JSStringValue | undefined) { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + let state = Q(GeneratorValidate(generator, generatorBrand)); + __ts_cast__(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 CreateIteratorResultObject(abruptCompletion.[[Value]], true). + return X(CreateIteratorResultObject(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(yield* resume(genContext, { type: 'generator-resume', value: 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); +} + +/** https://tc39.es/ecma262/#sec-getgeneratorkind */ +export function GetGeneratorKind(): 'async' | 'sync' | 'non-generator' { + // 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'; +} + +/** https://tc39.es/ecma262/#sec-generatoryield */ +export function* GeneratorYield(iterNextObj: ObjectValue): YieldEvaluator { + // 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 as GeneratorObject; + // 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 { type: 'yield', value: iterNextObj }; + Assert(resumptionValue.type === 'generator-resume'); + // 9. Return NormalCompletion(iterNextObj). + return resumptionValue.value; + // 10. NOTE: this returns to the evaluation of the operation that had most previously resumed evaluation of genContext. +} + +/** https://tc39.es/ecma262/#sec-yield */ +export function* Yield(value: Value): YieldEvaluator { + // 1. Let generatorKind be GetGeneratorKind(). + const generatorKind = GetGeneratorKind(); + // 2. If generatorKind is async, return ? AsyncGeneratorYield(? Await(value)). + if (generatorKind === 'async') { + return Q(yield* AsyncGeneratorYield(Q(yield* Await(value)))); + } + // 3. Otherwise, return ? GeneratorYield(CreateIteratorResultObject(value, false)). + return Q(yield* GeneratorYield(CreateIteratorResultObject(value, Value.false))); +} + +/** https://tc39.es/ecma262/#sec-createiteratorfromclosure */ +export function CreateIteratorFromClosure(closure: () => YieldEvaluator, generatorBrand: JSStringValue | undefined, generatorPrototype: ObjectValue, extraSlots?: string[], enclosedValues?: readonly Value[]): Mutable { + Assert(typeof closure === 'function'); + // 1. NOTE: closure can contain uses of the Yield shorthand to yield an IteratorResult object. + // 2. If extraSlots is not present, set extraSlots to a new empty List. + extraSlots ??= []; + // 3. Let internalSlotsList be the list-concatenation of extraSlots and « [[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]] ». + const internalSlotsList = extraSlots.concat(['GeneratorState', 'GeneratorContext', 'GeneratorBrand']); + // 4. Let generator be OrdinaryObjectCreate(generatorPrototype, internalSlotsList). + const generator = OrdinaryObjectCreate(generatorPrototype, internalSlotsList) as Mutable; + // 5. Set generator.[[GeneratorBrand]] to generatorBrand. + generator.GeneratorBrand = generatorBrand; + // 6. Set generator.[[GeneratorState]] to suspended-start. + generator.GeneratorState = 'suspendedStart'; + + // NON-SPEC + if (enclosedValues && extraSlots.includes('HostCapturedValues')) { + generator.HostCapturedValues = enclosedValues.slice(); + } + + // 7. Let callerContext be the running execution context. + const callerContext = surroundingAgent.runningExecutionContext; + // 8. Let calleeContext be a new execution context. + const calleeContext = new ExecutionContext(); + // 9. Set the Function of calleeContext to null. + calleeContext.Function = Value.null; + // 10. Set the Realm of calleeContext to the current Realm Record. + calleeContext.Realm = surroundingAgent.currentRealmRecord; + // 11. Set the ScriptOrModule of calleeContext to callerContext's ScriptOrModule. + calleeContext.ScriptOrModule = callerContext.ScriptOrModule; + calleeContext.HostDefined ??= {}; + calleeContext.HostDefined.scriptId = callerContext.HostDefined?.scriptId; + // 12. If callerContext is not already suspended, suspend callerContext. + // 13. Push calleeContext onto the execution context stack; calleeContext is now the running execution context. + surroundingAgent.executionContextStack.push(calleeContext); + // 14. Perform GeneratorStart(generator, closure). + GeneratorStart(generator, closure); + // 15. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. + surroundingAgent.executionContextStack.pop(calleeContext); + // 16. Return generator. + return generator; +} diff --git a/src/abstract-ops/global-object.mts b/src/abstract-ops/global-object.mts new file mode 100644 index 0000000..bd71c86 --- /dev/null +++ b/src/abstract-ops/global-object.mts @@ -0,0 +1,392 @@ +import { HostEnsureCanCompileStrings, surroundingAgent } from '../host-defined/engine.mts'; +import { ExecutionContext } from '../execution-context/ExecutionContext.mts'; +import { JSStringValue, NullValue, Value } from '../value.mts'; +import { InstantiateFunctionObject } from '../runtime-semantics/all.mts'; +import { + IsStrict, + VarDeclaredNames, + VarScopedDeclarations, + LexicallyScopedDeclarations, + BoundNames, + IsConstantDeclaration, + ContainsArguments, +} from '../static-semantics/all.mts'; +import { + NormalCompletion, + EnsureCompletion, + Q, X, + type ValueEvaluator, + ThrowCompletion, + type PlainCompletion, +} from '../completion.mts'; +import { Parser, wrappedParse } from '../parse.mts'; +import { Evaluate, type PlainEvaluator } from '../evaluator.mts'; +import { __ts_cast__, JSStringSet } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Assert } from './all.mts'; +import { + GetThisEnvironment, + DeclarativeEnvironmentRecord, + EnvironmentRecord, + FunctionEnvironmentRecord, + GlobalEnvironmentRecord, + ObjectEnvironmentRecord, + PrivateEnvironmentRecord, +} from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-global-object */ + +/** https://tc39.es/ecma262/#sec-performeval */ +export function* PerformEval(x: Value, strictCaller: boolean, direct: boolean): ValueEvaluator { + // 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 (!(x instanceof JSStringValue)) { + return x; + } + // 3. Let evalRealm be the current Realm Record. + const evalRealm = surroundingAgent.currentRealmRecord; + // 4. Perform ? HostEnsureCanCompileStrings(evalRealm, « », x, direct). + Q(yield* HostEnsureCanCompileStrings(evalRealm, [], x.stringValue(), direct)); + // 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. Let inClassFieldInitializer be false. + let inClassFieldInitializer = false; + // 9. 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; + } + // v. Let classFieldInitializerName be F.[[ClassFieldInitializerName]]. + const classFieldInitializerName = F.ClassFieldInitializerName; + // vi. If classFieldInitializerName is not empty, set inClassFieldInitializer to true. + if (classFieldInitializerName !== undefined) { + inClassFieldInitializer = true; + } + } + } + // 10. Perform the following substeps in an implementation-dependent order, possibly interleaving parsing and error detection: + // a. Let script be ParseText(! StringToCodePoints(x), Script). + // b. If script is a List of errors, throw a SyntaxError exception. + // c. If script Contains ScriptBody is false, return undefined. + // d. Let body be the ScriptBody of script. + // e. If inFunction is false, and body Contains NewTarget, throw a SyntaxError exception. + // f. If inMethod is false, and body Contains SuperProperty, throw a SyntaxError exception. + // g. If inDerivedConstructor is false, and body Contains SuperCall, throw a SyntaxError exception. + // h. If inClassFieldInitializer is true, and ContainsArguments of body is true, throw a SyntaxError exception. + const privateIdentifiers: string[] = []; + let pointer = direct ? surroundingAgent.runningExecutionContext.PrivateEnvironment : Value.null; + while (!(pointer instanceof NullValue)) { + for (const binding of pointer.Names) { + privateIdentifiers.push(binding.Description.stringValue()); + } + pointer = pointer.OuterPrivateEnvironment; + } + const script = wrappedParse({ source: x.stringValue() }, (parser) => parser.scope.with({ + strict: strictCaller, + newTarget: inFunction, + superProperty: inMethod, + superCall: inDerivedConstructor, + private: privateIdentifiers.length > 0, + }, () => { + privateIdentifiers.forEach((name) => { + parser.scope.privateScope!.names.set(name, new Set(['field'])); + }); + return parser.parseScript(); + })); + const scriptId = surroundingAgent.addDynamicParsedSource(surroundingAgent.currentRealmRecord, x.stringValue(), script); + if (Array.isArray(script)) { + Parser.decorateSyntaxErrorWithScriptId(script[0], scriptId); + return ThrowCompletion(script[0]); + } + if (!script.ScriptBody) { + return Value.undefined; + } + const body = script.ScriptBody; + if (inClassFieldInitializer && ContainsArguments(body)) { + return surroundingAgent.Throw('SyntaxError', 'UnexpectedToken'); + } + // 11. If strictCaller is true, let strictEval be true. + // 12. Else, let strictEval be IsStrict of script. + let strictEval; + if (strictCaller === true) { + strictEval = true; + } else { + strictEval = IsStrict(script); + } + // 13. Let runningContext be the running execution context. + const runningContext = surroundingAgent.runningExecutionContext; + let lexEnv; + let varEnv; + let privateEnv; + // 14. 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. + // 15. If direct is true, then + if (direct === true) { + // a. Let lexEnv be NewDeclarativeEnvironment(runningContext's LexicalEnvironment). + lexEnv = new DeclarativeEnvironmentRecord(runningContext.LexicalEnvironment); + // b. Let varEnv be runningContext's VariableEnvironment. + varEnv = runningContext.VariableEnvironment; + // c. Let privateEnv be runningContext's PrivateEnvironment. + privateEnv = runningContext.PrivateEnvironment; + } else { // 16. Else, + // a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]). + lexEnv = new DeclarativeEnvironmentRecord(evalRealm.GlobalEnv); + // b. Let varEnv be evalRealm.[[GlobalEnv]]. + varEnv = evalRealm.GlobalEnv; + // c. Let privateEnv be null. + privateEnv = Value.null; + } + // 17. If strictEval is true, set varEnv to lexEnv. + if (strictEval === true) { + varEnv = lexEnv; + } + // 18. If runningContext is not already suspended, suspend runningContext. + // 19. Let evalContext be a new ECMAScript code execution context. + const evalContext = new ExecutionContext(); + evalContext.HostDefined ??= {}; + evalContext.HostDefined.scriptId = scriptId; + // 20. Set evalContext's Function to null. + evalContext.Function = Value.null; + // 21. Set evalContext's Realm to evalRealm. + evalContext.Realm = evalRealm; + // 22. Set evalContext's ScriptOrModule to runningContext's ScriptOrModule. + evalContext.ScriptOrModule = runningContext.ScriptOrModule; + // 23. Set evalContext's VariableEnvironment to varEnv. + evalContext.VariableEnvironment = varEnv; + // 24. Set evalContext's LexicalEnvironment to lexEnv. + evalContext.LexicalEnvironment = lexEnv; + // 25. Set evalContext's PrivateEnvironment to privateEnv. + evalContext.PrivateEnvironment = privateEnv; + // 26. Push evalContext onto the execution context stack. + surroundingAgent.executionContextStack.push(evalContext); + // 27. Let result be EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval). + let result: PlainCompletion = EnsureCompletion(yield* EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)); + // 28. If result.[[Type]] is normal, then + if (result.Type === 'normal') { + // a. Set result to the result of evaluating body. + result = EnsureCompletion(yield* Evaluate(body)); + } + // 29. 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); + } + // 30. Suspend evalContext and remove it from the execution context stack. + // 31. Resume the context that is now on the top of the execution context stack as the running execution context. + surroundingAgent.executionContextStack.pop(evalContext); + // 32. Return Completion(result). + return Q(result)!; +} + +/** https://tc39.es/ecma262/#sec-evaldeclarationinstantiation */ +export function* EvalDeclarationInstantiation(body: ParseNode.ScriptBody, varEnv: EnvironmentRecord, lexEnv: DeclarativeEnvironmentRecord, privateEnv: PrivateEnvironmentRecord | NullValue, strict: boolean): PlainEvaluator { + // 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 ((yield* 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: EnvironmentRecord = lexEnv; + // c. Assert: The following loop will terminate. + // d. Repeat, while thisEnv is not the same as varEnv, + while (thisEnv !== varEnv) { + __ts_cast__(thisEnv); + // 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 ((yield* 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 as EnvironmentRecord; + } + } + // 4. Let privateIdentifiers be a new empty List. + const privateIdentifiers = []; + // 5. Let pointer be privateEnv. + let pointer = privateEnv; + // 6. Repeat, while pointer is not null, + while (!(pointer instanceof NullValue)) { + // a. For each Private Name binding of pointer.[[Names]], do + for (const binding of pointer.Names) { + // i. If privateIdentifiers does not contain binding.[[Description]], append binding.[[Description]] to privateIdentifiers. + privateIdentifiers.push(binding.Description); + } + // b. Set pointer to pointer.[[OuterPrivateEnvironment]]. + pointer = pointer.OuterPrivateEnvironment; + } + // 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception. + Assert(true); + // 8. Let functionsToInitialize be a new empty List. + const functionsToInitialize = []; + // 9. Let declaredFunctionNames be a new empty List. + const declaredFunctionNames = new JSStringSet(); + // 10. 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(yield* 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); + } + } + } + // 11. NOTE: Annex B.3.3.3 adds additional steps at this point. + // 12. Let declaredVarNames be a new empty List. + const declaredVarNames = new JSStringSet(); + // 13. 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(yield* 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); + } + } + } + } + } + // 14. 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. + // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. + const lexDeclarations = LexicallyScopedDeclarations(body); + // 16. 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(yield* lexEnv.CreateMutableBinding(dn, Value.false)); + } + } + } + // 17. 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, privateEnv); + // c. If varEnv is a global Environment Record, then + if (varEnv instanceof GlobalEnvironmentRecord) { + // i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true). + Q(yield* varEnv.CreateGlobalFunctionBinding(fn, fo, Value.true)); + } else { // d. Else, + // i. Let bindingExists be varEnv.HasBinding(fn). + const bindingExists = yield* varEnv.HasBinding(fn); + // ii. If bindingExists is false, then + if (bindingExists === Value.false) { + // 1. Let status be ! varEnv.CreateMutableBinding(fn, true). + // 2. Assert: status is not an abrupt completion because of validation preceding step 12. + X(varEnv.CreateMutableBinding(fn, Value.true)); + // 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)); + } + } + } + // 18. 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(yield* varEnv.CreateGlobalVarBinding(vn, Value.true)); + } else { // b. Else, + // i. Let bindingExists be varEnv.HasBinding(vn). + const bindingExists = yield* varEnv.HasBinding(vn); + // ii. If bindingExists is false, then + if (bindingExists === Value.false) { + // 1. Let status be ! varEnv.CreateMutableBinding(vn, true). + // 2. Assert: status is not an abrupt completion because of validation preceding step 12. + X(varEnv.CreateMutableBinding(vn, Value.true)); + // 3. Perform ! varEnv.InitializeBinding(vn, undefined). + X(varEnv.InitializeBinding(vn, Value.undefined)); + } + } + } + // 19. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/abstract-ops/immutable-prototype-objects.mts b/src/abstract-ops/immutable-prototype-objects.mts new file mode 100644 index 0000000..2c5e210 --- /dev/null +++ b/src/abstract-ops/immutable-prototype-objects.mts @@ -0,0 +1,21 @@ +import { + BooleanValue, NullValue, ObjectValue, Value, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { Assert, SameValue, type ExoticObject } from './all.mts'; + +export type ImmutablePrototypeObject = ExoticObject; +/** https://tc39.es/ecma262/#sec-set-immutable-prototype */ +export function* SetImmutablePrototype(O: ObjectValue, V: Value): ValueEvaluator { + // 1. Assert: Either Type(V) is Object or Type(V) is Null. + Assert(V instanceof ObjectValue || V instanceof NullValue); + // 2. Let current be ? O.[[GetPrototypeOf]](). + const current = Q(yield* 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/src/abstract-ops/import-calls.mts b/src/abstract-ops/import-calls.mts new file mode 100644 index 0000000..c1dd122 --- /dev/null +++ b/src/abstract-ops/import-calls.mts @@ -0,0 +1,112 @@ +// This file covers abstract operations defined in +// https://tc39.es/ecma262/#sec-import-calls + +import { + AbstractModuleRecord, + Assert, + Call, CreateBuiltinFunction, CreateListIteratorRecord, GatherAsynchronousTransitiveDependencies, GetModuleNamespace, NewPromiseCapability, PerformPromiseThen, PromiseCapabilityRecord, surroundingAgent, Value, + type Arguments, + type PromiseObject, +} from '../index.mts'; +import { + AbruptCompletion, ValueOfNormalCompletion, X, type PlainCompletion, +} from '../completion.mts'; +import { PerformPromiseAll } from '../intrinsics/Promise.mts'; + +/** https://tc39.es/ecma262/#sec-ContinueDynamicImport */ +export function ContinueDynamicImport( + promiseCapability: PromiseCapabilityRecord, + moduleCompletion: PlainCompletion, + phase: 'defer' | 'evaluation', +) { + // 1. If moduleCompletion is an abrupt completion, then + if (moduleCompletion instanceof AbruptCompletion) { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »). + X(Call(promiseCapability.Reject, Value.undefined, [moduleCompletion.Value])); + // b. Return unused. + return; + } + // 2. Let module be moduleCompletion.[[Value]]. + const module = ValueOfNormalCompletion(moduleCompletion); + + // 3. Let loadPromise be module.LoadRequestedModules(). + const loadPromise = module.LoadRequestedModules(); + + // 4. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called: + const rejectedClosure = ([reason = Value.undefined]: Arguments): void => { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »). + X(Call(promiseCapability.Reject, Value.undefined, [reason])); + // b. Return unused. + }; + // 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + const onRejected = CreateBuiltinFunction(rejectedClosure, 1, Value(''), []); + + // 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, and onRejected and performs the following steps when called: + function* linkAndEvaluateClosure() { + // a. Let link be Completion(module.Link()). + const link = module.Link(); + // b. If link is an abrupt completion, then + if (link instanceof AbruptCompletion) { + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »). + X(Call(promiseCapability.Reject, Value.undefined, [link.Value])); + // ii. Return unused. + return; + } + + let evaluatePromise: PromiseObject; + // c. Let evaluatePromise be module.Evaluate(). + evaluatePromise = yield* module.Evaluate(); + + // d. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and promiseCapability and performs the following steps when called: + const fulfilledClosure = () => { + // i. Let namespace be GetModuleNamespace(module). + const namespace = GetModuleNamespace(module, phase); + // ii. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »). + X(Call(promiseCapability.Resolve, Value.undefined, [namespace])); + // iii. Return unused. + }; + + // e. If phase is "defer", then + if (phase === 'defer') { + // i. Let evaluationList be module.GatherAsynchronousTransitiveDependencies(). + const evaluationList = GatherAsynchronousTransitiveDependencies(module); + // ii. If evaluationList is empty, then + if (evaluationList.length === 0) { + // 1. Perform fulfilledClosure(). + fulfilledClosure(); + // 2. Return unused. + return; + } + // iii. Let asyncDepsEvaluationPromises be a new empty List. + const asyncDepsEvaluationPromises = []; + // iv. For each dep in evaluationList, append dep.Evaluate() to asyncDepsEvaluationPromises. + for (const dep of evaluationList) { + asyncDepsEvaluationPromises.push(yield* dep.Evaluate()); + } + // v. Let iterator be CreateListIteratorRecord(asyncDepsEvaluationPromises). + const iterator = CreateListIteratorRecord(asyncDepsEvaluationPromises); + // vi. Let pc be ! NewPromiseCapability(%Promise%). + const pc = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // vii. Let evaluatePromise be ! PerformPromiseAll(iterator, %Promise%, pc, %Promise.resolve%). + evaluatePromise = X(PerformPromiseAll(iterator, surroundingAgent.intrinsic('%Promise%'), pc, surroundingAgent.intrinsic('%Promise.resolve%'))) as PromiseObject; + } else { // f. Else, + // i. Assert: phase is EVALUATION. + Assert(phase === 'evaluation'); + // ii. Let evaluatePromise be module.Evaluate(). + evaluatePromise = yield* module.Evaluate(); + } + + // e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). + const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 0, Value(''), []); + + // f. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected). + PerformPromiseThen(evaluatePromise!, onFulfilled, onRejected); + // g. Return unused. + } + // 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »). + const linkAndEvaluate = CreateBuiltinFunction(linkAndEvaluateClosure, 0, Value(''), []); + + // 8. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected). + PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected); + // 9. Return unused. +} diff --git a/src/abstract-ops/iterator-operations.mts b/src/abstract-ops/iterator-operations.mts new file mode 100644 index 0000000..f2088d4 --- /dev/null +++ b/src/abstract-ops/iterator-operations.mts @@ -0,0 +1,319 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BooleanValue, + JSStringValue, + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, +} from '../value.mts'; +import { + Completion, + EnsureCompletion, + IfAbruptRejectPromise, + Q, X, + Await, + NormalCompletion, + type ValueEvaluator, + ThrowCompletion, + AbruptCompletion, +} from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import type { AsyncFromSyncIteratorObject } from '../intrinsics/AsyncFromSyncIteratorPrototype.mts'; +import type { + Evaluator, PlainEvaluator, YieldEvaluator, +} from '../evaluator.mts'; +import { + Assert, + Call, + CreateBuiltinFunction, + Get, + GetMethod, + PromiseResolve, + OrdinaryObjectCreate, + PerformPromiseThen, + ToBoolean, + CreateIteratorFromClosure, + type FunctionObject, + PromiseCapabilityRecord, + CreateDataPropertyOrThrow, + GeneratorYield, +} from './all.mts'; +import type { ValueCompletion, PromiseObject, OrdinaryObject } from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-operations-on-iterator-objects */ +// and +/** https://tc39.es/ecma262/#sec-iteration */ + +export interface IteratorRecord { + readonly Iterator: ObjectValue; + readonly NextMethod: Value; + Done: BooleanValue; +} + +export interface IteratorObject extends OrdinaryObject { + Iterated: IteratorRecord; +} + +/** https://tc39.es/ecma262/#sec-getiteratordirect */ +export function* GetIteratorDirect(obj: ObjectValue): PlainEvaluator { + const nextMethod = Q(yield* Get(obj, Value('next'))); + const iteratorRecord: IteratorRecord = { + Iterator: obj, + NextMethod: nextMethod, + Done: Value.false, + }; + return iteratorRecord; +} + +/** https://tc39.es/ecma262/#sec-getiteratorfrommethod */ +export function* GetIteratorFromMethod(obj: Value, method: FunctionObject): PlainEvaluator { + const iterator = Q(yield* Call(method, obj)); + if (!(iterator instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', iterator); + } + return yield* GetIteratorDirect(iterator); +} + +/** https://tc39.es/ecma262/#sec-getiterator */ +export function* GetIterator(obj: Value, kind: 'sync' | 'async'): PlainEvaluator { + let method; + if (kind === 'async') { + method = Q(yield* GetMethod(obj, wellKnownSymbols.asyncIterator)); + if (method === Value.undefined) { + const syncMethod = Q(yield* GetMethod(obj, wellKnownSymbols.iterator)); + if (syncMethod instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'NotIterable', obj); + } + const syncIteratorRecord = Q(yield* GetIteratorFromMethod(obj, syncMethod)); + return CreateAsyncFromSyncIterator(syncIteratorRecord); + } + } else { + method = Q(yield* GetMethod(obj, wellKnownSymbols.iterator)); + } + if (method instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'NotIterable', obj); + } + return yield* GetIteratorFromMethod(obj, method); +} + +export type PrimitiveHanding = 'iterate-string-primitives' | 'reject-primitives' +export function* GetIteratorFlattenable(obj: Value, primitiveHandling: PrimitiveHanding): PlainEvaluator { + if (!(obj instanceof ObjectValue)) { + if (primitiveHandling === 'reject-primitives') { + return surroundingAgent.Throw('TypeError', 'NotAnObject', obj); + } + Assert(primitiveHandling === 'iterate-string-primitives'); + if (!(obj instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', obj); + } + } + const method = Q(yield* GetMethod(obj, wellKnownSymbols.iterator)); + let iterator; + if (method instanceof UndefinedValue) { + iterator = obj; + } else { + iterator = Q(yield* Call(method, obj)); + } + if (!(iterator instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', iterator); + } + return yield* GetIteratorDirect(iterator); +} + +/** https://tc39.es/ecma262/#sec-iteratornext */ +export function* IteratorNext(iteratorRecord: IteratorRecord, value?: Value): ValueEvaluator { + let result; + if (!value) { + result = EnsureCompletion(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator)); + } else { + result = EnsureCompletion(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator, [value])); + } + if (result instanceof ThrowCompletion) { + iteratorRecord.Done = Value.true; + return Q(result); + } + result = X(result); + if (!(result instanceof ObjectValue)) { + iteratorRecord.Done = Value.true; + return surroundingAgent.Throw('TypeError', 'NotAnObject', result); + } + return result; +} + +/** https://tc39.es/ecma262/#sec-iteratorcomplete */ +export function* IteratorComplete(iteratorResult: ObjectValue): ValueEvaluator { + return ToBoolean(Q(yield* Get(iteratorResult, Value('done')))); +} + +/** https://tc39.es/ecma262/#sec-iteratorvalue */ +export function IteratorValue(iterResult: ObjectValue): ValueEvaluator { + return Get(iterResult, Value('value')); +} + +/** https://tc39.es/ecma262/#sec-iteratorstep */ +export function* IteratorStep(iteratorRecord: IteratorRecord): PlainEvaluator { + const result = Q(yield* IteratorNext(iteratorRecord)); + let done: ValueCompletion = EnsureCompletion(yield* IteratorComplete(result)); + if (done instanceof ThrowCompletion) { + iteratorRecord.Done = Value.true; + return done; + } + done = X(done); + if (done === Value.true) { + iteratorRecord.Done = Value.true; + return 'done'; + } + return result; +} + +/** https://tc39.es/ecma262/#sec-iteratorstepvalue */ +export function* IteratorStepValue(iteratorRecord: IteratorRecord): PlainEvaluator { + const result = Q(yield* IteratorStep(iteratorRecord)); + if (result === 'done') { + return 'done'; + } + const value = EnsureCompletion(yield* IteratorValue(result)); + if (value instanceof ThrowCompletion) { + iteratorRecord.Done = Value.true; + } + return value; +} + +/** https://tc39.es/ecma262/#sec-iteratorclose */ +export function* IteratorClose>(iteratorRecord: IteratorRecord, completion: C): Evaluator { + Assert(iteratorRecord.Iterator instanceof ObjectValue); + const iterator = iteratorRecord.Iterator; + let innerResult: ValueCompletion = EnsureCompletion(yield* GetMethod(iterator, Value('return'))); + if (innerResult instanceof NormalCompletion) { + const ret = innerResult.Value; + if (ret === Value.undefined) { + return completion; + } + innerResult = EnsureCompletion(yield* Call(ret, iterator)); + } + if (completion instanceof ThrowCompletion) { + return completion; + } + if (innerResult instanceof ThrowCompletion) { + return innerResult; + } + if (!(innerResult.Value instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult.Value); + } + return completion; +} + +/** https://tc39.es/ecma262/#sec-iteratorcloseall */ +export function* IteratorCloseAll(iters: Iterable, completion: Completion): Evaluator> { + for (const iter of [...iters].reverse()) { + completion = yield* IteratorClose(iter, completion); + } + return completion; +} + +/** https://tc39.es/ecma262/#sec-asynciteratorclose */ +export function* AsyncIteratorClose>(iteratorRecord: IteratorRecord, completion: C | T) { + Assert(iteratorRecord.Iterator instanceof ObjectValue); + const iterator = iteratorRecord.Iterator; + let innerResult: NormalCompletion | ThrowCompletion = EnsureCompletion(yield* GetMethod(iterator, Value('return'))); + if (innerResult instanceof NormalCompletion) { + const ret = innerResult.Value; + if (ret instanceof UndefinedValue) { + return completion; + } + innerResult = EnsureCompletion(yield* Call(ret, iterator)); + if (innerResult instanceof NormalCompletion) { + innerResult = EnsureCompletion(yield* Await(innerResult.Value)); + } + } + if (completion instanceof ThrowCompletion) { + return completion; + } + if (innerResult instanceof ThrowCompletion) { + return innerResult; + } + if (!(innerResult.Value instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult.Value); + } + return completion; +} + +/** https://tc39.es/ecma262/#sec-createiterresultobject */ +export function CreateIteratorResultObject(value: Value, done: BooleanValue) { + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataPropertyOrThrow(obj, Value('value'), value)); + X(CreateDataPropertyOrThrow(obj, Value('done'), done)); + return obj; +} + +/** https://tc39.es/ecma262/#sec-createlistiteratorRecord */ +export function CreateListIteratorRecord(list: Iterable): IteratorRecord { + const closure = function* closure(): YieldEvaluator { + for (const E of list) { + Q(yield* GeneratorYield(CreateIteratorResultObject(E, Value.false))); + } + return NormalCompletion(Value.undefined); + }; + const iterator = CreateIteratorFromClosure(closure, undefined, surroundingAgent.intrinsic('%Iterator.prototype%')); + return { + Iterator: iterator, + NextMethod: surroundingAgent.intrinsic('%GeneratorFunction.prototype.prototype.next%'), + Done: Value.false, + }; +} + +/** https://tc39.es/ecma262/#sec-iteratortolist */ +export function* IteratorToList(iteratorRecord: IteratorRecord): PlainEvaluator { + const list: Value[] = []; + while (true) { + const next = Q(yield* IteratorStepValue(iteratorRecord)); + if (next === 'done') { + return list; + } + list.push(next); + } +} + +/** https://tc39.es/ecma262/#sec-createasyncfromsynciterator */ +export function CreateAsyncFromSyncIterator(syncIteratorRecord: IteratorRecord): IteratorRecord { + const asyncIterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncFromSyncIteratorPrototype%'), [ + 'SyncIteratorRecord', + ]) as Mutable; + asyncIterator.SyncIteratorRecord = syncIteratorRecord; + const nextMethod = X(Get(asyncIterator, Value('next'))); + return { + Iterator: asyncIterator, + NextMethod: nextMethod, + Done: Value.false, + }; +} + +/** https://tc39.es/ecma262/#sec-asyncfromsynciteratorcontinuation */ +export function* AsyncFromSyncIteratorContinuation(result: ObjectValue, promiseCapability: PromiseCapabilityRecord, syncIteratorRecord: IteratorRecord, closeOnRejection: BooleanValue): ValueEvaluator { + const done = yield* IteratorComplete(result); + IfAbruptRejectPromise(done, promiseCapability); + __ts_cast__(done); + const value = yield* IteratorValue(result); + IfAbruptRejectPromise(value, promiseCapability); + __ts_cast__(value); + let valueWrapper = yield* PromiseResolve(surroundingAgent.intrinsic('%Promise%'), value); + if (valueWrapper instanceof AbruptCompletion && done === Value.false && closeOnRejection === Value.true) { + valueWrapper = yield* IteratorClose(syncIteratorRecord, valueWrapper); + } + IfAbruptRejectPromise(valueWrapper, promiseCapability); + __ts_cast__(valueWrapper); + const unwrap = ([v = Value.undefined]: Arguments) => CreateIteratorResultObject(v, done); + const onFullfilled = CreateBuiltinFunction(unwrap, 1, Value(''), []); + let onRejected; + if (done === Value.true || closeOnRejection === Value.false) { + onRejected = Value.undefined; + } else { + const closeIterator = ([error = Value.undefined]: Arguments) => IteratorClose(syncIteratorRecord, ThrowCompletion(error)); + onRejected = CreateBuiltinFunction(closeIterator, 1, Value(''), []); + } + PerformPromiseThen(valueWrapper, onFullfilled, onRejected, promiseCapability); + return promiseCapability.Promise; +} diff --git a/src/abstract-ops/keyed-collections.mts b/src/abstract-ops/keyed-collections.mts new file mode 100644 index 0000000..2bcc0e6 --- /dev/null +++ b/src/abstract-ops/keyed-collections.mts @@ -0,0 +1,19 @@ +import { + F, R, +} from './all.mts'; +import { + NumberValue, Value, +} from '#self'; + +// This file covers abstract operations defined in +// https://tc39.es/ecma262/#sec-abstract-operations-for-keyed-collections + +/** https://tc39.es/ecma262/#sec-canonicalizekeyedcollectionkey */ +export function CanonicalizeKeyedCollectionKey(key : Value) : Value { + // 1. If key is -0𝔽, return +0𝔽. + if (key instanceof NumberValue && Object.is(R(key), -0)) { + key = F(+0); + } + // 2. Return key. + return key; +} diff --git a/src/abstract-ops/math.mts b/src/abstract-ops/math.mts new file mode 100644 index 0000000..604a3a7 --- /dev/null +++ b/src/abstract-ops/math.mts @@ -0,0 +1,8 @@ +export function abs(x: number): number +export function abs(x: bigint): bigint +export function abs(x: bigint | number): bigint | number { + if (x < 0) { + return -x; + } + return x; +} diff --git a/src/abstract-ops/module-namespace-exotic-objects.mts b/src/abstract-ops/module-namespace-exotic-objects.mts new file mode 100644 index 0000000..7eca98c --- /dev/null +++ b/src/abstract-ops/module-namespace-exotic-objects.mts @@ -0,0 +1,305 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, X } from '../completion.mts'; +import { AbstractModuleRecord, CyclicModuleRecord, ResolvedBindingRecord } from '../modules.mts'; +import { + SymbolValue, + Value, + Descriptor, + wellKnownSymbols, + JSStringValue, + type ObjectInternalMethods, + UndefinedValue, + type PropertyKeyValue, + ObjectValue, + BooleanValue, +} from '../value.mts'; +import { + JSStringSet, type Mutable, +} from '../helpers.mts'; +import { + Assert, + CompareArrayElements, + SameValue, + MakeBasicObject, + IsPropertyKey, + IsAccessorDescriptor, + SetImmutablePrototype, + OrdinaryGetOwnProperty, + OrdinaryDefineOwnProperty, + OrdinaryHasProperty, + OrdinaryGet, + OrdinaryDelete, + OrdinaryOwnPropertyKeys, + GetModuleNamespace, R, + type ExoticObject, + EvaluateModuleSync, + GetImportedModule, +} from './all.mts'; +import type { ModuleRecord, PlainEvaluator } from '#self'; + +export interface ModuleNamespaceObject extends ExoticObject { + readonly Module: AbstractModuleRecord; + readonly Exports: JSStringSet; + readonly Deferred: boolean; +} + +export function isModuleNamespaceObject(V: Value): V is ModuleNamespaceObject { + return V instanceof ObjectValue && 'Module' in V; +} + +const InternalMethods = { + * GetPrototypeOf() { + return Value.null; + }, + * SetPrototypeOf(V) { + return Q(yield* SetImmutablePrototype(this, V)); + }, + * IsExtensible() { + return Value.false; + }, + * PreventExtensions() { + return Value.true; + }, + * GetOwnProperty(P) { + const O = this; + + if (IsSymbolLikeNamespaceKey(P, O)) { + return OrdinaryGetOwnProperty(O, P); + } + const exports = Q(yield* GetModuleExportsList(O)); + if (!exports.has(P as JSStringValue)) { + return Value.undefined; + } + const value = Q(yield* O.Get(P, O)); + return Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.false, + }); + }, + * DefineOwnProperty(P, Desc) { + const O = this; + + if (IsSymbolLikeNamespaceKey(P, O)) { + return yield* OrdinaryDefineOwnProperty(O, P, Desc); + } + + const current = Q(yield* O.GetOwnProperty(P)); + if (current instanceof UndefinedValue) { + 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; + }, + * HasProperty(P) { + const O = this; + + if (IsSymbolLikeNamespaceKey(P, O)) { + return yield* OrdinaryHasProperty(O, P); + } + const exports = Q(yield* GetModuleExportsList(O)); + if (exports.has(P as JSStringValue)) { + return Value.true; + } + return Value.false; + }, + /** https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver */ + * Get(P, Receiver) { + const O = this; + + // 1. Assert: IsPropertyKey(P) is true. + Assert(IsPropertyKey(P)); + // 2. If Type(P) is Symbol, then + if (IsSymbolLikeNamespaceKey(P, O)) { + // a. Return ? OrdinaryGet(O, P, Receiver). + return yield* OrdinaryGet(O, P, Receiver); + } + const exports = Q(yield* GetModuleExportsList(O)); + // 4. If P is not an element of exports, return undefined. + if (!exports.has(P as JSStringValue)) { + 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 as JSStringValue); + // 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 instanceof UndefinedValue)); + // 10. If binding.[[BindingName]] is ~namespace~, then + if (binding.BindingName === 'namespace') { + // a. Return ? GetModuleNamespace(targetModule). + return Q(GetModuleNamespace(targetModule, 'evaluation')); + } + // 11. Let targetEnv be targetModule.[[Environment]]. + const targetEnv = targetModule.Environment; + // 12. If targetEnv is undefined, throw a ReferenceError exception. + if (!targetEnv) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', P); + } + // 13. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true). + return Q(yield* targetEnv.GetBindingValue(binding.BindingName, Value.true)); + }, + * Set() { + return Value.false; + }, + * Delete(P) { + const O = this; + + Assert(IsPropertyKey(P)); + if (IsSymbolLikeNamespaceKey(P, O)) { + return Q(yield* OrdinaryDelete(O, P)); + } + const exports = Q(yield* GetModuleExportsList(O)); + if (exports.has(P as JSStringValue)) { + return Value.false; + } + return Value.true; + }, + * OwnPropertyKeys() { + const O = this; + + let exports; + exports = Q(yield* GetModuleExportsList(O)); + if (O.Deferred && exports.has('then')) { + exports = [...exports].filter((x) => x.stringValue() !== 'then'); + } + + const symbolKeys = X(OrdinaryOwnPropertyKeys(O)); + return [...exports, ...symbolKeys]; + }, +} satisfies Partial>; + +/** https://tc39.es/ecma262/#sec-modulenamespacecreate */ +export function ModuleNamespaceCreate( + module: AbstractModuleRecord, + exports: readonly JSStringValue[], + phase: 'defer' | 'evaluation', +): ModuleNamespaceObject { + // 2. Let internalSlotsList be the internal slots listed in Table 31. + const internalSlotsList = ['Module', 'Exports']; + // 3. Let M be MakeBasicObject(internalSlotsList). + const M = MakeBasicObject(internalSlotsList) as Mutable; + // 4. Set M's essential internal methods to the definitions specified in 10.4.6. + /** https://tc39.es/ecma262/#sec-module-namespace-exotic-objects */ + M.GetPrototypeOf = InternalMethods.GetPrototypeOf; + M.SetPrototypeOf = InternalMethods.SetPrototypeOf; + M.IsExtensible = InternalMethods.IsExtensible; + M.PreventExtensions = InternalMethods.PreventExtensions; + M.GetOwnProperty = InternalMethods.GetOwnProperty; + M.DefineOwnProperty = InternalMethods.DefineOwnProperty; + M.HasProperty = InternalMethods.HasProperty; + M.Get = InternalMethods.Get; + M.Set = InternalMethods.Set; + M.Delete = InternalMethods.Delete; + M.OwnPropertyKeys = InternalMethods.OwnPropertyKeys; + // 5. Set M.[[Module]] to module. + M.Module = module; + // 6. Let sortedExports be a List whose elements are the elements of exports, sorted according to lexicographic code unit order. + const sortedExports = [...exports].sort((x, y) => { + const result = X(CompareArrayElements(x, y, Value.undefined)); + return R(result); + }); + // 7. Set M.[[Exports]] to sortedExports. + M.Exports = new JSStringSet(sortedExports); + let toStringTag: JSStringValue; + // 9. If phase is defer, then + if (phase === 'defer') { + // a. Assert: module.[[DeferredNamespace]] is empty. + Assert(module.DeferredNamespace === undefined); + // b. Set module.[[DeferredNamespace]] to M. + (module as Mutable).DeferredNamespace = M; + // c. Set M.[[Deferred]] to true. + M.Deferred = true; + // d. Let toStringTag be "Deferred Module". + toStringTag = Value('Deferred Module'); + } else { // 10. Else, + // a. Assert: module.[[Namespace]] is empty. + Assert(module.Namespace === undefined); + // b. Set module.[[Namespace]] to M. + (module as Mutable).Namespace = M; + // c. Set M.[[Deferred]] to false. + M.Deferred = false; + // d. Let toStringTag be "Module". + toStringTag = Value('Module'); + } + // 11. Create an own data property of M named %Symbol.toStringTag% whose [[Value]] is toStringTag whose [[Writable]], [[Enumerable]], and [[Configurable]] attributes are false. + M.properties.set(wellKnownSymbols.toStringTag, Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + Value: toStringTag, + })); + // 10. Return M. + return M; +} + +/** https://tc39.es/proposal-defer-import-eval/#sec-IsSymbolLikeNamespaceKey */ +function IsSymbolLikeNamespaceKey(P: PropertyKeyValue, ns: ModuleNamespaceObject): P is SymbolValue { + if (P instanceof SymbolValue) { + return true; + } + if (ns.Deferred && P.stringValue() === 'then') { + return true; + } + return false; +} + +/** https://tc39.es/proposal-defer-import-eval/#sec-GetModuleExportsList */ +function* GetModuleExportsList(O: ModuleNamespaceObject): PlainEvaluator { + if (O.Deferred) { + const m = O.Module; + if (ReadyForSyncExecution(m) === Value.false) { + return surroundingAgent.Throw('TypeError', 'DeferredModuleNotReady', m); + } + Q(yield* EvaluateModuleSync(m)); + } + return O.Exports; +} + +/** https://tc39.es/proposal-defer-import-eval/#sec-ReadyForSyncExecution */ +export function ReadyForSyncExecution(module: ModuleRecord, seen?: Set): BooleanValue { + if (!(module instanceof CyclicModuleRecord)) { + return Value.true; + } + seen ??= new Set(); + if (seen.has(module)) { + return Value.true; + } + seen.add(module); + if (module.Status === 'evaluated') { + return Value.true; + } + if (module.Status === 'evaluating' || module.Status === 'evaluating-async') { + return Value.false; + } + Assert(module.Status === 'linked'); + if (module.HasTLA === Value.true) { + return Value.false; + } + for (const request of module.RequestedModules) { + const requiredModule = GetImportedModule(module, request); + if (ReadyForSyncExecution(requiredModule, seen) === Value.false) { + return Value.false; + } + } + return Value.true; +} diff --git a/src/abstract-ops/module-records.mts b/src/abstract-ops/module-records.mts new file mode 100644 index 0000000..5c44920 --- /dev/null +++ b/src/abstract-ops/module-records.mts @@ -0,0 +1,579 @@ +import { + surroundingAgent, HostLoadImportedModule, HostPromiseRejectionTracker, +} from '../host-defined/engine.mts'; +import { IncrementModuleAsyncEvaluationCount } from '../execution-context/Agent.mts'; +import { + CyclicModuleRecord, + SyntheticModuleRecord, + ResolvedBindingRecord, + AbstractModuleRecord, + type ModuleRecordHostDefined, + ModuleRecord, +} from '../modules.mts'; +import { + JSStringValue, ObjectValue, Value, +} from '../value.mts'; +import { + Q, X, NormalCompletion, ThrowCompletion, AbruptCompletion, + type PlainCompletion, + EnsureCompletion, +} from '../completion.mjs'; +import { + Assert, + ModuleNamespaceCreate, + NewPromiseCapability, + PerformPromiseThen, + CreateBuiltinFunction, + Call, + ContinueDynamicImport, + PromiseCapabilityRecord, +} from './all.mts'; +import { + Realm, + Completion, + HostGetSupportedImportAttributes, + ModuleRequestsEqual, + ReadyForSyncExecution, + type Arguments, type ImportAttributeRecord, type ModuleRequestRecord, type PlainEvaluator, type ScriptRecord, type SourceTextModuleRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#graphloadingstate-record */ +export class GraphLoadingState { + readonly PromiseCapability: PromiseCapabilityRecord; + + readonly HostDefined?: ModuleRecordHostDefined; + + IsLoading = true; + + readonly Visited = new Set(); + + PendingModules = 1; + + constructor({ PromiseCapability, HostDefined }: Pick) { + this.PromiseCapability = PromiseCapability; + this.HostDefined = HostDefined; + } +} + +/** https://tc39.es/ecma262/#sec-InnerModuleLoading */ +export function InnerModuleLoading(state: GraphLoadingState, module: AbstractModuleRecord) { + // 1. Assert: state.[[IsLoading]] is true. + Assert(Boolean(state.IsLoading === true)); // this Boolean() is let step 2.d.iii not having a type error. + + // 2. If module is a Cyclic Module Record, module.[[Status]] is new, and state.[[Visited]] does not contain module, then + if (module instanceof CyclicModuleRecord && module.Status === 'new' && !state.Visited.has(module)) { + // a. Append module to state.[[Visited]]. + state.Visited.add(module); + // b. Let requestedModulesCount be the number of elements in module.[[RequestedModules]]. + const requestedModulesCout = module.RequestedModules.length; + // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. + state.PendingModules += requestedModulesCout; + // d. For each ModuleRequest Record request of module.[[RequestedModules]], do + for (const request of module.RequestedModules) { + // i. If AllImportAttributesSupported(request.[[Attributes]]) is false, then + const invalidAttributeKey = AllImportAttributesSupported(request.Attributes); + if (invalidAttributeKey) { + // 1. Let error be ThrowCompletion(a newly created SyntaxError object). + const error = surroundingAgent.Throw('SyntaxError', 'UnsupportedImportAttribute', invalidAttributeKey); + // 2. Perform ContinueModuleLoading(state, error). + ContinueModuleLoading(state, error); + } else { + // ii. Else if module.[[LoadedModules]] contains a LoadedModuleRequest Record record such that ModuleRequestsEqual(record, request) is true, then + const record = getRecordWithSpecifier(module.LoadedModules, request); + if (record !== undefined) { + // 1. Perform InnerModuleLoading(state, record.[[Module]]). + InnerModuleLoading(state, record.Module); + } else { // iii. Else, + // 1. Perform HostLoadImportedModule(module, request, state.[[HostDefined]], state). + HostLoadImportedModule(module, request, state.HostDefined, state); + } + } + + // iii. If state.[[IsLoading]] is false, return unused. + if (state.IsLoading === false) { + return; + } + } + } + + // 3. Assert: state.[[PendingModulesCount]] ≥ 1. + Assert(state.PendingModules >= 1); + // 4. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] - 1. + state.PendingModules -= 1; + // 5. If state.[[PendingModulesCount]] = 0, then + if (state.PendingModules === 0) { + // a. Set state.[[IsLoading]] to false. + state.IsLoading = false; + // b. For each Cyclic Module Record loaded of state.[[Visited]], do + for (const loaded of state.Visited) { + // i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked. + if (loaded.Status === 'new') { + loaded.Status = 'unlinked'; + } + } + // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »). + X(Call(state.PromiseCapability.Resolve, Value.undefined, [Value.undefined])); + } + + // 6. Return unused. +} + +/** https://tc39.es/ecma262/#sec-ContinueModuleLoading */ +export function ContinueModuleLoading(state: GraphLoadingState, result: PlainCompletion) { + // 1. If state.[[IsLoading]] is false, return unused. + if (state.IsLoading === false) { + return; + } + result = EnsureCompletion(result); + // 2. If moduleCompletion is a normal completion, then + if (result instanceof NormalCompletion) { + // a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]). + InnerModuleLoading(state, result.Value); + // 3. Else, + } else { + // a. Set state.[[IsLoading]] to false. + state.IsLoading = false; + // b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »). + X(Call(state.PromiseCapability.Reject, Value.undefined, [result.Value])); + } + + // 4. Return unused. +} + +/** https://tc39.es/ecma262/#sec-InnerModuleLinking */ +export function InnerModuleLinking(module: AbstractModuleRecord, stack: CyclicModuleRecord[], index: number): PlainCompletion { + if (!(module instanceof CyclicModuleRecord)) { + Q(module.Link()); + return index; + } + if (module.Status === 'linking' || module.Status === 'linked' || module.Status === 'evaluating-async' || module.Status === 'evaluated') { + return index; + } + Assert(module.Status === 'unlinked'); + module.Status = 'linking'; + const moduleIndex = index; + module.DFSAncestorIndex = index; + index += 1; + stack.push(module); + for (const required of module.RequestedModules) { + const requiredModule = GetImportedModule(module, required); + index = Q(InnerModuleLinking(requiredModule, stack, index)); + if (requiredModule instanceof CyclicModuleRecord) { + Assert(requiredModule.Status === 'linking' || requiredModule.Status === 'linked' || requiredModule.Status === 'evaluating-async' || requiredModule.Status === 'evaluated'); + Assert((requiredModule.Status === 'linking') === stack.includes(requiredModule)); + if (requiredModule.Status === 'linking') { + module.DFSAncestorIndex = Math.min(module.DFSAncestorIndex, requiredModule.DFSAncestorIndex!); + } + } + } + Q((module as SourceTextModuleRecord).InitializeEnvironment()); + Assert(stack.indexOf(module) === stack.lastIndexOf(module)); + Assert(module.DFSAncestorIndex <= moduleIndex); + if (module.DFSAncestorIndex === moduleIndex) { + let done = false; + while (done === false) { + const requiredModule = stack.pop(); + Assert(requiredModule instanceof CyclicModuleRecord); + requiredModule.Status = 'linked'; + if (requiredModule === module) { + done = true; + } + } + } + return index; +} + +/** https://tc39.es/ecma262/#sec-EvaluateModuleSync */ +export function* EvaluateModuleSync(module: ModuleRecord): PlainEvaluator { + // 1. Assert: If module is a Cyclic Module Record, ReadyForSyncExecution(module) is true. + Assert(module instanceof CyclicModuleRecord ? ReadyForSyncExecution(module) === Value.true : true); + // 2. Let promise be module.Evaluate()./ + const promise = yield* module.Evaluate(); + // 3. Assert: promise.[[PromiseState]] is either fulfilled or rejected. + Assert(promise.PromiseState === 'fulfilled' || promise.PromiseState === 'rejected'); + // 4. If promise.[[PromiseState]] is rejected, then + if (promise.PromiseState === 'rejected') { + // a. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle"). + if (promise.PromiseIsHandled === Value.false) { + HostPromiseRejectionTracker(promise, 'handle'); + } + // b. Set promise.[[PromiseIsHandled]] to true. + promise.PromiseIsHandled = Value.true; + // c. Return ThrowCompletion(promise.[[PromiseResult]]). + return ThrowCompletion(promise.PromiseResult!); + } + // 5. Return unused. + return undefined; +} + +/** https://tc39.es/ecma262/#sec-innermoduleevaluation */ +export function* InnerModuleEvaluation(module: AbstractModuleRecord, stack: CyclicModuleRecord[], index: number): PlainEvaluator { + if (!(module instanceof CyclicModuleRecord)) { + Q(yield* EvaluateModuleSync(module)); + return NormalCompletion(index); + } + if (module.Status === 'evaluating-async' || module.Status === 'evaluated') { + if (module.EvaluationError === undefined) { + return NormalCompletion(index); + } else { + return module.EvaluationError; + } + } + if (module.Status === 'evaluating') { + return NormalCompletion(index); + } + Assert(module.Status === 'linked'); + module.Status = 'evaluating'; + const moduleIndex = index; + module.DFSAncestorIndex = index; + module.PendingAsyncDependencies = 0; + module.AsyncParentModules = []; + index += 1; + const evaluationList: ModuleRecord[] = []; + for (const request of module.RequestedModules) { + const requiredModule = GetImportedModule(module, request); + if (request.Phase === 'defer') { + const additionalModules = GatherAsynchronousTransitiveDependencies(requiredModule); + for (const additionalModule of additionalModules) { + if (!evaluationList.includes(additionalModule)) { + evaluationList.push(additionalModule); + } + } + } else if (!evaluationList.includes(requiredModule)) { + evaluationList.push(requiredModule); + } + } + stack.push(module); + for (const required of evaluationList!) { + let requiredModule: ModuleRecord | CyclicModuleRecord = required as ModuleRecord; + index = Q(yield* InnerModuleEvaluation(requiredModule, stack, index)); + if (requiredModule instanceof CyclicModuleRecord) { + Assert(requiredModule.Status === 'evaluating' || requiredModule.Status === 'evaluating-async' || requiredModule.Status === 'evaluated'); + Assert((requiredModule.Status === 'evaluating') === stack.includes(requiredModule)); + if (requiredModule.Status === 'evaluating') { + module.DFSAncestorIndex = Math.min(module.DFSAncestorIndex, requiredModule.DFSAncestorIndex!); + } else { + requiredModule = requiredModule.CycleRoot!; + Assert((requiredModule as CyclicModuleRecord).Status === 'evaluating-async' || (requiredModule as CyclicModuleRecord).Status === 'evaluated'); + if ((requiredModule as CyclicModuleRecord).EvaluationError !== undefined) { + return EnsureCompletion((requiredModule as CyclicModuleRecord).EvaluationError); + } + } + if (typeof (requiredModule as CyclicModuleRecord).AsyncEvaluationOrder === 'number') { + module.PendingAsyncDependencies += 1; + (requiredModule as CyclicModuleRecord).AsyncParentModules.push(module); + } + } + } + if (module.PendingAsyncDependencies > 0 || module.HasTLA === Value.true) { + Assert(module.AsyncEvaluationOrder === 'unset'); + module.AsyncEvaluationOrder = IncrementModuleAsyncEvaluationCount(); + if (module.PendingAsyncDependencies === 0) { + X(yield* ExecuteAsyncModule(module)); + } + } else { + Q(yield* module.ExecuteModule()); + } + Assert(stack.indexOf(module) === stack.lastIndexOf(module)); + Assert(module.DFSAncestorIndex <= moduleIndex); + if (module.DFSAncestorIndex === moduleIndex) { + let done = false; + while (done === false) { + const requiredModule = stack.pop(); + Assert(requiredModule instanceof CyclicModuleRecord); + Assert(typeof requiredModule.AsyncEvaluationOrder === 'number' || requiredModule.AsyncEvaluationOrder === 'unset'); + if (requiredModule.AsyncEvaluationOrder === 'unset') { + requiredModule.Status = 'evaluated'; + } else { + requiredModule.Status = 'evaluating-async'; + } + if (requiredModule === module) { + done = true; + } + requiredModule.CycleRoot = module; + } + } + return index; +} + +/** https://tc39.es/proposal-defer-import-eval/#sec-GatherAsynchronousTransitiveDependencies */ +export function GatherAsynchronousTransitiveDependencies(module: ModuleRecord, seen?: Set): ModuleRecord[] { + // 1. If seen is not present, set seen to a new empty List. + seen ??= new Set(); + // 2. Let result be a new empty List. + const result: ModuleRecord[] = []; + // 3. If seen contains module, return result. + if (seen.has(module)) { + return result; + } + // 4. Append module to seen. + seen.add(module); + // 5. If module is not a Cyclic Module Record, return result. + if (!(module instanceof CyclicModuleRecord)) { + return result; + } + // 6. If module.[[Status]] is either evaluating or evaluated, return result. + if (module.Status === 'evaluating' || module.Status === 'evaluated') { + return result; + } + // 7. If module.[[HasTLA]] is true, then + if (module.HasTLA === Value.true) { + // a. Append module to result. + result.push(module); + // b. Return result. + return result; + } + // 8. For each ModuleRequest Record request of module.[[RequestedModules]], do + for (const request of module.RequestedModules) { + // a. Let requiredModule be GetImportedModule(module, request). + const requiredModule = GetImportedModule(module, request); + // b. Let additionalModules be GatherAsynchronousTransitiveDependencies(requiredModule, seen). + const additionalModules = GatherAsynchronousTransitiveDependencies(requiredModule, seen); + // c. For each Module Record m of additionalModules, do + for (const m of additionalModules) { + // i. If result does not contain m, append m to result. + if (!result.includes(m)) { + result.push(m); + } + } + } + // 9. Return result. + return result; +} + +/** https://tc39.es/ecma262/#sec-execute-async-module */ +function* ExecuteAsyncModule(module: CyclicModuleRecord) { + // 1. Assert: module.[[Status]] is evaluating or evaluating-async. + Assert(module.Status === 'evaluating' || module.Status === 'evaluating-async'); + // 2. Assert: module.[[HasTLA]] is true. + Assert(module.HasTLA === Value.true); + // 3. Let capability be ! NewPromiseCapability(%Promise%). + const capability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called: + function* fulfilledClosure() { + // a. Perform ! AsyncModuleExecutionFulfilled(module). + X(yield* AsyncModuleExecutionFulfilled(module)); + // b. Return undefined. + return Value.undefined; + } + // 5. Let onFulfilled be ! CreateBuiltinFunction(fulfilledClosure, 0, "", « »). + const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 0, Value(''), ['Module']); + // 6. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures module and performs the following steps when called: + const rejectedClosure = ([error = Value.undefined]: Arguments) => { + // a. Perform ! AsyncModuleExecutionRejected(module, error). + X(AsyncModuleExecutionRejected(module, error)); + // b. Return undefined. + return Value.undefined; + }; + // 7. Let onRejected be ! CreateBuiltinFunction(rejectedClosure, 0, "", « »). + const onRejected = CreateBuiltinFunction(rejectedClosure, 0, Value(''), ['Module']); + // 8. Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected). + X(PerformPromiseThen(capability.Promise, onFulfilled, onRejected)); + // 9. Perform ! module.ExecuteModule(capability). + X(yield* module.ExecuteModule(capability)); + // 10. Return. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-gather-available-ancestors */ +function GatherAvailableAncestors(module: CyclicModuleRecord, execList: CyclicModuleRecord[]) { + for (const m of module.AsyncParentModules) { + if (!execList.includes(m) && m.CycleRoot!.EvaluationError === undefined) { + Assert(m.Status === 'evaluating-async'); + Assert(m.EvaluationError === undefined); + Assert(typeof m.AsyncEvaluationOrder === 'number'); + Assert(m.PendingAsyncDependencies! > 0); + m.PendingAsyncDependencies! -= 1; + if (m.PendingAsyncDependencies === 0) { + execList.push(m); + if (m.HasTLA === Value.false) { + GatherAvailableAncestors(m, execList); + } + } + } + } +} + +/** https://tc39.es/ecma262/#sec-asyncmodulexecutionfulfilled */ +function* AsyncModuleExecutionFulfilled(module: CyclicModuleRecord): PlainEvaluator { + if (module.Status === 'evaluated') { + Assert(module.EvaluationError !== undefined); + return; + } + Assert(module.Status === 'evaluating-async'); + Assert(typeof module.AsyncEvaluationOrder === 'number'); + Assert(module.EvaluationError === undefined); + module.AsyncEvaluationOrder = 'done'; + module.Status = 'evaluated'; + if (module.TopLevelCapability !== undefined) { + Assert(module.CycleRoot === module); + X(Call(module.TopLevelCapability.Resolve, Value.undefined, [Value.undefined])); + } + + const execList: CyclicModuleRecord[] = []; + GatherAvailableAncestors(module, execList); + Assert(execList.every((m) => typeof m.AsyncEvaluationOrder === 'number' && m.PendingAsyncDependencies === 0 && m.EvaluationError === undefined)); + const sortedExecList = execList.toSorted((m1, m2) => (m1.AsyncEvaluationOrder as number) - (m2.AsyncEvaluationOrder as number)); + + for (const m of sortedExecList) { + if (m.Status === 'evaluated') { + Assert(m.EvaluationError !== undefined); + } else if (m.HasTLA === Value.true) { + X(yield* ExecuteAsyncModule(m)); + } else { + const result = yield* m.ExecuteModule(); + if (result instanceof AbruptCompletion) { + X(AsyncModuleExecutionRejected(m, result.Value)); + } else { + m.AsyncEvaluationOrder = 'done'; + m.Status = 'evaluated'; + if (m.TopLevelCapability !== undefined) { + Assert(m.CycleRoot === m); + X(Call(m.TopLevelCapability.Resolve, Value.undefined, [Value.undefined])); + } + } + } + } +} + +/** https://tc39.es/ecma262/#sec-AsyncModuleExecutionRejected */ +function AsyncModuleExecutionRejected(module: CyclicModuleRecord, error: Value) { + if (module.Status === 'evaluated') { + Assert(module.EvaluationError !== undefined); + return; + } + Assert(module.Status === 'evaluating-async'); + Assert(typeof module.AsyncEvaluationOrder === 'number'); + Assert(module.EvaluationError === undefined); + module.EvaluationError = ThrowCompletion(error); + module.Status = 'evaluated'; + module.AsyncEvaluationOrder = 'done'; + if (module.TopLevelCapability !== undefined) { + Assert(module.CycleRoot === module); + X(Call(module.TopLevelCapability.Reject, Value.undefined, [error])); + } + for (const m of module.AsyncParentModules) { + AsyncModuleExecutionRejected(m, error); + } +} + +function getRecordWithSpecifier(loadedModules: CyclicModuleRecord['LoadedModules'], request: ModuleRequestRecord) { + const records = loadedModules.filter((r) => ModuleRequestsEqual(r, request)); + Assert(records.length <= 1); + return records.length === 1 ? records[0] : undefined; +} + +/** https://tc39.es/ecma262/#sec-GetImportedModule */ +export function GetImportedModule(referrer: CyclicModuleRecord, request: ModuleRequestRecord) { + const record = getRecordWithSpecifier(referrer.LoadedModules, request); + Assert(record !== undefined); + return record.Module; +} + +/** https://tc39.es/ecma262/#sec-FinishLoadingImportedModule */ +export function FinishLoadingImportedModule(referrer: ScriptRecord | CyclicModuleRecord | Realm, moduleRequest: ModuleRequestRecord, result: PlainCompletion, state: GraphLoadingState | PromiseCapabilityRecord) { + result = EnsureCompletion(result); + // 1. If result is a normal completion, then + if (result.Type === 'normal') { + // a. If referrer.[[LoadedModules]] contains a LoadedModuleRequest Record record such that ModuleRequestsEqual(record, moduleRequest) is true, then + const record = getRecordWithSpecifier(referrer.LoadedModules, moduleRequest); + if (record !== undefined) { + // i. Assert: That Record's [[Module]] is result.[[Value]]. + Assert(record.Module === result.Value); + } else { // b. Else, + // i. Append the LoadedModuleRequest Record { [[Specifier]]: moduleRequest.[[Specifier]], [[Attributes]]: moduleRequest.[[Attributes]], [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. + referrer.LoadedModules.push({ Specifier: moduleRequest.Specifier, Attributes: moduleRequest.Attributes, Module: result.Value }); + } + } + + // 2. If payload is a GraphLoadingState Record, then + if (state instanceof GraphLoadingState) { + // a. Perform ContinueModuleLoading(payload, result). + ContinueModuleLoading(state, result); + // 3. Else, + } else { + // a. Perform ContinueDynamicImport(payload, result). + ContinueDynamicImport(state, result, moduleRequest.Phase); + } + + // 4. Return unused. +} + +/** https://tc39.es/ecma262/#sec-AllImportAttributesSupported */ +export function AllImportAttributesSupported(attributes: readonly ImportAttributeRecord[]) { + // Note: This function is meant to return a boolean. Instead, we return: + // - instead of *false*, the key of the unsupported attribute + // - instead of *true*, undefined + + const supported: readonly string[] = HostGetSupportedImportAttributes(); + for (const attribute of attributes) { + if (!supported.includes(attribute.Key.stringValue())) { + return attribute.Key; + } + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-getmodulenamespace */ +export function GetModuleNamespace( + module: AbstractModuleRecord, + phase: 'defer' | 'evaluation', +): ObjectValue { + // 1. Assert: If module is a Cyclic Module Record, then module.[[Status]] is not new or unlinked. + if (module instanceof CyclicModuleRecord) { + Assert(module.Status !== 'new' && module.Status !== 'unlinked'); + } + // 2. Let namespace be module.[[Namespace]]. + let namespace = phase === 'defer' ? module.DeferredNamespace : module.Namespace; + // 3. If namespace is empty, then + if (namespace === undefined) { + // a. Let exportedNames be module.GetExportedNames(). + const exportedNames = module.GetExportedNames(); + // b. Let unambiguousNames be a new empty List. + const unambiguousNames = []; + // c. For each element name of exportedNames, do + for (const name of exportedNames) { + // i. Let resolution be module.ResolveExport(name). + const resolution = module.ResolveExport(name); + // ii. If resolution is a ResolvedBinding Record, append name to unambiguousNames. + if (resolution instanceof ResolvedBindingRecord) { + unambiguousNames.push(name); + } + } + // d. Set namespace to ModuleNamespaceCreate(module, unambiguousNames). + namespace = ModuleNamespaceCreate(module, unambiguousNames, phase); + } + // 4. Return namespace. + return namespace; +} + +export function CreateSyntheticModule(exportNames: readonly JSStringValue[], evaluationSteps: (record: SyntheticModuleRecord) => PlainEvaluator | Completion, realm: Realm, hostDefined: ModuleRecordHostDefined) { + // 1. Return Synthetic Module Record { + // [[Realm]]: realm, + // [[Environment]]: undefined, + // [[Namespace]]: undefined, + // [[HostDefined]]: hostDefined, + // [[ExportNames]]: exportNames, + // [[EvaluationSteps]]: evaluationSteps + // }. + return new SyntheticModuleRecord({ + Realm: realm, + Environment: undefined, + Namespace: undefined, + HostDefined: hostDefined, + ExportNames: exportNames, + EvaluationSteps: evaluationSteps, + }); +} + +/** https://tc39.es/ecma262/#sec-create-default-export-synthetic-module */ +export function CreateDefaultExportSyntheticModule(defaultExport: Value, realm: Realm, hostDefined: ModuleRecordHostDefined) { + // 1. Let closure be the a Abstract Closure with parameters (module) that captures defaultExport and performs the following steps when called: + const closure = function* closure(module: SyntheticModuleRecord): PlainEvaluator { + // a. Return module.SetSyntheticExport("default", defaultExport). + Q(yield* module.SetSyntheticExport(Value('default'), defaultExport)); + return NormalCompletion(undefined); + }; + // 2. Return CreateSyntheticModule(« "default" », closure, realm) + return CreateSyntheticModule([Value('default')], closure, realm, hostDefined); +} diff --git a/src/abstract-ops/notational-conventions.mts b/src/abstract-ops/notational-conventions.mts new file mode 100644 index 0000000..70c5edc --- /dev/null +++ b/src/abstract-ops/notational-conventions.mts @@ -0,0 +1,61 @@ +import { + ThrowCompletion, type Completion, type Value, +} from '../index.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ObjectValue } from '../value.mts'; + +class AssertError extends Error {} +export function Assert(invariant: boolean, source?: string, completion?: Completion): asserts invariant { + /* node:coverage disable */ + if (!invariant) { + throw new AssertError(source, { cause: completion }); + } + /* node:coverage enable */ +} +Assert.Error = AssertError; +Assert.Throw = (source?: string, completion?: Completion) => { + /* node:coverage disable */ + throw new AssertError(source, { cause: completion }); + /* node:coverage enable */ +}; + +/** https://tc39.es/ecma262/#sec-requireinternalslot */ +export function RequireInternalSlot(O: Value, internalSlot: string): ThrowCompletion | undefined { + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + if (!(internalSlot in O)) { + return surroundingAgent.Throw('TypeError', 'InternalSlotMissing', O, internalSlot); + } + return undefined; +} + +export function sourceTextMatchedBy(node: ParseNode) { + 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: ParseNode) { + return node.strict; +} diff --git a/src/abstract-ops/object-operations.mts b/src/abstract-ops/object-operations.mts new file mode 100644 index 0000000..b2ab458 --- /dev/null +++ b/src/abstract-ops/object-operations.mts @@ -0,0 +1,599 @@ +import { + Descriptor, + JSStringValue, BooleanValue, + Value, + ObjectValue, + wellKnownSymbols, + type PropertyKeyValue, + UndefinedValue, + NullValue, + type Arguments, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { InstanceofOperator } from '../runtime-semantics/all.mts'; +import { + EnsureCompletion, + Q, X, + type PlainCompletion, +} from '../completion.mts'; +import { __ts_cast__, isArray } from '../helpers.mts'; +import { isBoundFunctionObject } from '../intrinsics/FunctionPrototype.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { + ArrayCreate, + Assert, + IsAccessorDescriptor, + IsCallable, + IsConstructor, + IsDataDescriptor, + IsExtensible, + IsPropertyKey, + SameValue, + ToLength, + ToObject, + ToString, + isProxyExoticObject, + F as toNumberValue, R, type FunctionObject, Realm, + RequireObjectCoercible, + GetIterator, + IteratorClose, + IteratorStepValue, + F, + IfAbruptCloseIterator, + type ValueCompletion, + ToPropertyKey, + CanonicalizeKeyedCollectionKey, + Throw, +} from '#self'; + + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-operations-on-objects */ + +/** https://tc39.es/ecma262/#sec-makebasicobject */ +export function MakeBasicObject(internalSlotsList: readonly T[]) { + // 1. Assert: internalSlotsList is a List of internal slot names. + Assert(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) as ObjectValue & Record; + Object.assign(obj, internalSlotsList.reduce((extraFields, currentField) => { + extraFields[currentField] = Value.undefined; + return extraFields; + }, {} as Record)); + // 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 as readonly string[]).includes('Extensible')) { + (obj as ObjectValue & { Extensible: BooleanValue }).Extensible = Value.true; + } + // 7. Return obj. + return obj; +} + +/** https://tc39.es/ecma262/#sec-get-o-p */ +export function* Get(O: ObjectValue, P: PropertyKeyValue): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + return Q(yield* O.Get(P, O)); +} + +/** https://tc39.es/ecma262/#sec-getv */ +export function* GetV(V: Value, P: PropertyKeyValue): ValueEvaluator { + Assert(IsPropertyKey(P)); + const O = Q(ToObject(V)); + return Q(yield* O.Get(P, V)); +} + +/** https://tc39.es/ecma262/#sec-set-o-p-v-throw */ +export function* Set(O: ObjectValue, P: PropertyKeyValue, V: Value, Throw: BooleanValue) { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + Assert(Throw instanceof BooleanValue); + const success = Q(yield* O.Set(P, V, O)); + if (success === Value.false && Throw === Value.true) { + return surroundingAgent.Throw('TypeError', 'CannotSetProperty', P, O); + } + return success; +} + +/** https://tc39.es/ecma262/#sec-createdataproperty */ +export function* CreateDataProperty(O: ObjectValue, P: PropertyKeyValue, V: Value): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + + const newDesc = Descriptor({ + Value: V, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.true, + }); + return Q(yield* O.DefineOwnProperty(P, newDesc)); +} + +/** https://tc39.es/ecma262/#sec-createmethodproperty */ +export function* CreateMethodProperty(O: ObjectValue, P: PropertyKeyValue, V: Value): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + + const newDesc = Descriptor({ + Value: V, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }); + return Q(yield* O.DefineOwnProperty(P, newDesc)); +} + +/** https://tc39.es/ecma262/#sec-createdatapropertyorthrow */ +export function* CreateDataPropertyOrThrow(O: ObjectValue, P: PropertyKeyValue, V: Value) { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + const success = Q(yield* CreateDataProperty(O, P, V)); + if (success === Value.false) { + return surroundingAgent.Throw('TypeError', 'CannotDefineProperty', P); + } + return success; +} + +export function CreateNonEnumerableDataPropertyOrThrow(O: ObjectValue, P: PropertyKeyValue, V: Value) { + Assert(O instanceof ObjectValue); + const newDesc = Descriptor({ + Value: V, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }); + X(DefinePropertyOrThrow(O, P, newDesc)); +} + +/** https://tc39.es/ecma262/#sec-definepropertyorthrow */ +export function* DefinePropertyOrThrow(O: ObjectValue, P: PropertyKeyValue, desc: Descriptor) { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + const success = Q(yield* O.DefineOwnProperty(P, desc)); + if (success === Value.false) { + return surroundingAgent.Throw('TypeError', 'CannotDefineProperty', P); + } + return success; +} + +/** https://tc39.es/ecma262/#sec-deletepropertyorthrow */ +export function* DeletePropertyOrThrow(O: ObjectValue, P: PropertyKeyValue) { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + const success = Q(yield* O.Delete(P)); + if (success === Value.false) { + return surroundingAgent.Throw('TypeError', 'CannotDeleteProperty', P); + } + return success; +} + +/** https://tc39.es/ecma262/#sec-getmethod */ +export function* GetMethod(V: Value, P: PropertyKeyValue): ValueEvaluator { + Assert(IsPropertyKey(P)); + const func = Q(yield* GetV(V, P)); + if (func === Value.null || func === Value.undefined) { + return Value.undefined; + } + if (!IsCallable(func)) { + return Throw.TypeError('$1 is not a function', func); + } + return func; +} + +/** https://tc39.es/ecma262/#sec-hasproperty */ +export function* HasProperty(O: ObjectValue, P: PropertyKeyValue): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + return Q(yield* O.HasProperty(P)); +} + +/** https://tc39.es/ecma262/#sec-hasownproperty */ +export function* HasOwnProperty(O: ObjectValue, P: PropertyKeyValue): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(IsPropertyKey(P)); + const desc = Q(yield* O.GetOwnProperty(P)); + if (desc === Value.undefined) { + return Value.false; + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-call */ +export function* Call(F: Value, V: Value, argumentsList: Arguments = []): ValueEvaluator { + Assert(argumentsList.every((a) => a instanceof Value)); + + if (!IsCallable(F)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', F); + } + + return EnsureCompletion(Q(yield* F.Call(V, argumentsList))); +} + +/** https://tc39.es/ecma262/#sec-construct */ +export function* Construct(F: FunctionObject, argumentsList: Arguments = [], newTarget?: FunctionObject | UndefinedValue): ValueEvaluator { + if (!newTarget) { + newTarget = F; + } + Assert(IsConstructor(F)); + Assert(IsConstructor(newTarget)); + return Q(yield* F.Construct(argumentsList, newTarget)); +} + +/** https://tc39.es/ecma262/#sec-setintegritylevel */ +export function* SetIntegrityLevel(O: ObjectValue, level: 'sealed' | 'frozen'): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(level === 'sealed' || level === 'frozen'); + const status = Q(yield* O.PreventExtensions()); + if (status === Value.false) { + return Value.false; + } + const keys = Q(yield* O.OwnPropertyKeys()); + if (level === 'sealed') { + for (const k of keys) { + Q(yield* DefinePropertyOrThrow(O, k, Descriptor({ Configurable: Value.false }))); + } + } else if (level === 'frozen') { + for (const k of keys) { + const currentDesc = Q(yield* O.GetOwnProperty(k)); + if (!(currentDesc instanceof UndefinedValue)) { + let desc; + if (IsAccessorDescriptor(currentDesc) === true) { + desc = Descriptor({ Configurable: Value.false }); + } else { + desc = Descriptor({ Configurable: Value.false, Writable: Value.false }); + } + Q(yield* DefinePropertyOrThrow(O, k, desc)); + } + } + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-testintegritylevel */ +export function* TestIntegrityLevel(O: ObjectValue, level: 'sealed' | 'frozen'): ValueEvaluator { + Assert(O instanceof ObjectValue); + Assert(level === 'sealed' || level === 'frozen'); + const extensible = Q(yield* IsExtensible(O)); + if (extensible === Value.true) { + return Value.false; + } + const keys = Q(yield* O.OwnPropertyKeys()); + for (const k of keys) { + const currentDesc = Q(yield* O.GetOwnProperty(k)); + if (!(currentDesc instanceof UndefinedValue)) { + if (currentDesc.Configurable === Value.true) { + return Value.false; + } + if (level === 'frozen' && IsDataDescriptor(currentDesc)) { + if (currentDesc.Writable === Value.true) { + return Value.false; + } + } + } + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-createarrayfromlist */ +export function CreateArrayFromList(elements: Arguments) { + // 1. Assert: elements is a List whose elements are all ECMAScript language values. + Assert(elements.every((e) => e instanceof Value)); + // 2. Let array be ! ArrayCreate(0). + const array = X(ArrayCreate(0)); + // 3. Let n be 0. + let n = 0; + // 4. For each element e of elements, do + for (const e of elements) { + // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(n)), e). + X(CreateDataPropertyOrThrow(array, X(ToString(toNumberValue(n))), e)); + // b. Set n to n + 1. + n += 1; + } + // 5. Return array. + return array; +} + +/** https://tc39.es/ecma262/#sec-lengthofarraylike */ +export function* LengthOfArrayLike(obj: ObjectValue): PlainEvaluator { + // 1. Assert: Type(obj) is Object. + Assert(obj instanceof ObjectValue); + // 2. Return ℝ(? ToLength(? Get(obj, "length"))). + return R(Q(yield* ToLength(Q(yield* Get(obj, Value('length')))))); +} + +/** https://tc39.es/ecma262/#sec-createlistfromarraylike */ +export function CreateListFromArrayLike(obj: Value, validElementTypes?: undefined | 'all'): PlainEvaluator +export function CreateListFromArrayLike(obj: Value, validElementTypes: 'property-key'): PlainEvaluator +export function* CreateListFromArrayLike(obj: Value, validElementTypes: 'all' | 'property-key' = 'all'): PlainEvaluator { + // 2. If Type(obj) is not Object, throw a TypeError exception. + if (!(obj instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', obj); + } + // 3. Let len be ? LengthOfArrayLike(obj). + const len = Q(yield* LengthOfArrayLike(obj)); + // 4. Let list be a new empty List. + const list = []; + // 5. Let index be 0. + let index = 0; + // 6. Repeat, while index < len, + while (index < len) { + // a. Let indexName be ! ToString(𝔽(index)). + const indexName = X(ToString(toNumberValue(index))); + // b. Let next be ? Get(obj, indexName). + const next = Q(yield* Get(obj, indexName)); + // c. If Type(next) is not an element of elementTypes, throw a TypeError exception. + if (validElementTypes === 'property-key' && !IsPropertyKey(next)) { + return surroundingAgent.Throw('TypeError', 'NotPropertyName', next); + } + // d. Append next as the last element of list. + list.push(next); + // e. Set index to index + 1. + index += 1; + } + // 7. Return list. + return list; +} + +/** https://tc39.es/ecma262/#sec-invoke */ +export function* Invoke(V: Value, P: PropertyKeyValue, argumentsList: Arguments = []): ValueEvaluator { + Assert(IsPropertyKey(P)); + const func = Q(yield* GetV(V, P)); + return Q(yield* Call(func, V, argumentsList)); +} + +/** https://tc39.es/ecma262/#sec-ordinaryhasinstance */ +export function* OrdinaryHasInstance(C: Value, O: Value): ValueEvaluator { + if (!IsCallable(C)) { + return Value.false; + } + if (isBoundFunctionObject(C)) { + const BC = C.BoundTargetFunction; + return Q(yield* InstanceofOperator(O, BC)); + } + if (!(O instanceof ObjectValue)) { + return Value.false; + } + const P = Q(yield* Get(C, Value('prototype'))); + if (!(P instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', P); + } + while (true) { + O = Q(yield* O.GetPrototypeOf()); + if (O instanceof NullValue) { + return Value.false; + } + if (SameValue(P, O) === Value.true) { + return Value.true; + } + } +} + +/** https://tc39.es/ecma262/#sec-speciesconstructor */ +export function* SpeciesConstructor(O: ObjectValue, defaultConstructor: FunctionObject): ValueEvaluator { + Assert(O instanceof ObjectValue); + const C = Q(yield* Get(O, Value('constructor'))); + if (C === Value.undefined) { + return defaultConstructor; + } + if (!(C instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', C); + } + const S = Q(yield* Get(C, wellKnownSymbols.species)); + if (S === Value.undefined || S === Value.null) { + return defaultConstructor; + } + if (IsConstructor(S)) { + return S; + } + return surroundingAgent.Throw('TypeError', 'SpeciesNotConstructor'); +} + +/** https://tc39.es/ecma262/#sec-enumerableownpropertynames */ +export function EnumerableOwnProperties(O: ObjectValue, kind: 'key'): PlainEvaluator +export function EnumerableOwnProperties(O: ObjectValue, kind: 'value'): PlainEvaluator +export function EnumerableOwnProperties(O: ObjectValue, kind: 'key' | 'value' | 'key+value'): PlainEvaluator +export function* EnumerableOwnProperties(O: ObjectValue, kind: 'key' | 'value' | 'key+value'): PlainEvaluator { + const ownKeys = Q(yield* O.OwnPropertyKeys()); + const results = []; + for (const key of ownKeys) { + if (key instanceof JSStringValue) { + const desc = Q(yield* O.GetOwnProperty(key)); + if (!(desc instanceof UndefinedValue) && desc.Enumerable === Value.true) { + if (kind === 'key') { + results.push(key); + } else { + const value = Q(yield* Get(O, key)); + if (kind === 'value') { + results.push(value); + } else { + Assert(kind === 'key+value'); + const entry = X(CreateArrayFromList([key, value])); + results.push(entry); + } + } + } + } + } + return results; +} + +/** https://tc39.es/ecma262/#sec-getfunctionrealm */ +export function GetFunctionRealm(obj: FunctionObject): PlainCompletion { + Assert(IsCallable(obj)); + if ('Realm' in (obj as object)) { + return obj.Realm; + } + + if (isBoundFunctionObject(obj)) { + const target = obj.BoundTargetFunction; + return Q(GetFunctionRealm(target)); + } + + if (isProxyExoticObject(obj)) { + if (obj.ProxyHandler instanceof NullValue) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'GetFunctionRealm'); + } + const proxyTarget = obj.ProxyTarget as FunctionObject; + return Q(GetFunctionRealm(proxyTarget)); + } + + return surroundingAgent.currentRealmRecord; +} + +/** https://tc39.es/ecma262/#sec-copydataproperties */ +export function* CopyDataProperties(target: ObjectValue, source: Value, excludedItems: readonly PropertyKeyValue[]): ValueEvaluator { + Assert(target instanceof ObjectValue); + Assert(excludedItems.every((i) => IsPropertyKey(i))); + if (source === Value.undefined || source === Value.null) { + return target; + } + const from = X(ToObject(source)); + const keys = Q(yield* 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(yield* from.GetOwnProperty(nextKey)); + if (!(desc instanceof UndefinedValue) && desc.Enumerable === Value.true) { + const propValue = Q(yield* Get(from, nextKey)); + X(CreateDataProperty(target, nextKey, propValue)); + } + } + } + return target; +} + +/** https://tc39.es/ecma262/#sec-SetterThatIgnoresPrototypeProperties */ +export function* SetterThatIgnoresPrototypeProperties(thisValue: Value, home: ObjectValue, p: PropertyKeyValue, v: Value): PlainEvaluator { + // 1. If thisValue is not an Object, then + if (!(thisValue instanceof ObjectValue)) { + // a. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAnObject', thisValue); + } + // 2. If SameValue(thisValue, home) is true, then + if (SameValue(thisValue, home) === Value.true) { + // a. NOTE: Throwing here emulates assignment to a non-writable data property on the home object in strict mode code. + // b. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotSetProperty', p, thisValue); + } + // 3. Let desc be ? thisValue.[[GetOwnProperty]](p). + const desc = Q(yield* thisValue.GetOwnProperty(p)); + // 4. If desc is undefined, then + if (desc === Value.undefined) { + // a. Perform ? CreateDataPropertyOrThrow(thisValue, p, v). + Q(yield* CreateDataPropertyOrThrow(thisValue, p, v)); + } else { // 5. Else, + // a. Perform ? Set(thisValue, p, v, true). + Q(yield* Set(thisValue, p, v, Value.true)); + } + // 6. Return unused. + return undefined; +} + +export type KeyedGroupRecord = { + Key: PropertyKeyValue, + Elements: Value[] +}; + +/** https://tc39.es/ecma262/#sec-add-value-to-keyed-group */ +function AddValueToKeyedGroup(groups: KeyedGroupRecord[], key: PropertyKeyValue, value: Value): void { + /* + 1. For each Record { [[Key]], [[Elements]] } g of groups, do + a. If SameValue(g.[[Key]], key) is true, then + i. Assert: Exactly one element of groups meets this criterion. + ii. Append value to g.[[Elements]]. + iii. Return unused. + 2. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }. + 3. Append group to groups. + 4. Return unused. + */ + for (const g of groups) { + if (SameValue(g.Key, key) === Value.true) { + let count = 0; + for (const otherG of groups) { + if (SameValue(otherG.Key, key) === Value.true) { + count += 1; + } + } + Assert(count === 1); + g.Elements.push(value); + return; + } + } + + const group: KeyedGroupRecord = { Key: key, Elements: [value] }; + groups.push(group); +} + +export function* GroupBy(items: Value, callback: Value, keyCoercion: 'property' | 'collection'): PlainEvaluator { + /* + 1. Perform ? RequireObjectCoercible(items). + 2. If IsCallable(callback) is false, throw a TypeError exception. + 3. Let groups be a new empty List. + 4. Let iteratorRecord be ? GetIterator(items, sync). + 5. Let k be 0. + */ + Q(RequireObjectCoercible(items)); + if (!IsCallable(callback)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callback); + } + const groups: KeyedGroupRecord[] = []; + const iteratorRecord = Q(yield* GetIterator(items, 'sync')); + let k = 0; + const MAX_SAFE_INTEGER = (2 ** 53) - 1; + + while (true) { + /* + 6. Repeat, + a. If k ≥ 2**53 - 1, then + i. Let error be ThrowCompletion(a newly created TypeError object). + ii. Return ? IteratorClose(iteratorRecord, error). + b. Let next be ? IteratorStepValue(iteratorRecord). + c. If next is done, then + i. Return groups. + d. Let value be next. + e. Let key be Completion(Call(callback, undefined, « value, 𝔽(k) »)). + f. IfAbruptCloseIterator(key, iteratorRecord). + g. If keyCoercion is property, then + i. Set key to Completion(ToPropertyKey(key)). + ii. IfAbruptCloseIterator(key, iteratorRecord). + h. Else, + i. Assert: keyCoercion is collection. + ii. Set key to CanonicalizeKeyedCollectionKey(key). + i. Perform AddValueToKeyedGroup(groups, key, value). + j. Set k to k + 1. + */ + if (k >= MAX_SAFE_INTEGER) { + const error = surroundingAgent.Throw('TypeError', 'OutOfRange', k); + return Q(yield* IteratorClose(iteratorRecord, error)); + } + const next: Value | 'done' = Q(yield* IteratorStepValue(iteratorRecord)); + if (next === 'done') { + return groups; + } + const value: Value = next; + let key: ValueCompletion = yield* Call(callback, Value.undefined, [value, F(k)]); + IfAbruptCloseIterator(key, iteratorRecord); + __ts_cast__(key); + + if (keyCoercion === 'property') { + key = yield* ToPropertyKey(key); + IfAbruptCloseIterator(key, iteratorRecord); + } else { + Assert(keyCoercion === 'collection'); + key = CanonicalizeKeyedCollectionKey(key); + } + __ts_cast__(key); + + AddValueToKeyedGroup(groups, key, value); + k += 1; + } +} diff --git a/src/abstract-ops/objects.mts b/src/abstract-ops/objects.mts new file mode 100644 index 0000000..bc29421 --- /dev/null +++ b/src/abstract-ops/objects.mts @@ -0,0 +1,453 @@ +import { + Descriptor, + ObjectValue, + SymbolValue, JSStringValue, UndefinedValue, NullValue, + Value, + BooleanValue, + type PropertyKeyValue, + type DescriptorInit, + type CanBeNativeSteps, +} from '../value.mts'; +import { + Q, X, +} from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { + Assert, + Call, + CreateDataProperty, + Get, + GetFunctionRealm, + IsAccessorDescriptor, + IsCallable, + IsDataDescriptor, + IsExtensible, + IsGenericDescriptor, + IsPropertyKey, + SameValue, + MakeBasicObject, + isArrayIndex, + type FunctionObject, + type Intrinsics, +} from './all.mts'; +import { CreateBuiltinFunction, surroundingAgent } from '#self'; + +export interface OrdinaryObject extends ObjectValue { + Prototype: ObjectValue | NullValue; + Extensible: BooleanValue; +} + +export function isOrdinaryObject(value: Value): value is OrdinaryObject { + return value instanceof ObjectValue + && value.GetPrototypeOf === ObjectValue.prototype.GetPrototypeOf + && value.SetPrototypeOf === ObjectValue.prototype.SetPrototypeOf + && value.IsExtensible === ObjectValue.prototype.IsExtensible + && value.PreventExtensions === ObjectValue.prototype.PreventExtensions + && value.GetOwnProperty === ObjectValue.prototype.GetOwnProperty + && value.DefineOwnProperty === ObjectValue.prototype.DefineOwnProperty + && value.HasProperty === ObjectValue.prototype.HasProperty + && value.Get === ObjectValue.prototype.Get + && value.Set === ObjectValue.prototype.Set + && value.Delete === ObjectValue.prototype.Delete + && value.OwnPropertyKeys === ObjectValue.prototype.OwnPropertyKeys + && 'Prototype' in value + && 'Extensible' in value; +} + +// TODO: ban other direct extension from ObjectValue in the linter +export type ExoticObject = ObjectValue; +// 9.1.1.1 OrdinaryGetPrototypeOf +export function OrdinaryGetPrototypeOf(O: OrdinaryObject) { + return O.Prototype; +} + +// 9.1.2.1 OrdinarySetPrototypeOf +export function OrdinarySetPrototypeOf(O: OrdinaryObject, V: ObjectValue | NullValue) { + Assert(V instanceof ObjectValue || V instanceof NullValue); + + 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 instanceof NullValue) { + done = true; + } else if (SameValue(p, O) === Value.true) { + return Value.false; + } else if (p.GetPrototypeOf !== ObjectValue.prototype.GetPrototypeOf) { + done = true; + } else { + p = (p as OrdinaryObject).Prototype; + } + } + O.Prototype = V; + return Value.true; +} + +// 9.1.3.1 OrdinaryIsExtensible +export function OrdinaryIsExtensible(O: OrdinaryObject) { + return O.Extensible; +} + +// 9.1.4.1 OrdinaryPreventExtensions +export function OrdinaryPreventExtensions(O: OrdinaryObject) { + O.Extensible = Value.false; + return Value.true; +} + +// 9.1.5.1 OrdinaryGetOwnProperty +export function OrdinaryGetOwnProperty(O: ObjectValue, P: PropertyKeyValue) { + Assert(IsPropertyKey(P)); + + if (!O.properties.has(P)) { + return Value.undefined; + } + + const D: Mutable = {}; + + 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 Descriptor(D); +} + +// 9.1.6.1 OrdinaryDefineOwnProperty +export function* OrdinaryDefineOwnProperty(O: ObjectValue, P: PropertyKeyValue, Desc: Descriptor): ValueEvaluator { + const current = Q(yield* O.GetOwnProperty(P)); + const extensible = Q(yield* IsExtensible(O)); + return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current); +} + +/** https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor */ +export function IsCompatiblePropertyDescriptor(Extensible: BooleanValue, Desc: Descriptor, Current: UndefinedValue | Descriptor) { + return ValidateAndApplyPropertyDescriptor(Value.undefined, Value.undefined, Extensible, Desc, Current); +} + +// 9.1.6.3 ValidateAndApplyPropertyDescriptor +export function ValidateAndApplyPropertyDescriptor(O: ObjectValue | UndefinedValue, P: PropertyKeyValue | UndefinedValue, extensible: BooleanValue, Desc: Descriptor, current: UndefinedValue | Descriptor) { + Assert(O === Value.undefined || IsPropertyKey(P)); + + if (current instanceof UndefinedValue) { + if (extensible === Value.false) { + return Value.false; + } + + Assert(extensible === Value.true); + + if (IsGenericDescriptor(Desc) || IsDataDescriptor(Desc)) { + if (!(O instanceof UndefinedValue)) { + O.properties.set(P as PropertyKeyValue, 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 (!(O instanceof UndefinedValue)) { + O.properties.set(P as PropertyKeyValue, 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 as Descriptor).Configurable === Value.false) { + if (Desc.Configurable !== undefined && Desc.Configurable === Value.true) { + return Value.false; + } + + if (Desc.Enumerable !== undefined && Desc.Enumerable !== (current as Descriptor).Enumerable) { + return Value.false; + } + } + + if (IsGenericDescriptor(Desc)) { + // No further validation is required. + } else if (IsDataDescriptor(current) !== IsDataDescriptor(Desc)) { + if ((current as Descriptor).Configurable === Value.false) { + return Value.false; + } + if (IsDataDescriptor(current)) { + if (!(O instanceof UndefinedValue)) { + const entry = { ...O.properties.get(P as PropertyKeyValue)! }; + entry.Value = undefined; + entry.Writable = undefined; + entry.Get = Value.undefined; + entry.Set = Value.undefined; + O.properties.set(P as PropertyKeyValue, Descriptor(entry)); + } + } else { + if (!(O instanceof UndefinedValue)) { + const entry = { ...O.properties.get(P as PropertyKeyValue) }; + entry.Get = undefined; + entry.Set = undefined; + entry.Value = Value.undefined; + entry.Writable = Value.false; + O.properties.set(P as PropertyKeyValue, Descriptor(entry)); + } + } + } 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 (!(O instanceof UndefinedValue)) { + const target = { ...O.properties.get(P as PropertyKeyValue) }; + 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; + } + O.properties.set(P as PropertyKeyValue, Descriptor(target)); + } + + return Value.true; +} + +// 9.1.7.1 OrdinaryHasProperty +export function* OrdinaryHasProperty(O: ObjectValue, P: PropertyKeyValue): ValueEvaluator { + Assert(IsPropertyKey(P)); + + const hasOwn = Q(yield* O.GetOwnProperty(P)); + if (!(hasOwn instanceof UndefinedValue)) { + return Value.true; + } + const parent = Q(yield* O.GetPrototypeOf()); + if (!(parent instanceof NullValue)) { + return Q(yield* parent.HasProperty(P)); + } + return Value.false; +} + +// 9.1.8.1 +export function* OrdinaryGet(O: ObjectValue, P: PropertyKeyValue, Receiver: Value): ValueEvaluator { + Assert(IsPropertyKey(P)); + + const desc = Q(yield* O.GetOwnProperty(P)); + if (desc instanceof UndefinedValue) { + const parent = Q(yield* O.GetPrototypeOf()); + if (parent instanceof NullValue) { + return Value.undefined; + } + return Q(yield* parent.Get(P, Receiver)); + } + if (IsDataDescriptor(desc)) { + return desc.Value; + } + Assert(IsAccessorDescriptor(desc)); + const getter = desc.Get; + if (getter instanceof UndefinedValue) { + return Value.undefined; + } + return Q(yield* Call(getter, Receiver)); +} + +// 9.1.9.1 OrdinarySet +export function* OrdinarySet(O: ObjectValue, P: PropertyKeyValue, V: Value, Receiver: Value) { + Assert(IsPropertyKey(P)); + const ownDesc = Q(yield* O.GetOwnProperty(P)); + return yield* OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc); +} + +// 9.1.9.2 OrdinarySetWithOwnDescriptor +export function* OrdinarySetWithOwnDescriptor(O: ObjectValue, P: PropertyKeyValue, V: Value, Receiver: Value, ownDesc: Descriptor | UndefinedValue): ValueEvaluator { + Assert(IsPropertyKey(P)); + + if (ownDesc instanceof UndefinedValue) { + const parent = Q(yield* O.GetPrototypeOf()); + if (!(parent instanceof NullValue)) { + return Q(yield* 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 (!(Receiver instanceof ObjectValue)) { + return Value.false; + } + + const existingDescriptor = Q(yield* Receiver.GetOwnProperty(P)); + if (!(existingDescriptor instanceof UndefinedValue)) { + if (IsAccessorDescriptor(existingDescriptor)) { + return Value.false; + } + if (existingDescriptor.Writable === Value.false) { + return Value.false; + } + const valueDesc = Descriptor({ Value: V }); + return Q(yield* Receiver.DefineOwnProperty(P, valueDesc)); + } + return yield* CreateDataProperty(Receiver, P, V); + } + + Assert(IsAccessorDescriptor(ownDesc)); + const setter = ownDesc.Set; + if (setter === undefined || setter instanceof UndefinedValue) { + return Value.false; + } + Q(yield* Call(setter, Receiver, [V])); + return Value.true; +} + +// 9.1.10.1 OrdinaryDelete +export function* OrdinaryDelete(O: ObjectValue, P: PropertyKeyValue): ValueEvaluator { + Assert(IsPropertyKey(P)); + const desc = Q(yield* O.GetOwnProperty(P)); + if (desc instanceof UndefinedValue) { + 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: ObjectValue) { + const keys: PropertyKeyValue[] = []; + + // 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 as JSStringValue).stringValue(), 10) - Number.parseInt((b as JSStringValue).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 (P instanceof JSStringValue && 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 (P instanceof SymbolValue) { + keys.push(P); + } + } + + return keys; +} + +/** https://tc39.es/ecma262/#sec-ordinaryobjectcreate */ +export function OrdinaryObjectCreate(proto: ObjectValue | NullValue, additionalInternalSlotsList?: readonly T[]) { + Assert(!!proto); + // 1. Let internalSlotsList be « [[Prototype]], [[Extensible]] ». + const internalSlotsList: ['Prototype', 'Extensible', ...T[]] = ['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)) as OrdinaryObject; + // 4. Set O.[[Prototype]] to proto. + O.Prototype = proto; + // 5. Return O. + return O; +} + +/** This is a helper function to define non-spec host objects. */ +OrdinaryObjectCreate.from = (object: Record, proto?: ObjectValue | NullValue) => { + const O = OrdinaryObjectCreate(proto || surroundingAgent.intrinsic('%Object.prototype%')); + for (const key in object) { + if (Object.hasOwn(object, key)) { + const value = object[key]; + X(CreateDataProperty(O, Value(key), value instanceof Value ? value : CreateBuiltinFunction.from(value, key))); + } + } + return O; +}; + +// 9.1.13 OrdinaryCreateFromConstructor +export function* OrdinaryCreateFromConstructor(constructor: FunctionObject, intrinsicDefaultProto: keyof Intrinsics, internalSlotsList?: readonly T[]): ValueEvaluator { + // Assert: intrinsicDefaultProto is a String value that is this specification's name of an intrinsic object. + const proto = Q(yield* GetPrototypeFromConstructor(constructor, intrinsicDefaultProto)); + return OrdinaryObjectCreate(proto, internalSlotsList); +} + +// 9.1.14 GetPrototypeFromConstructor +export function* GetPrototypeFromConstructor(constructor: FunctionObject, intrinsicDefaultProto: keyof Intrinsics): ValueEvaluator { + // Assert: intrinsicDefaultProto is a String value that + // is this specification's name of an intrinsic object. + Assert(IsCallable(constructor)); + let proto = Q(yield* Get(constructor, Value('prototype'))); + if (!(proto instanceof ObjectValue)) { + const realm = Q(GetFunctionRealm(constructor)); + proto = realm.Intrinsics[intrinsicDefaultProto]; + } + return proto; +} diff --git a/src/abstract-ops/private-names.mts b/src/abstract-ops/private-names.mts new file mode 100644 index 0000000..fa5be0f --- /dev/null +++ b/src/abstract-ops/private-names.mts @@ -0,0 +1,170 @@ +import { ObjectValue, PrivateName, Value } from '../value.mts'; +import { Q, X } from '../completion.mts'; +import { ClassElementDefinitionRecord, PrivateElementRecord } from '../runtime-semantics/all.mts'; +import { + Assert, Call, IsExtensible, +} from './all.mts'; +import { Throw, type PlainEvaluator } from '#self'; + +/** https://tc39.es/ecma262/#sec-privateelementfind */ +export function PrivateElementFind(P: PrivateName, O: ObjectValue) { + const entry = O.PrivateElements.find((e) => e.Key === P); + // 1. If O.[[PrivateElements]] contains a PrivateElement whose [[Key]] is P, then + if (entry) { + // a. Let entry be that PrivateElement. + // b. Return entry. + return entry; + } + // 2. Return empty. + return undefined; +} + +/** https://tc39.es/ecma262/#sec-privateget */ +export function* PrivateGet(O: ObjectValue, P: PrivateName) { + // 1. Let entry be ! PrivateElementFind(P, O). + const entry = X(PrivateElementFind(P, O)); + // 2. If entry is empty, throw a TypeError exception. + if (entry === undefined) { + return Throw.TypeError('$1 does not exist on $2', P, O); + } + // 3. If entry.[[Kind]] is field or method, then + if (entry.Kind === 'field' || entry.Kind === 'method') { + // a. Return entry.[[Value]]. + return entry.Value!; + } + // 4. Assert: entry.[[Kind]] is accessor. + Assert(entry.Kind === 'accessor'); + // 5. If entry.[[Get]] is undefined, throw a TypeError exception. + if (entry.Get === Value.undefined) { + return Throw.TypeError('Private field $1 is not a getter', P); + } + // 6. Let getter be entry.[[Get]]. + const getter = entry.Get!; + // 7. Return ? Call(getter, O). + return Q(yield* Call(getter, O)); +} + +export function* PrivateSet(O: ObjectValue, P: PrivateName, value: Value) { + // 1. Let entry be ! PrivateElementFind(P, O). + const entry = X(PrivateElementFind(P, O)); + // 2. If entry is empty, throw a TypeError exception. + if (entry === undefined) { + return Throw.TypeError('$1 does not exist on $2', P, O); + } + // 3. If entry.[[Kind]] is field, then + if (entry.Kind === 'field') { + // a. Set entry.[[Value]] to value. + entry.Value = value; + } else if (entry.Kind === 'method') { // 4. Else if entry.[[Kind]] is method, then + // a. Throw a TypeError exception. + return Throw.TypeError('Private method $1 cannot be set', P); + } else { // 5. Else, + // a. Assert: entry.[[Kind]] is accessor. + Assert(entry.Kind === 'accessor'); + // b. If entry.[[Set]] is undefined, throw a TypeError exception. + if (entry.Set === Value.undefined) { + return Throw.TypeError('Private field $1 is not a setter', P); + } + // c. Let setter be entry.[[Set]]. + const setter = entry.Set!; + // d. Perform ? Call(setter, O, « value »). + Q(yield* Call(setter, O, [value])); + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-privatemethodoraccessoradd */ +export function* PrivateMethodOrAccessorAdd(O: ObjectValue, method: PrivateElementRecord) { + // 1. Assert: method.[[Kind]] is either method or accessor. + Assert(method.Kind === 'method' || method.Kind === 'accessor'); + if (Q(yield* IsExtensible(O)) === Value.false) { + return Throw.TypeError('Cannot define private element to a non-extensible object'); + } + // 2. Let entry be ! PrivateElementFind(method.[[Key]], O). + const entry = X(PrivateElementFind(method.Key, O)); + // 3. If entry is not empty, throw a TypeError exception. + if (entry !== undefined) { + return Throw.TypeError('Private element $1 is already defined on $2', method.Key, O); + } + // 4. Append method to O.[[PrivateElements]]. + O.PrivateElements.push(method); + // 5. NOTE: The values for private methods and accessors are shared across instances. + // This step does not create a new copy of the method or accessor. + return undefined; +} + +/** https://tc39.es/ecma262/#sec-privatefieldadd */ +export function* PrivateFieldAdd(O: ObjectValue, P: PrivateName, value: Value) { + // 1. Let entry be ! PrivateElementFind(P, O). + const entry = X(PrivateElementFind(P, O)); + if (Q(yield* IsExtensible(O)) === Value.false) { + return Throw.TypeError('Cannot define private element to a non-extensible object'); + } + // 2. If entry is not empty, throw a TypeError exception. + if (entry !== undefined) { + return Throw.TypeError('Private element $1 is already defined on $2', P, O); + } + // 3. Append PrivateElement { [[Key]]: P, [[Kind]]: field, [[Value]]: value } to O.[[PrivateElements]]. + O.PrivateElements.push(PrivateElementRecord({ + Key: P, + Kind: 'field', + Value: value, + })); + return undefined; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-initializeprivatemethods */ +export function* InitializePrivateMethods(O: ObjectValue, elementDefinitions: readonly ClassElementDefinitionRecord[]): PlainEvaluator { + const privateMethods: PrivateElementRecord[] = []; + for (const element of elementDefinitions) { + if (element.Key instanceof PrivateName && (element.Kind === 'method' || element.Kind === 'getter' || element.Kind === 'setter' || element.Kind === 'accessor')) { + if (element.Kind === 'method') { + const privateElement = PrivateElementRecord({ + Key: element.Key, + Kind: 'method', + Value: element.Value, + }); + privateMethods.push(privateElement); + } else if (element.Kind === 'accessor') { + const privateElement = PrivateElementRecord({ + Key: element.Key, + Kind: 'accessor', + Get: element.Get, + Set: element.Set, + }); + privateMethods.push(privateElement); + } else { + Assert(element.Kind === 'getter' || element.Kind === 'setter'); + let getter = element.Kind === 'getter' ? element.Get : Value.undefined; + let setter = element.Kind === 'setter' ? element.Set : Value.undefined; + let existing: PrivateElementRecord | undefined; + const e = privateMethods.find(((e) => e.Key === element.Key)); + if (e) { + Assert(e.Kind === 'accessor'); + existing = e; + if (e.Get !== undefined && e.Get !== Value.undefined) { + getter = e.Get; + } + if (e.Set !== undefined && e.Set !== Value.undefined) { + setter = e.Set; + } + } + const privateElement = PrivateElementRecord({ + Key: element.Key, + Kind: 'accessor', + Get: getter, + Set: setter, + }); + if (existing) { + const index = privateMethods.indexOf(existing); + privateMethods[index] = privateElement; + } else { + privateMethods.push(privateElement); + } + } + } + } + for (const method of privateMethods) { + Q(yield* PrivateMethodOrAccessorAdd(O, method)); + } +} diff --git a/src/abstract-ops/promise-operations.mts b/src/abstract-ops/promise-operations.mts new file mode 100644 index 0000000..39d2e83 --- /dev/null +++ b/src/abstract-ops/promise-operations.mts @@ -0,0 +1,448 @@ +import { + HostPromiseRejectionTracker, + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + HostEnqueuePromiseJob, + HostMakeJobCallback, + HostCallJobCallback, +} from '../execution-context/Job.mts'; +import { + ObjectValue, Value, UndefinedValue, BooleanValue, NullValue, type Arguments, +} from '../value.mts'; +import { + AbruptCompletion, + EnsureCompletion, + NormalCompletion, + Q, + ThrowCompletion, + X, +} from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { + Assert, + Call, + Construct, + CreateBuiltinFunction, + Get, + IsCallable, + IsConstructor, + SameValue, + GetFunctionRealm, + isFunctionObject, + type BuiltinFunctionObject, +} from './all.mts'; +import type { + Realm, + ValueEvaluator, JobCallbackRecord, PromiseObject, + ValueCompletion, +} from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-promise-objects */ + + +/** https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions */ +export interface PromiseAllResolveElementFunctionObject extends BuiltinFunctionObject { + readonly Index: number; + readonly AlreadyCalled: { Value: boolean }; +} + +/** https://tc39.es/ecma262/#sec-promise.any-reject-element-functions */ +export interface PromiseAllRejectElementFunctionObject extends BuiltinFunctionObject { + readonly Index: number; + readonly AlreadyCalled: { Value: boolean }; +} + +/** https://tc39.es/ecma262/#sec-promisecapability-records */ +export class PromiseCapabilityRecord { + readonly Promise!: PromiseObject; + + readonly Resolve: Value = Value.undefined; + + readonly Reject: Value = Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-promisereaction-records */ +export class PromiseReactionRecord { + readonly Capability: PromiseCapabilityRecord | UndefinedValue; + + readonly Type: 'Fulfill' | 'Reject'; + + readonly Handler: JobCallbackRecord | undefined; + + constructor(O: PromiseReactionRecord) { + 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; + } +} + +/** https://tc39.es/ecma262/#sec-createresolvingfunctions */ +export function CreateResolvingFunctions(promise: PromiseObject) { + // 1. Let alreadyResolved be the Record { [[Value]]: false }. + const alreadyResolved = { Value: false }; + // 2. Let resolveSteps be the algorithm steps defined in Promise Resolve Functions. + const resolveSteps = function* PromiseResolveFunctions([resolution = Value.undefined]: Arguments): ValueEvaluator { + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if (alreadyResolved.Value) { + return Value.undefined; + } + Q(surroundingAgent.debugger_tryTouchDuringPreview(promise)); + // 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). + RejectPromise(promise, selfResolutionError); + return Value.undefined; + } + // 8. If Type(resolution) is not Object, then + if (!(resolution instanceof ObjectValue)) { + // a. Return FulfillPromise(promise, resolution). + FulfillPromise(promise, resolution); + return Value.undefined; + } + // 9. Let then be Get(resolution, "then"). + const then = EnsureCompletion(yield* Get(resolution, Value('then'))); + // 10. If then is an abrupt completion, then + if (then instanceof AbruptCompletion) { + // a. Return RejectPromise(promise, then.[[Value]]). + RejectPromise(promise, then.Value); + return Value.undefined; + } + // 11. Let thenAction be then.[[Value]]. + const thenAction = then.Value; + // 12. If IsCallable(thenAction) is false, then + if (!IsCallable(thenAction)) { + // a. Return FulfillPromise(promise, resolution). + FulfillPromise(promise, resolution); + return Value.undefined; + } + if (surroundingAgent.debugger_isPreviewing) { + return Value.undefined; + } + // 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; + }; + // 4. Let resolve be CreateBuiltinFunction(resolveSteps, 1, "", « »). + const resolve = CreateBuiltinFunction(resolveSteps, 1, Value(''), []); + // 7. Let rejectSteps be the algorithm steps defined in Promise Reject Functions. + const rejectSteps = function PromiseRejectFunctions([reason = Value.undefined]: Arguments): ValueCompletion { + if (alreadyResolved.Value) { + return Value.undefined; + } + Q(surroundingAgent.debugger_tryTouchDuringPreview(promise)); + alreadyResolved.Value = true; + RejectPromise(promise, reason); + return Value.undefined; + }; + // 9. Let reject be CreateBuiltinFunction(rejectSteps, 1, "", « »). + const reject = CreateBuiltinFunction(rejectSteps, 1, Value(''), []); + // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. + return { + Resolve: resolve, + Reject: reject, + }; +} + +/** https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob */ +function NewPromiseResolveThenableJob(promiseToResolve: PromiseObject, thenable: Value, then: JobCallbackRecord) { + // 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: + function* job() { + // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). + const resolvingFunctions = CreateResolvingFunctions(promiseToResolve); + // b. Let thenCallResult be HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). + const thenCallResult = yield* 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 = yield* Call(resolvingFunctions.Reject, Value.undefined, [thenCallResult.Value]); + // ii. Return Completion(status). + return status; + } + // d. Return Completion(thenCallResult). + return EnsureCompletion(thenCallResult); + } + // 2. Let getThenRealmResult be GetFunctionRealm(then.[[Callback]]). + const getThenRealmResult = EnsureCompletion(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 }; +} + +/** https://tc39.es/ecma262/#sec-fulfillpromise */ +function FulfillPromise(promise: PromiseObject, value: 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); +} + +/** https://tc39.es/ecma262/#sec-newpromisecapability */ +export function* NewPromiseCapability(C: Value): PlainEvaluator { + // 1. If IsConstructor(C) is false, throw a TypeError exception. + if (!IsConstructor(C)) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 26.2.3.1). + // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }. + const promiseCapability = new PromiseCapabilityRecord() as Mutable; + // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: + const executorClosure = ([resolve = Value.undefined, reject = Value.undefined]: Arguments) => { + // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. + if (!(promiseCapability.Resolve instanceof UndefinedValue)) { + return surroundingAgent.Throw('TypeError', 'PromiseCapabilityFunctionAlreadySet', 'resolve'); + } + // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception. + if (!(promiseCapability.Reject instanceof UndefinedValue)) { + return surroundingAgent.Throw('TypeError', 'PromiseCapabilityFunctionAlreadySet', 'reject'); + } + // c. Set promiseCapability.[[Resolve]] to resolve. + promiseCapability.Resolve = resolve; + // d. Set promiseCapability.[[Reject]] to reject. + promiseCapability.Reject = reject; + // e. Return undefined. + return Value.undefined; + }; + // 5. Let executor be ! CreateBuiltinFunction(executorClosure, 2, "", « »). + const executor = X(CreateBuiltinFunction(executorClosure, 2, Value(''), [])); + // 8. Let promise be ? Construct(C, « executor »). + const promise = Q(yield* Construct(C, [executor])) as PromiseObject; + // 9. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception. + if (!IsCallable(promiseCapability.Resolve)) { + return surroundingAgent.Throw('TypeError', 'PromiseResolveFunction', promiseCapability.Resolve); + } + // 10. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception. + if (!IsCallable(promiseCapability.Reject)) { + return surroundingAgent.Throw('TypeError', 'PromiseRejectFunction', promiseCapability.Reject); + } + // 11. Set promiseCapability.[[Promise]] to promise. + promiseCapability.Promise = promise; + // 12. Return promiseCapability. + return NormalCompletion(promiseCapability); +} + +/** https://tc39.es/ecma262/#sec-ispromise */ +export function IsPromise(x: Value): BooleanValue { + if (!(x instanceof ObjectValue)) { + return Value.false; + } + if (!('PromiseState' in x)) { + return Value.false; + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-rejectpromise */ +function RejectPromise(promise: PromiseObject, reason: Value) { + 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); +} + +/** https://tc39.es/ecma262/#sec-triggerpromisereactions */ +function TriggerPromiseReactions(reactions: readonly PromiseReactionRecord[], argument: Value) { + // 1. For each reaction in reactions, 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; +} + +/** https://tc39.es/ecma262/#sec-promise-resolve */ +export function* PromiseResolve(C: ObjectValue, x: Value): ValueEvaluator { + Assert(C instanceof ObjectValue); + if (IsPromise(x) === Value.true) { + const xConstructor = Q(yield* Get(x as PromiseObject, Value('constructor'))); + if (SameValue(xConstructor, C) === Value.true) { + return x as PromiseObject; + } + } + const promiseCapability = Q(yield* NewPromiseCapability(C)); + Q(yield* Call(promiseCapability.Resolve, Value.undefined, [x])); + return promiseCapability.Promise; +} + +/** https://tc39.es/ecma262/#sec-newpromisereactionjob */ +function NewPromiseReactionJob(reaction: PromiseReactionRecord, argument: Value) { + // 1. Let job be a new Job abstract closure with no parameters that captures + // reaction and argument and performs the following steps when called: + function* 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: ValueCompletion; + // 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 = yield* HostCallJobCallback(handler, Value.undefined, [argument]); + } + // g. If promiseCapability is undefined, then + if (promiseCapability instanceof UndefinedValue) { + // 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 = yield* Call(promiseCapability.Reject, Value.undefined, [handlerResult.Value]); + } else { + // ii. Let status be Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). + status = yield* Call(promiseCapability.Resolve, Value.undefined, [X(handlerResult)]); + } + // j. Return Completion(status). + return status; + } + // 2. Let handlerRealm be null. + let handlerRealm: NullValue | Realm = Value.null; + // 3. If reaction.[[Handler]] is not empty, then + if (reaction.Handler !== undefined) { + // a. Let getHandlerRealmResult be GetFunctionRealm(reaction.[[Handler]].[[Callback]]). + const getHandlerRealmResult = EnsureCompletion(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 }; +} + +/** https://tc39.es/ecma262/#sec-performpromisethen */ +export function PerformPromiseThen(promise: PromiseObject, onFulfilled: Value, onRejected: Value, resultCapability?: PromiseCapabilityRecord | UndefinedValue) { + // 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)) { + // 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)) { + // 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') { + surroundingAgent.debugger_tryTouchDuringPreview(promise); + // 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 instanceof UndefinedValue) { + // a. Return undefined. + return Value.undefined; + } else { // 14. Else, + // a. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } +} diff --git a/src/abstract-ops/proxy-objects.mts b/src/abstract-ops/proxy-objects.mts new file mode 100644 index 0000000..e061695 --- /dev/null +++ b/src/abstract-ops/proxy-objects.mts @@ -0,0 +1,579 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + UndefinedValue, NullValue, ObjectValue, Value, + type ObjectInternalMethods, +} from '../value.mts'; +import { + Q, X, + type ValueCompletion, +} from '../completion.mts'; +import { __ts_cast__, PropertyKeyMap } from '../helpers.mts'; +import type { ProxyObject } from '../intrinsics/Proxy.mts'; +import { + Assert, + MakeBasicObject, + IsConstructor, + IsCallable, + Call, + Construct, + GetMethod, + CreateArrayFromList, + CreateListFromArrayLike, + IsExtensible, + IsPropertyKey, + SameValue, + ToBoolean, + ToPropertyDescriptor, + FromPropertyDescriptor, + CompletePropertyDescriptor, + IsCompatiblePropertyDescriptor, + IsDataDescriptor, + IsAccessorDescriptor, +} from './all.mts'; + +const InternalMethods = { + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof */ + * GetPrototypeOf() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'getPrototypeOf'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('getPrototypeOf'))); + if (trap === Value.undefined) { + return Q(yield* target.GetPrototypeOf()); + } + const handlerProto = Q(yield* Call(trap, handler, [target])); + if (!(handlerProto instanceof ObjectValue) && !(handlerProto instanceof NullValue)) { + return surroundingAgent.Throw('TypeError', 'ProxyGetPrototypeOfInvalid'); + } + const extensibleTarget = Q(yield* IsExtensible(target)); + if (extensibleTarget === Value.true) { + return handlerProto; + } + const targetProto = Q(yield* target.GetPrototypeOf()); + if (SameValue(handlerProto, targetProto) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyGetPrototypeOfNonExtensible'); + } + return handlerProto; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v */ + * SetPrototypeOf(V) { + const O = this; + + Assert(V instanceof ObjectValue || V instanceof NullValue); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'setPrototypeOf'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('setPrototypeOf'))); + if (trap === Value.undefined) { + return Q(yield* target.SetPrototypeOf(V)); + } + const booleanTrapResult = ToBoolean(Q(yield* Call(trap, handler, [target, V]))); + if (booleanTrapResult === Value.false) { + return Value.false; + } + const extensibleTarget = Q(yield* IsExtensible(target)); + if (extensibleTarget === Value.true) { + return Value.true; + } + const targetProto = Q(yield* target.GetPrototypeOf()); + if (SameValue(V, targetProto) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxySetPrototypeOfNonExtensible'); + } + return Value.true; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible */ + * IsExtensible() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'isExtensible'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget; + const trap = Q(yield* GetMethod(handler, Value('isExtensible'))); + if (trap === Value.undefined) { + return Q(yield* IsExtensible(target as ObjectValue)); + } + const booleanTrapResult = ToBoolean(Q(yield* Call(trap, handler, [target]))); + const targetResult = Q(yield* IsExtensible(target as ObjectValue)); + if (SameValue(booleanTrapResult, targetResult) === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyIsExtensibleInconsistent', targetResult); + } + return booleanTrapResult; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions */ + * PreventExtensions() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'preventExtensions'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('preventExtensions'))); + if (trap === Value.undefined) { + return Q(yield* target.PreventExtensions()); + } + const booleanTrapResult = ToBoolean(Q(yield* Call(trap, handler, [target]))); + if (booleanTrapResult === Value.true) { + const extensibleTarget = Q(yield* IsExtensible(target)); + if (extensibleTarget === Value.true) { + return surroundingAgent.Throw('TypeError', 'ProxyPreventExtensionsExtensible'); + } + } + return booleanTrapResult; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p */ + * GetOwnProperty(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(handler instanceof ObjectValue); + // 5. Let target be O.[[ProxyTarget]]. + const target = O.ProxyTarget as ObjectValue; + // 6. Let trap be ? Getmethod(handler, "getOwnPropertyDescriptor"). + const trap = Q(yield* GetMethod(handler, Value('getOwnPropertyDescriptor'))); + // 7. If trap is undefined, then + if (trap === Value.undefined) { + // a. Return ? target.[[GetOwnProperty]](P). + return Q(yield* target.GetOwnProperty(P)); + } + // 8. Let trapResultObj be ? Call(trap, handler, « target, P »). + const trapResultObj = Q(yield* Call(trap, handler, [target, P])); + // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. + if (!(trapResultObj instanceof ObjectValue) && !(trapResultObj instanceof UndefinedValue)) { + return surroundingAgent.Throw('TypeError', 'ProxyGetOwnPropertyDescriptorInvalid', P); + } + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + const targetDesc = Q(yield* target.GetOwnProperty(P)); + // 11. If trapResultObj is undefined, then + if (trapResultObj === Value.undefined) { + // a. If targetDesc is undefined, return undefined. + if (targetDesc instanceof UndefinedValue) { + 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(yield* 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(yield* IsExtensible(target)); + // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). + const resultDesc = Q(yield* 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 instanceof UndefinedValue || 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; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc */ + * DefineOwnProperty(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(handler instanceof ObjectValue); + // 5. Let target be O.[[ProxyTarget]]. + const target = O.ProxyTarget as ObjectValue; + // 6. Let trap be ? GetMethod(handler, "defineProperty"). + const trap = Q(yield* GetMethod(handler, Value('defineProperty'))); + // 7. If trap is undefined, then + if (trap === Value.undefined) { + // a. Return ? target.[[DefineOwnProperty]](P, Desc). + return Q(yield* 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(yield* 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(yield* target.GetOwnProperty(P)); + // 12. Let extensibleTarget be ? IsExtensible(target). + const extensibleTarget = Q(yield* 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 instanceof UndefinedValue) { + // 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; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p */ + * HasProperty(P) { + const O = this; + + Assert(IsPropertyKey(P)); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'has'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('has'))); + if (trap === Value.undefined) { + return Q(yield* target.HasProperty(P)); + } + const booleanTrapResult = ToBoolean(Q(yield* Call(trap, handler, [target, P]))); + if (booleanTrapResult === Value.false) { + const targetDesc = Q(yield* target.GetOwnProperty(P)); + if (!(targetDesc instanceof UndefinedValue)) { + if (targetDesc.Configurable === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyHasNonConfigurable', P); + } + const extensibleTarget = Q(yield* IsExtensible(target)); + if (extensibleTarget === Value.false) { + return surroundingAgent.Throw('TypeError', 'ProxyHasNonExtensible', P); + } + } + } + return booleanTrapResult; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver */ + * Get(P, Receiver) { + const O = this; + + Assert(IsPropertyKey(P)); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'get'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('get'))); + if (trap === Value.undefined) { + return Q(yield* target.Get(P, Receiver)); + } + const trapResult = Q(yield* Call(trap, handler, [target, P, Receiver])); + const targetDesc = Q(yield* target.GetOwnProperty(P)); + if (!(targetDesc instanceof UndefinedValue) && 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; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver */ + * Set(P, V, Receiver) { + const O = this; + + Assert(IsPropertyKey(P)); + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'set'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('set'))); + if (trap === Value.undefined) { + return Q(yield* target.Set(P, V, Receiver)); + } + const booleanTrapResult = ToBoolean(Q(yield* Call(trap, handler, [target, P, V, Receiver]))); + if (booleanTrapResult === Value.false) { + return Value.false; + } + const targetDesc = Q(yield* target.GetOwnProperty(P)); + if (!(targetDesc instanceof UndefinedValue) && 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; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p */ + * Delete(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(handler instanceof ObjectValue); + // 5. Let target be O.[[ProxyTarget]]. + const target = O.ProxyTarget as ObjectValue; + // 6. Let trap be ? GetMethod(handler, "deleteProperty"). + const trap = Q(yield* GetMethod(handler, Value('deleteProperty'))); + // 7. If trap is undefined, then + if (trap === Value.undefined) { + // a. Return ? target.[[Delete]](P). + return Q(yield* target.Delete(P)); + } + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + const booleanTrapResult = ToBoolean(Q(yield* 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(yield* target.GetOwnProperty(P)); + // 11. If targetDesc is undefined, return true. + if (targetDesc instanceof UndefinedValue) { + 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(yield* 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; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys */ + * OwnPropertyKeys() { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'ownKeys'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget as ObjectValue; + const trap = Q(yield* GetMethod(handler, Value('ownKeys'))); + if (trap === Value.undefined) { + return Q(yield* target.OwnPropertyKeys()); + } + const trapResultArray = Q(yield* Call(trap, handler, [target])); + const trapResult = Q(yield* CreateListFromArrayLike(trapResultArray, 'property-key')); + const noDuplicate = new PropertyKeyMap(); + trapResult.forEach((key) => { + noDuplicate.set(key, true); + }); + if (noDuplicate.size !== trapResult.length) { + return surroundingAgent.Throw('TypeError', 'ProxyOwnKeysDuplicateEntries'); + } + const extensibleTarget = Q(yield* IsExtensible(target)); + const targetKeys = Q(yield* 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(yield* target.GetOwnProperty(key)); + if (!(desc instanceof UndefinedValue) && desc.Configurable === Value.false) { + targetNonconfigurableKeys.push(key); + } else { + targetConfigurableKeys.push(key); + } + } + if (extensibleTarget === Value.true && targetNonconfigurableKeys.length === 0) { + return trapResult; + } + const uncheckedResultKeys = new PropertyKeyMap(); + trapResult.forEach((key) => { + uncheckedResultKeys.set(key, true); + }); + 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; + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist */ + * Call(thisArgument, argumentsList) { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'apply'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget; + const trap = Q(yield* GetMethod(handler, Value('apply'))); + if (trap === Value.undefined) { + return Q(yield* Call(target, thisArgument, argumentsList)); + } + const argArray = X(CreateArrayFromList(argumentsList)); + return Q(yield* Call(trap, handler, [target, thisArgument, argArray])); + }, + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget */ + * Construct(argumentsList, newTarget) { + const O = this; + + const handler = O.ProxyHandler; + if (handler === Value.null) { + return surroundingAgent.Throw('TypeError', 'ProxyRevoked', 'construct'); + } + Assert(handler instanceof ObjectValue); + const target = O.ProxyTarget; + Assert(IsConstructor(target)); + const trap = Q(yield* GetMethod(handler, Value('construct'))); + if (trap === Value.undefined) { + return Q(yield* Construct(target, argumentsList, newTarget)); + } + const argArray = X(CreateArrayFromList(argumentsList)); + const newObj = Q(yield* Call(trap, handler, [target, argArray, newTarget])); + if (!(newObj instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', newObj); + } + return newObj; + }, +} satisfies ObjectInternalMethods; + +/** https://tc39.es/ecma262/#sec-proxycreate */ +export function ProxyCreate(target: Value, handler: Value): ValueCompletion { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'CannotCreateProxyWith', 'non-object', 'target'); + } + // 2. If Type(handler) is not Object, throw a TypeError exception. + if (!(handler instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'CannotCreateProxyWith', 'non-object', 'handler'); + } + // 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »). + const P = X(MakeBasicObject(['ProxyHandler', 'ProxyTarget'])) as ProxyObject; + // 4. Set P's essential internal methods, except for [[Call]] and [[Construct]], to the definitions specified in 9.5. + P.GetPrototypeOf = InternalMethods.GetPrototypeOf; + P.SetPrototypeOf = InternalMethods.SetPrototypeOf; + P.IsExtensible = InternalMethods.IsExtensible; + P.PreventExtensions = InternalMethods.PreventExtensions; + P.GetOwnProperty = InternalMethods.GetOwnProperty; + P.DefineOwnProperty = InternalMethods.DefineOwnProperty; + P.HasProperty = InternalMethods.HasProperty; + P.Get = InternalMethods.Get; + P.Set = InternalMethods.Set; + P.Delete = InternalMethods.Delete; + P.OwnPropertyKeys = InternalMethods.OwnPropertyKeys; + // 5. If IsCallable(target) is true, then + if (IsCallable(target)) { + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist. */ + P.Call = InternalMethods.Call; + // b. If IsConstructor(target) is true, then + if (IsConstructor(target)) { + /** https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget. */ + P.Construct = InternalMethods.Construct; + } + } + // 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/src/abstract-ops/realms.mts b/src/abstract-ops/realms.mts new file mode 100644 index 0000000..bdc15ed --- /dev/null +++ b/src/abstract-ops/realms.mts @@ -0,0 +1,200 @@ +import { + Descriptor, + Value, +} from '../value.mts'; +import { X } from '../completion.mts'; +import { + ObjectValue, type BuiltinFunctionObject, type FunctionObject + , +} from '../index.mts'; +import type { Realm } from '../execution-context/Realm.mts'; +import { + Assert, + DefinePropertyOrThrow, +} from './all.mts'; + +/** https://tc39.es/ecma262/#table-well-known-intrinsic-objects */ +interface Intrinsics_Table6 { + '%AggregateError%': FunctionObject; + '%Array%': FunctionObject; + '%ArrayBuffer%': FunctionObject; + '%ArrayIteratorPrototype%': ObjectValue; + '%AsyncFromSyncIteratorPrototype%': ObjectValue; + '%AsyncFunction%': FunctionObject; + '%AsyncGeneratorFunction%': FunctionObject; + '%AsyncGeneratorPrototype%': ObjectValue; + '%AsyncIteratorPrototype%': ObjectValue; + '%Atomics%': ObjectValue; + '%BigInt%': FunctionObject; + '%BigInt64Array%': FunctionObject; + '%BigUint64Array%': FunctionObject; + '%Boolean%': FunctionObject; + '%DataView%': FunctionObject; + '%Date%': FunctionObject; + '%decodeURI%': FunctionObject; + '%decodeURIComponent%': FunctionObject; + '%encodeURI%': FunctionObject; + '%encodeURIComponent%': FunctionObject; + '%Error%': FunctionObject; + '%eval%': FunctionObject; + '%EvalError%': FunctionObject; + '%FinalizationRegistry%': FunctionObject; + '%Float16Array%': FunctionObject; + '%Float32Array%': FunctionObject; + '%Float64Array%': FunctionObject; + '%ForInIteratorPrototype%': ObjectValue; + '%Function%': FunctionObject; + '%GeneratorFunction%': FunctionObject; + '%GeneratorPrototype%': ObjectValue; + '%Int8Array%': FunctionObject; + '%Int16Array%': FunctionObject; + '%Int32Array%': FunctionObject; + '%isFinite%': FunctionObject; + '%isNaN%': FunctionObject; + '%Iterator%': FunctionObject; + '%IteratorHelperPrototype%': ObjectValue; + '%JSON%': ObjectValue; + '%Map%': FunctionObject; + '%MapIteratorPrototype%': ObjectValue; + '%Math%': ObjectValue; + '%Number%': FunctionObject; + '%Object%': FunctionObject; + '%parseFloat%': FunctionObject; + '%parseInt%': FunctionObject; + '%Promise%': FunctionObject; + '%Proxy%': FunctionObject; + '%RangeError%': FunctionObject; + '%ReferenceError%': FunctionObject; + '%Reflect%': ObjectValue; + '%RegExp%': FunctionObject; + '%RegExpStringIteratorPrototype%': ObjectValue; + '%Set%': FunctionObject; + '%SetIteratorPrototype%': ObjectValue; + '%SharedArrayBuffer%': FunctionObject; + '%String%': FunctionObject; + '%StringIteratorPrototype%': ObjectValue; + '%Symbol%': FunctionObject; + '%SyntaxError%': FunctionObject; + '%ThrowTypeError%': FunctionObject; + '%TypedArray%': FunctionObject; + '%TypeError%': FunctionObject; + '%Uint8Array%': FunctionObject; + '%Uint8ClampedArray%': FunctionObject; + '%Uint16Array%': FunctionObject; + '%Uint32Array%': FunctionObject; + '%URIError%': FunctionObject; + '%WeakMap%': FunctionObject; + '%WeakRef%': FunctionObject; + '%WeakSet%': FunctionObject; + '%WrapForValidIteratorPrototype%': ObjectValue; +} +export interface Intrinsics extends Intrinsics_Table6 { + '%AggregateError.prototype%': ObjectValue; + '%Array.prototype.values%': FunctionObject; + '%Array.prototype%': ObjectValue; + '%ArrayBuffer.prototype%': ObjectValue; + '%AsyncFunction.prototype%': ObjectValue; + '%AsyncGeneratorFunction.prototype.prototype%': ObjectValue; + '%AsyncGeneratorFunction.prototype%': ObjectValue; + '%BigInt.prototype%': ObjectValue; + '%BigInt64Array.prototype%': ObjectValue; + '%BigInt64Array%': FunctionObject; + '%BigUint64Array.prototype%': ObjectValue; + '%BigUint64Array%': FunctionObject; + '%Boolean.prototype%': ObjectValue; + '%DataView.prototype%': ObjectValue; + '%Date.prototype%': ObjectValue; + '%Error.prototype%': ObjectValue; + '%Error.prototype.toString%': BuiltinFunctionObject; + '%EvalError.prototype%': ObjectValue; + '%EvalError%': FunctionObject; + '%FinalizationRegistry.prototype%': ObjectValue; + '%Float32Array.prototype%': ObjectValue; + '%Float32Array%': FunctionObject; + '%Float64Array.prototype%': ObjectValue; + '%Float64Array%': FunctionObject; + '%Function.prototype%': FunctionObject; + '%GeneratorFunction.prototype.prototype.next%': FunctionObject; + '%GeneratorFunction.prototype.prototype%': ObjectValue; + '%GeneratorFunction.prototype%': ObjectValue; + '%Int16Array.prototype%': ObjectValue; + '%Int16Array%': FunctionObject; + '%Int32Array.prototype%': ObjectValue; + '%Int32Array%': FunctionObject; + '%Int8Array.prototype%': ObjectValue; + '%Int8Array%': FunctionObject; + '%Iterator.prototype%': ObjectValue; + '%JSON.parse%': FunctionObject; + '%JSON.stringify%': FunctionObject; + '%Map.prototype%': ObjectValue; + '%Number.prototype%': ObjectValue; + '%Object.prototype.toString%': BuiltinFunctionObject; + '%Object.prototype.valueOf%': FunctionObject; + '%Object.prototype%': ObjectValue; + '%Promise.prototype.then%': FunctionObject; + '%Promise.prototype%': ObjectValue; + '%Promise.resolve%': FunctionObject; + '%RangeError.prototype%': ObjectValue; + '%RangeError%': FunctionObject; + '%ReferenceError.prototype%': ObjectValue; + '%ReferenceError%': FunctionObject; + '%RegExp.prototype%': ObjectValue; + '%Set.prototype%': ObjectValue; + '%ShadowRealm%': FunctionObject; + '%ShadowRealm.prototype%': ObjectValue; + '%String.prototype%': ObjectValue; + // Note: do not add any well known symbols here, use wellKnownSymbols.* + '%Symbol.prototype%': ObjectValue; + '%SyntaxError.prototype%': ObjectValue; + '%SyntaxError%': FunctionObject; + '%Temporal%': ObjectValue; + '%Temporal.Duration%': FunctionObject; + '%Temporal.Duration.prototype%': ObjectValue; + '%Temporal.Instant%': FunctionObject; + '%Temporal.Instant.prototype%': ObjectValue; + '%Temporal.PlainDate%': FunctionObject; + '%Temporal.PlainDate.prototype%': ObjectValue; + '%Temporal.PlainDateTime%': FunctionObject; + '%Temporal.PlainDateTime.prototype%': ObjectValue; + '%Temporal.PlainMonthDay%': FunctionObject; + '%Temporal.PlainMonthDay.prototype%': ObjectValue; + '%Temporal.PlainYearMonth%': FunctionObject; + '%Temporal.PlainYearMonth.prototype%': ObjectValue; + '%Temporal.PlainTime%': FunctionObject; + '%Temporal.PlainTime.prototype%': ObjectValue; + '%Temporal.ZonedDateTime%': FunctionObject; + '%Temporal.ZonedDateTime.prototype%': ObjectValue; + '%TypedArray.prototype%': ObjectValue; + '%TypeError.prototype%': ObjectValue; + '%TypeError%': FunctionObject; + '%Uint16Array.prototype%': ObjectValue; + '%Uint16Array%': FunctionObject; + '%Uint32Array.prototype%': ObjectValue; + '%Uint32Array%': FunctionObject; + '%Uint8Array.prototype%': ObjectValue; + '%Uint8Array%': FunctionObject; + '%Uint8ClampedArray.prototype%': ObjectValue; + '%Uint8ClampedArray%': FunctionObject; + '%URIError.prototype%': ObjectValue; + '%URIError%': FunctionObject; + '%WeakMap.prototype%': ObjectValue; + '%WeakRef.prototype%': ObjectValue; + '%WeakSet.prototype%': ObjectValue; +} + +export function AddRestrictedFunctionProperties(F: ObjectValue, realm: Realm) { + Assert(!!realm.Intrinsics['%ThrowTypeError%']); + const thrower = realm.Intrinsics['%ThrowTypeError%']; + X(DefinePropertyOrThrow(F, Value('caller'), Descriptor({ + Get: thrower, + Set: thrower, + Enumerable: Value.false, + Configurable: Value.true, + }))); + X(DefinePropertyOrThrow(F, Value('arguments'), Descriptor({ + Get: thrower, + Set: thrower, + Enumerable: Value.false, + Configurable: Value.true, + }))); +} diff --git a/src/abstract-ops/reference-operations.mts b/src/abstract-ops/reference-operations.mts new file mode 100644 index 0000000..a268a6a --- /dev/null +++ b/src/abstract-ops/reference-operations.mts @@ -0,0 +1,211 @@ +import { DynamicParsedCodeRecord, surroundingAgent } from '../host-defined/engine.mts'; +import { + ReferenceRecord, + Value, + PrivateName, + JSStringValue, + NullValue, + ObjectValue, +} from '../value.mts'; +import { + Q, + type PlainCompletion, +} from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { ResolvePrivateIdentifier } from '../execution-context/PrivateEnvironment.mts'; +import { + Assert, + ToObject, + Set, + PrivateGet, + PrivateSet, + IsPropertyKey, + ToPropertyKey, + getActiveScriptId, +} from './all.mts'; +import { EnvironmentRecord, GetGlobalObject } from '#self'; + +/** https://tc39.es/ecma262/#sec-ispropertyreference */ +export function IsPropertyReference(V: ReferenceRecord) { + // 1. If V.[[Base]] is unresolvable, return false. + if (V.Base === 'unresolvable') { + return Value.false; + } + // 2. If V.[[Base]] is an Environment Record, return false; otherwise return true. + return V.Base instanceof EnvironmentRecord ? Value.false : Value.true; +} +export type PropertyReference = ReferenceRecord & { + readonly Base: Exclude, +}; + +/** https://tc39.es/ecma262/#sec-isunresolvablereference */ +export function IsUnresolvableReference(V: ReferenceRecord) { + // 1. Assert: V is a Reference Record. + Assert(V instanceof ReferenceRecord); + // 2. If V.[[Base]] is unresolvable, return true; otherwise return false. + return V.Base === 'unresolvable' ? Value.true : Value.false; +} + +/** https://tc39.es/ecma262/#sec-issuperreference */ +export function IsSuperReference(V: ReferenceRecord) { + // 1. Assert: V is a Reference Record. + Assert(V instanceof ReferenceRecord); + // 2. If V.[[ThisValue]] is not empty, return true; otherwise return false. + return V.ThisValue !== undefined ? Value.true : Value.false; +} + +/** https://tc39.es/ecma262/#sec-isprivatereference */ +export function IsPrivateReference(V: ReferenceRecord): V is ReferenceRecord & { readonly ReferencedName: PrivateName } { + // 1. Assert: V is a Reference Record. + Assert(V instanceof ReferenceRecord); + // 2. If V.[[ReferencedName]] is a Private Name, return true; otherwise return false. + return V.ReferencedName instanceof PrivateName; +} + +/** https://tc39.es/ecma262/#sec-getvalue */ +export function* GetValue(V: ReferenceRecord | Value): PlainEvaluator { + // 1. If V is not a Reference Record, return V. + if (!(V instanceof ReferenceRecord)) { + return V; + } + // 2. If IsUnresolvableReference(V) is true, throw a ReferenceError exception. + if (IsUnresolvableReference(V) === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', V.ReferencedName); + } + // 3. If IsPropertyReference(V) is true, then + if (IsPropertyReference(V) === Value.true) { + __ts_cast__(V); + // a. Let baseObj be ? ToObject(V.[[Base]]). + const baseObj = Q(ToObject(V.Base)); + // b. If IsPrivateReference(V) is true, then + if (IsPrivateReference(V)) { + // i. Return ? PrivateGet(baseObj, V.[[ReferencedName]]). + return Q(yield* PrivateGet(baseObj, V.ReferencedName)); + } + if (!IsPropertyKey(V.ReferencedName)) { + V.ReferencedName = Q(yield* ToPropertyKey(V.ReferencedName as Value)); + } + // c. Return ? baseObj.[[Get]](V.[[ReferencedName]], GetThisValue(V)). + return Q(yield* baseObj.Get(V.ReferencedName, GetThisValue(V))); + } else { // 5. Else, + // a. Let base be V.[[Base]]. + const base = V.Base; + // b. Assert: base is an Environment Record. + Assert(base instanceof EnvironmentRecord); + // c. Return ? base.GetBindingValue(V.[[ReferencedName]], V.[[Strict]]). + return Q(yield* base.GetBindingValue(V.ReferencedName as JSStringValue, V.Strict)); + } +} + +/** https://tc39.es/ecma262/#sec-putvalue */ +export function* PutValue(V: ReferenceRecord | Value, W: Value): PlainEvaluator { + // 1. If V is not a Reference Record, throw a ReferenceError exception. + if (!(V instanceof ReferenceRecord)) { + return surroundingAgent.Throw('ReferenceError', 'InvalidAssignmentTarget'); + } + // 2. If IsUnresolvableReference(V) is true, then + if (IsUnresolvableReference(V) === Value.true) { + // a. If V.[[Strict]] is true, throw a ReferenceError exception. + if (V.Strict === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', V.ReferencedName); + } + // b. Let globalObj be GetGlobalObject(). + const globalObj = GetGlobalObject(); + // c. Return ? Set(globalObj, V.[[ReferencedName]], W, false). + Q(yield* Set(globalObj, V.ReferencedName as JSStringValue, W, Value.false)); + return undefined; + } + // 5. If IsPropertyReference(V) is true, then + if (IsPropertyReference(V) === Value.true) { + // a. Let baseObj be ? ToObject(V.[[Base]]). + const baseObj = Q(ToObject(V.Base as JSStringValue)); + // b. If IsPrivateReference(V) is true, then + if (IsPrivateReference(V)) { + // i. Return ? PrivateSet(baseObj, V.[[ReferencedName]], W). + return Q(yield* PrivateSet(baseObj, V.ReferencedName, W)); + } + if (!IsPropertyKey(V.ReferencedName)) { + V.ReferencedName = Q(yield* ToPropertyKey(V.ReferencedName as Value)); + } + // c. Let succeeded be ? baseObj.[[Set]](V.[[ReferencedName]], W, GetThisValue(V)). + const succeeded = Q(yield* baseObj.Set(V.ReferencedName, W, GetThisValue(V))); + // d. If succeeded is false and V.[[Strict]] is true, throw a TypeError exception. + if (succeeded === Value.false && V.Strict === Value.true) { + return surroundingAgent.Throw('TypeError', 'CannotSetProperty', V.ReferencedName, V.Base); + } + // e. Return. + return undefined; + } else { // 6. Else, + // a. Let base be V.[[Base]]. + const base = V.Base; + // b. Assert: base is an Environment Record. + Assert(base instanceof EnvironmentRecord); + // c. Return ? base.SetMutableBinding(V.[[ReferencedName]], W, V.[[Strict]]) (see 9.1). + return Q(yield* base.SetMutableBinding(V.ReferencedName as JSStringValue, W, V.Strict)); + } +} + +/** https://tc39.es/ecma262/#sec-getthisvalue */ +export function GetThisValue(V: ReferenceRecord) { + // 1. Assert: IsPropertyReference(V) is true. + Assert(IsPropertyReference(V) === Value.true); + // 2. If IsSuperReference(V) is true, return V.[[ThisValue]]; otherwise return V.[[Base]]. + if (IsSuperReference(V) === Value.true) { + return V.ThisValue!; + } else { + return V.Base as Value; + } +} + +/** https://tc39.es/ecma262/#sec-initializereferencedbinding */ +export function* InitializeReferencedBinding(V: PlainCompletion, W: Value): PlainEvaluator { + Q(V); + Q(W); + // 3. Assert: V is a Reference Record. + Assert(V instanceof ReferenceRecord); + // 4. Assert: IsUnresolvableReference(V) is false. + Assert(IsUnresolvableReference(V) === Value.false); + // 5. Let base be V.[[Base]]. + const base = V.Base; + // 6. Assert: base is an Environment Record. + Assert(base instanceof EnvironmentRecord); + // 7. Return base.InitializeBinding(V.[[ReferencedName]], W). + return yield* base.InitializeBinding(V.ReferencedName as JSStringValue, W); +} + +/** https://tc39.es/ecma262/#sec-makeprivatereference */ +export function MakePrivateReference(baseValue: Value, privateIdentifier: JSStringValue) { + // 1. Let privEnv be the running execution context's PrivateEnvironment. + const privEnv = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 2. Assert: privEnv is not null. + // but we allow private reference to be accessed directly in the inspector eval + if (privEnv instanceof NullValue) { + const scriptId = getActiveScriptId(); + const script = surroundingAgent.parsedSources.get(scriptId!); + if (script instanceof DynamicParsedCodeRecord && script?.HostDefined?.isInspectorEval) { + let privateName; + if (baseValue instanceof ObjectValue) { + privateName = baseValue.PrivateElements.find((elem) => elem.Key.Description.stringValue() === privateIdentifier.stringValue())?.Key; + } + privateName ??= new PrivateName(privateIdentifier); + return new ReferenceRecord({ + Base: baseValue, + ReferencedName: privateName, + Strict: Value.true, + ThisValue: undefined, + }); + } else { + Assert(!(privEnv instanceof NullValue)); + } + } + // 3. Let privateName be ! ResolvePrivateIdentifier(privEnv, privateIdentifier). + const privateName = ResolvePrivateIdentifier(privEnv, privateIdentifier); + // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: privateName, [[Strict]]: true, [[ThisValue]]: empty }. + return new ReferenceRecord({ + Base: baseValue, + ReferencedName: privateName, + Strict: Value.true, + ThisValue: undefined, + }); +} diff --git a/src/abstract-ops/regexp-objects.mts b/src/abstract-ops/regexp-objects.mts new file mode 100644 index 0000000..6c35d7e --- /dev/null +++ b/src/abstract-ops/regexp-objects.mts @@ -0,0 +1,306 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Descriptor, Value, ObjectValue, BooleanValue, JSStringValue, + UndefinedValue, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { CompilePattern, CountLeftCapturingParensWithin, type RegExpRecord } from '../runtime-semantics/all.mts'; +import { ParsePattern } from '../parse.mts'; +import { isLineTerminator } from '../parser/Lexer.mts'; +import type { Mutable } from '../helpers.mts'; +import type { RegExpObject } from '../intrinsics/RegExp.mts'; +import { + ArrayCreate, + Assert, + CreateArrayFromList, + CreateDataPropertyOrThrow, + DefinePropertyOrThrow, + OrdinaryCreateFromConstructor, + OrdinaryObjectCreate, + SameValue, + Set, + ToString, + F as toNumberValue, + type FunctionObject, +} from './all.mts'; + +/** https://tc39.es/ecma262/#sec-regexpalloc */ +export function* RegExpAlloc(newTarget: FunctionObject): ValueEvaluator { + const obj = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%RegExp.prototype%', ['RegExpMatcher', 'OriginalSource', 'OriginalFlags'])) as Mutable; + X(DefinePropertyOrThrow(obj, Value('lastIndex'), Descriptor({ + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + return obj; +} + +/** https://tc39.es/ecma262/#sec-regexpinitialize */ +export function* RegExpInitialize(obj: Mutable, pattern: Value, flags: Value) { + let P: JSStringValue; + // 1. If pattern is undefined, let P be the empty String. + if (pattern === Value.undefined) { + P = Value(''); + } else { // 2. Else, let P be ? ToString(pattern). + P = Q(yield* ToString(pattern)); + } + let F; + // 3. If flags is undefined, let F be the empty String. + if (flags === Value.undefined) { + F = Value(''); + } else { // 4. Else, let F be ? ToString(flags). + F = Q(yield* ToString(flags)); + } + const f = F.stringValue(); + // 5. If F contains any code unit other than "d", "g", "i", "m", "s", "u", "v", or "y" or if it contains the same code unit more than once, throw a SyntaxError exception. + if (/^[dgimsuvy]*$/.test(f) === false || (new globalThis.Set(f).size !== f.length)) { + return surroundingAgent.Throw('SyntaxError', 'InvalidRegExpFlags', f); + } + const i = f.includes('i'); + const m = f.includes('m'); + const s = f.includes('s'); + const u = f.includes('u'); + const v = f.includes('v'); + + // 11. If u is true or v is true, then + // a. Let patternText be StringToCodePoints(P). + // 12. 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. + const patternText = P.stringValue(); + + const parseResult = ParsePattern(patternText, u, v); + if (Array.isArray(parseResult)) { + return surroundingAgent.Throw(parseResult[0], 'Raw', parseResult[0]); + } + obj.OriginalSource = P; + obj.OriginalFlags = F; + const capturingGroupsCount = CountLeftCapturingParensWithin(parseResult); + const rer: RegExpRecord = { + IgnoreCase: i, + Multiline: m, + DotAll: s, + Unicode: u, + UnicodeSets: v, + CapturingGroupsCount: capturingGroupsCount, + }; + obj.RegExpRecord = rer; + obj.parsedPattern = parseResult; + obj.RegExpMatcher = CompilePattern(parseResult, rer); + Q(yield* Set(obj, Value('lastIndex'), toNumberValue(+0), Value.true)); + return obj; +} + +/** https://tc39.es/ecma262/#sec-regexpcreate */ +export function* RegExpCreate(P: Value, F: Value): ValueEvaluator { + const obj = Q(yield* RegExpAlloc(surroundingAgent.intrinsic('%RegExp%'))); + return Q(yield* RegExpInitialize(obj, P, F)); +} + +/** https://tc39.es/ecma262/#sec-escaperegexppattern */ +export function EscapeRegExpPattern(P: JSStringValue, _F: Value) { + const source = P.stringValue(); + if (source === '') { + return Value('(?:)'); + } + let index = 0; + let escaped = ''; + let inClass = false; + let isEscape = false; + while (index < source.length) { + const c = source[index]; + switch (c) { + case '\\': + index += 1; + if (isLineTerminator(source[index])) { + // nothing + } else { + isEscape = !isEscape; + escaped += '\\'; + } + break; + case '/': + index += 1; + if (inClass || isEscape) { + isEscape = false; + escaped += '/'; + } else { + escaped += '\\/'; + } + break; + case '[': + inClass = !isEscape; + index += 1; + escaped += '['; + break; + case ']': + inClass = !isEscape; + 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; + } + if (c !== '\\') { + isEscape = false; + } + } + return Value(escaped); +} + +/** https://tc39.es/ecma262/#sec-getstringindex */ +export function GetStringIndex(S: JSStringValue, Input: readonly string[], e: number) { + // 1. Assert: Type(S) is String. + Assert(S instanceof JSStringValue); + // 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. + Assert(e >= 0); + // 4. If S is the empty String, return 0. + if (S.stringValue() === '') { + return 0; + } + // 5. 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; + } + } + // 6. Return eUTF. + return eUTF; +} + +export interface MatchRecord { + readonly StartIndex: number; + readonly EndIndex: number; +} +/** https://tc39.es/ecma262/#sec-getmatchstring */ +export function GetMatchString(S: JSStringValue, match: MatchRecord) { + // 1. Assert: Type(S) is String. + Assert(S instanceof JSStringValue); + // 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 Value(S.stringValue().slice(match.StartIndex, match.EndIndex)); +} + +/** https://tc39.es/ecma262/#sec-getmatchindexpair */ +export function GetMatchIndexPair(S: JSStringValue, match: MatchRecord) { + // 1. Assert: Type(S) is String. + Assert(S instanceof JSStringValue); + // 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([ + toNumberValue(match.StartIndex), + toNumberValue(match.EndIndex), + ]); +} + +/** https://tc39.es/ecma262/#sec-makematchindicesindexpairarray */ +export function MakeMatchIndicesIndexPairArray(S: JSStringValue, indices: readonly (MatchRecord | UndefinedValue)[], groupNames: readonly (JSStringValue | UndefinedValue)[], hasGroups: BooleanValue) { + // 1. Assert: Type(S) is String. + Assert(S instanceof JSStringValue); + // 2. Assert: indices is a List. + Assert(Array.isArray(indices)); + // 3. Let n be the number of elements in indices. + const n = indices.length; + // 4. Assert: n < 2**32-1. + Assert(n < (2 ** 32) - 1); + // 5. Assert: groupNames is a List with _n_ - 1 elements. + Assert(Array.isArray(groupNames) && groupNames.length === n - 1); + // 6. NOTE: The groupNames List contains elements aligned with the indices List starting at indices[1]. + // 7. Assert: Type(hasGroups) is Boolean. + Assert(hasGroups instanceof BooleanValue); + // 8. Set A to ! ArrayCreate(n). + // 9. Assert: The value of A's "length" property is n. + const A = X(ArrayCreate(n)); + // 10. If hasGroups is true, then + let groups: ObjectValue | UndefinedValue; + if (hasGroups === Value.true) { + // a. Let groups be ! ObjectCreate(null). + groups = X(OrdinaryObjectCreate(Value.null)); + } else { // 9. Else, + // b. Let groups be undefined. + groups = Value.undefined; + } + // 11. Perform ! CreateDataProperty(A, "groups", groups). + X(CreateDataPropertyOrThrow(A, Value('groups'), groups)); + // 12. 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 ! GetMatchIndexPair(S, matchIndices). + matchIndicesArray = X(GetMatchIndexPair(S, matchIndices as MatchRecord)); + } else { // c. Else, + // i. Let matchIndicesArray be undefined. + matchIndicesArray = Value.undefined; + } + // d. Perform ! CreateDataProperty(A, ! ToString(𝔽(i)), matchIndicesArray). + X(CreateDataPropertyOrThrow(A, X(ToString(toNumberValue(i))), matchIndicesArray)); + // e. If i > 0 and groupNames[i - 1] is not undefined, then + if (i > 0 && groupNames[i - 1] !== Value.undefined) { + // i. Perform ! CreateDataProperty(groups, groupNames[i - 1], matchIndicesArray). + X(CreateDataPropertyOrThrow(groups as ObjectValue, groupNames[i - 1] as JSStringValue, matchIndicesArray)); + } + } + // 13. Return A. + return A; +} + +/** https://tc39.es/ecma262/#sec-regexphasflag */ +export function RegExpHasFlag(R: Value, codeUnit: string) { + // 1. If Type(R) is not Object, throw a TypeError exception. + if (!(R instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + // 2. If R does not have an [[OriginalFlags]] internal slot, then + if (!('OriginalFlags' in R)) { + // a. If SameValue(R, %RegExp.prototype%) is true, return undefined. + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value.undefined; + } + // b. Otherwise, throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + // 3. Let flags be R.[[OriginalFlags]]. + const flags = (R as RegExpObject).OriginalFlags.stringValue(); + // 4. If flags contains codeUnit, return true. + if (flags.includes(codeUnit)) { + return Value.true; + } + // 5. Return false. + return Value.false; +} diff --git a/src/abstract-ops/shadow-realm.mts b/src/abstract-ops/shadow-realm.mts new file mode 100644 index 0000000..3e73014 --- /dev/null +++ b/src/abstract-ops/shadow-realm.mts @@ -0,0 +1,216 @@ +import { captureStack, isArray, callSiteToErrorStack } from '../helpers.mts'; +import type { ErrorObject } from '../intrinsics/Error.mts'; +import { + Assert, Call, Construct, CopyNameAndLength, CreateBuiltinFunction, DeclarativeEnvironmentRecord, EnvironmentRecord, EvalDeclarationInstantiation, Evaluate, ExecutionContext, Get, GetFunctionRealm, HasOwnProperty, HostEnsureCanCompileStrings, HostLoadImportedModule, IsCallable, isErrorObject, isModuleNamespaceObject, JSStringValue, MakeBasicObject, NewPromiseCapability, NormalCompletion, ObjectValue, Parser, PerformPromiseThen, Q, RequireInternalSlot, surroundingAgent, ThrowCompletion, Value, wrappedParse, X, type Arguments, type BuiltinFunctionObject, type ExoticObject, type FunctionObject, type Mutable, type PlainCompletion, type Realm, type ValueEvaluator, +} from '#self'; + +/** https://tc39.es/proposal-shadowrealm/#table-internal-slots-of-wrapped-function-exotic-objects */ +export interface WrappedFunctionExoticObject extends BuiltinFunctionObject, ExoticObject { + readonly WrappedTargetFunction: FunctionObject; + readonly Realm: Realm; +} + +export function isWrappedFunctionExoticObject(value: Value): value is WrappedFunctionExoticObject { + return 'WrappedTargetFunction' in value; +} + +/** https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist */ +function* WrappedFunction_Call(this: WrappedFunctionExoticObject, thisArgument: Value, argumentList: Arguments): ValueEvaluator { + const F = this; + const callerContext = surroundingAgent.runningExecutionContext; + const calleeContext = PrepareForWrappedFunctionCall(F); + Assert(surroundingAgent.runningExecutionContext === calleeContext); + const result = yield* OrdinaryWrappedFunctionCall(F, thisArgument, argumentList); + surroundingAgent.executionContextStack.pop(calleeContext); + Assert(surroundingAgent.runningExecutionContext === callerContext); + return Q(result); +} + +/** https://tc39.es/proposal-shadowrealm/#sec-create-type-error-copy */ +export function CreateTypeErrorCopy(realmRecord: Realm, non_spec_evalRealm: Realm, originalError: Value): ObjectValue { + realmRecord.HostDefined.attachingInspectorReportError?.(non_spec_evalRealm, originalError); + let message = 'An error occurred in a ShadowRealm.'; + let errorData: string | undefined; + let hostStack: ErrorObject['HostDefinedErrorStack']; + let stack = ''; + if (originalError instanceof ObjectValue) { + if (isErrorObject(originalError)) { + errorData = originalError.ErrorData.stringValue(); + hostStack = originalError.HostDefinedErrorStack; + } else { + const S = captureStack(); + stack = callSiteToErrorStack(S.stack, S.nativeStack); + } + if (originalError.properties.has('message')) { + const messageProp = originalError.properties.get('message'); + if (messageProp && messageProp.Value && messageProp.Value instanceof JSStringValue) { + message = messageProp.Value.stringValue(); + } + } + } + const newError = X(Construct(realmRecord.Intrinsics['%TypeError%'], [Value(message)])) as ErrorObject; + newError.ErrorData = errorData ? Value(errorData) : Value(message + stack); + newError.HostDefinedErrorStack ??= hostStack; + return newError; +} + +/** https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call */ +export function* OrdinaryWrappedFunctionCall(F: WrappedFunctionExoticObject, thisArgument: Value, argumentList: Arguments) { + const target = F.WrappedTargetFunction; + Assert(IsCallable(target)); + const callerRealm = F.Realm; + + // Note: Any exception objects produced after this point are associated with callerRealm. + const targetRealm = Q(GetFunctionRealm(target)); + const wrappedArgs: Value[] = []; + for (const arg of argumentList.values()) { + const wrappedValue = Q(yield* GetWrappedValue(targetRealm, arg)); + wrappedArgs.push(wrappedValue); + } + const wrappedThisArgument = Q(yield* GetWrappedValue(targetRealm, thisArgument)); + const result = yield* Call(target, wrappedThisArgument, wrappedArgs); + if (result instanceof Value || result instanceof NormalCompletion) { + return Q(yield* GetWrappedValue(callerRealm, result instanceof Value ? result : result.Value)); + } else { + const copiedError = CreateTypeErrorCopy(callerRealm, targetRealm, result.Value); + return ThrowCompletion(copiedError); + } +} + +/** https://tc39.es/proposal-shadowrealm/#sec-prepare-for-wrapped-function-call */ +export function PrepareForWrappedFunctionCall(F: WrappedFunctionExoticObject) { + const calleeContext = new ExecutionContext(); + calleeContext.Function = F; + const calleeRealm = F.Realm; + calleeContext.Realm = calleeRealm; + calleeContext.ScriptOrModule = Value.null; + surroundingAgent.executionContextStack.push(calleeContext); + // 9. NOTE: Any exception objects produced after this point are associated with calleeRealm. + return calleeContext; +} + +/** https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate */ +export function* WrappedFunctionCreate(callerRealm: Realm, Target: FunctionObject) { + const internalSlotsList = ['WrappedTargetFunction', 'Call', 'Realm', 'Prototype', 'Extensible']; + const wrapped = MakeBasicObject(internalSlotsList) as Mutable; + wrapped.Prototype = callerRealm.Intrinsics['%Function.prototype%']; + wrapped.Call = WrappedFunction_Call; + wrapped.WrappedTargetFunction = Target; + wrapped.Realm = callerRealm; + const result = yield* CopyNameAndLength(wrapped, Target); + if (result instanceof ThrowCompletion) { + return surroundingAgent.Throw('TypeError', 'Raw', 'Cannot create wrapped function'); + } + return wrapped; +} + +/** https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval */ +export function* PerformShadowRealmEval(sourceText: string, callerRealm: Realm, evalRealm: Realm): ValueEvaluator { + Q(yield* HostEnsureCanCompileStrings(evalRealm, [], sourceText, false)); + const script = wrappedParse({ source: sourceText }, (p) => p.scope.with({ + newTarget: false, + superProperty: false, + superCall: false, + }, () => p.parseScript())); + const scriptId = surroundingAgent.addDynamicParsedSource(surroundingAgent.currentRealmRecord, sourceText, script); + if (isArray(script)) { + Parser.decorateSyntaxErrorWithScriptId(script[0], scriptId); + return ThrowCompletion(script[0]); + } + if (!script.ScriptBody) { + return Value.undefined; + } + + const body = script.ScriptBody; + const strictEval = script.strict; + const evalContext = GetShadowRealmContext(evalRealm, strictEval); + evalContext.HostDefined ??= {}; + evalContext.HostDefined.scriptId = scriptId; + // TODO: spec bug? dynamic import leak + // evalContext.ScriptOrModule = scriptRec; + const lexEnv = evalContext.LexicalEnvironment; + // TODO: spec bug? + Assert(lexEnv instanceof DeclarativeEnvironmentRecord); + const varEnv = evalContext.VariableEnvironment; + surroundingAgent.executionContextStack.push(evalContext); + let result: PlainCompletion = yield* EvalDeclarationInstantiation(body, varEnv, lexEnv, Value.null, strictEval); + if (result instanceof NormalCompletion) { + result = yield* Evaluate(body); + } + if (result === undefined || (result instanceof NormalCompletion && result.Value === undefined)) { + result = NormalCompletion(Value.undefined); + } + surroundingAgent.executionContextStack.pop(evalContext); + if (result instanceof ThrowCompletion) { + const copiedError = CreateTypeErrorCopy(callerRealm, evalRealm, result.Value); + return ThrowCompletion(copiedError); + } + return Q(yield* GetWrappedValue(callerRealm, X(result) || Value.undefined)); +} + +/** https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue */ +export function ShadowRealmImportValue(specifierString: JSStringValue, exportNameString: JSStringValue, callerRealm: Realm, evalRealm: Realm): Value { + const evalContext = GetShadowRealmContext(evalRealm, true); + const innerCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + surroundingAgent.executionContextStack.push(evalContext); + const referrer = evalContext.Realm; + HostLoadImportedModule(referrer, { + Specifier: specifierString, + Phase: 'evaluation', + Attributes: [], + }, undefined, innerCapability); + surroundingAgent.executionContextStack.pop(evalContext); + const onFullfilled = CreateBuiltinFunction(function* onFullfilled([exports = Value.undefined]) { + Assert(isModuleNamespaceObject(exports)); + const f = surroundingAgent.activeFunctionObject as FunctionObject; + const string = exportNameString; + const hasOwn = Q(yield* HasOwnProperty(exports, string)); + if (hasOwn === Value.false) { + return surroundingAgent.Throw('TypeError', 'Raw', `The module does not define an export named ${string.stringValue()}.`); + } + const value = Q(yield* Get(exports, string)); + const realm = f.Realm; + return Q(yield* GetWrappedValue(realm, value)); + }, 1, Value(''), [], callerRealm); + const onRejected = CreateBuiltinFunction((([error = Value.undefined]) => { + // 1. Let realmRecord be the function's associated Realm Record. + const realmRecord = callerRealm; + const copiedError = CreateTypeErrorCopy(realmRecord, evalRealm, error); + return ThrowCompletion(copiedError); + }), 1, Value(''), [], callerRealm); + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + return PerformPromiseThen(innerCapability.Promise, onFullfilled, onRejected, promiseCapability); +} + +/** https://tc39.es/proposal-shadowrealm/#sec-getwrappedvalue */ +export function* GetWrappedValue(callerRealm: Realm, value: Value): ValueEvaluator { + if (value instanceof ObjectValue) { + if (!IsCallable(value)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', value); + } + return Q(yield* WrappedFunctionCreate(callerRealm, value)); + } + return value; +} + +/** https://tc39.es/proposal-shadowrealm/#sec-validateshadowrealmobject */ +export function ValidateShadowRealmObject(O: Value): PlainCompletion { + Q(RequireInternalSlot(O, 'ShadowRealm')); +} + +/** https://tc39.es/proposal-shadowrealm/#sec-getshadowrealmcontext */ +export function GetShadowRealmContext(shadowRealmRecord: Realm, strictEval: boolean): ExecutionContext { + const lexEnv = new DeclarativeEnvironmentRecord(shadowRealmRecord.GlobalEnv); + let varEnv: EnvironmentRecord = shadowRealmRecord.GlobalEnv; + if (strictEval) { + varEnv = lexEnv; + } + const context = new ExecutionContext(); + context.Function = Value.null; + context.Realm = shadowRealmRecord; + context.ScriptOrModule = Value.null; + context.VariableEnvironment = varEnv; + context.LexicalEnvironment = lexEnv; + context.PrivateEnvironment = Value.null; + return context; +} diff --git a/src/abstract-ops/shared-arraybuffer.mts b/src/abstract-ops/shared-arraybuffer.mts new file mode 100644 index 0000000..9cc3e3f --- /dev/null +++ b/src/abstract-ops/shared-arraybuffer.mts @@ -0,0 +1,8 @@ +export function sharedArrayBufferNotSupported(): never { + throw new Error('SharedArrayBuffer is not supported'); +} + +/** https://tc39.es/ecma262/#sec-isgrowablesharedarraybuffer */ +export function IsGrowableSharedArrayBuffer(_object: unknown): boolean { + return false; +} diff --git a/src/abstract-ops/spec-types.mts b/src/abstract-ops/spec-types.mts new file mode 100644 index 0000000..42941d0 --- /dev/null +++ b/src/abstract-ops/spec-types.mts @@ -0,0 +1,220 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BigIntValue, + DataBlock, + Descriptor, + NumberValue, + ObjectValue, + UndefinedValue, + Value, + BooleanValue, +} from '../value.mts'; +import { NormalCompletion, Q, X } from '../completion.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { + Assert, + CreateDataProperty, + Get, + HasProperty, + IsCallable, + OrdinaryObjectCreate, + ToBoolean, + type FunctionObject, +} from './all.mts'; +import { isNonNegativeInteger } from './data-types-and-values.mts'; + +// #𝔽 +export function F(x: number): NumberValue { + Assert(typeof x === 'number'); + return Value(x); +} + +// #ℤ +export function Z(x: bigint): BigIntValue { + Assert(typeof x === 'bigint'); + return Value(x); +} + +// #ℝ +export function R(x: NumberValue): number; +export function R(x: BigIntValue): bigint; +export function R(x: BigIntValue | NumberValue): bigint | number; +export function R(x: unknown) { + if (x instanceof BigIntValue) { + return x.bigintValue(); // eslint-disable-line @engine262/mathematical-value + } + Assert(x instanceof NumberValue); + return x.numberValue(); // eslint-disable-line @engine262/mathematical-value +} + +// 6.2.5.1 IsAccessorDescriptor +export function IsAccessorDescriptor(Desc: Descriptor): Desc is Descriptor & { Get: Value; Set: Value } { + if (Desc.Get === undefined && Desc.Set === undefined) { + return false; + } + + return true; +} + +// 6.2.5.2 IsDataDescriptor +export function IsDataDescriptor(Desc: Descriptor): Desc is Descriptor & { Value: Value; Writable: BooleanValue } { + if (Desc.Value === undefined && Desc.Writable === undefined) { + return false; + } + + return true; +} + +// 6.2.5.3 IsGenericDescriptor +export function IsGenericDescriptor(Desc: Descriptor) { + if (!IsAccessorDescriptor(Desc) && !IsDataDescriptor(Desc)) { + return true; + } + + return false; +} + +/** https://tc39.es/ecma262/#sec-frompropertydescriptor */ +export function FromPropertyDescriptor(Desc: Descriptor | UndefinedValue) { + if (Desc instanceof UndefinedValue) { + return Value.undefined; + } + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + if (Desc.Value !== undefined) { + X(CreateDataProperty(obj, Value('value'), Desc.Value)); + } + if (Desc.Writable !== undefined) { + X(CreateDataProperty(obj, Value('writable'), Desc.Writable)); + } + if (Desc.Get !== undefined) { + X(CreateDataProperty(obj, Value('get'), Desc.Get)); + } + if (Desc.Set !== undefined) { + X(CreateDataProperty(obj, Value('set'), Desc.Set)); + } + if (Desc.Enumerable !== undefined) { + X(CreateDataProperty(obj, Value('enumerable'), Desc.Enumerable)); + } + if (Desc.Configurable !== undefined) { + X(CreateDataProperty(obj, Value('configurable'), Desc.Configurable)); + } + // Assert: All of the above CreateDataProperty operations return true. + return obj; +} + +/** https://tc39.es/ecma262/#sec-topropertydescriptor */ +export function* ToPropertyDescriptor(Obj: Value): PlainEvaluator { + if (!(Obj instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', Obj); + } + + let desc = Descriptor({}); + const hasEnumerable = Q(yield* HasProperty(Obj, Value('enumerable'))); + if (hasEnumerable === Value.true) { + const enumerable = ToBoolean(Q(yield* Get(Obj, Value('enumerable')))); + desc = Descriptor({ ...desc, Enumerable: enumerable }); + } + const hasConfigurable = Q(yield* HasProperty(Obj, Value('configurable'))); + if (hasConfigurable === Value.true) { + const conf = ToBoolean(Q(yield* Get(Obj, Value('configurable')))); + desc = Descriptor({ ...desc, Configurable: conf }); + } + const hasValue = Q(yield* HasProperty(Obj, Value('value'))); + if (hasValue === Value.true) { + const value = Q(yield* Get(Obj, Value('value'))); + desc = Descriptor({ ...desc, Value: value }); + } + const hasWritable = Q(yield* HasProperty(Obj, Value('writable'))); + if (hasWritable === Value.true) { + const writable = ToBoolean(Q(yield* Get(Obj, Value('writable')))); + desc = Descriptor({ ...desc, Writable: writable }); + } + const hasGet = Q(yield* HasProperty(Obj, Value('get'))); + if (hasGet === Value.true) { + const getter = Q(yield* Get(Obj, Value('get'))); + if (!IsCallable(getter) && !(getter instanceof UndefinedValue)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', getter); + } + desc = Descriptor({ ...desc, Get: getter as FunctionObject }); + } + const hasSet = Q(yield* HasProperty(Obj, Value('set'))); + if (hasSet === Value.true) { + const setter = Q(yield* Get(Obj, Value('set'))); + if (!IsCallable(setter) && !(setter instanceof UndefinedValue)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', setter); + } + desc = Descriptor({ ...desc, Set: setter as FunctionObject }); + } + if (desc.Get !== undefined || desc.Set !== undefined) { + if (desc.Value !== undefined || desc.Writable !== undefined) { + return surroundingAgent.Throw('TypeError', 'InvalidPropertyDescriptor'); + } + } + return desc; +} + +/** https://tc39.es/ecma262/#sec-completepropertydescriptor */ +export function CompletePropertyDescriptor(Desc: Descriptor) { + Assert(Desc instanceof Descriptor); + const like = Descriptor({ + Value: Value.undefined, + Writable: Value.false, + Get: Value.undefined, + Set: Value.undefined, + Enumerable: Value.false, + Configurable: Value.false, + }); + if (IsGenericDescriptor(Desc) || IsDataDescriptor(Desc)) { + if (Desc.Value === undefined) { + Desc = Descriptor({ ...Desc, Value: like.Value }); + } + if (Desc.Writable === undefined) { + Desc = Descriptor({ ...Desc, Writable: like.Writable }); + } + } else { + if (Desc.Get === undefined) { + Desc = Descriptor({ ...Desc, Get: like.Get }); + } + if (Desc.Set === undefined) { + Desc = Descriptor({ ...Desc, Set: like.Set }); + } + } + if (Desc.Enumerable === undefined) { + Desc = Descriptor({ ...Desc, Enumerable: like.Enumerable }); + } + if (Desc.Configurable === undefined) { + Desc = Descriptor({ ...Desc, Configurable: like.Configurable }); + } + return Desc; +} + +/** https://tc39.es/ecma262/#sec-createbytedatablock */ +export function CreateByteDataBlock(size: number) { + Assert(isNonNegativeInteger(size)); + let db; + try { + db = new DataBlock(size); + } catch (err) { + return surroundingAgent.Throw('RangeError', 'CannotAllocateDataBlock'); + } + return db; +} + +/** https://tc39.es/ecma262/#sec-copydatablockbytes */ +export function CopyDataBlockBytes(toBlock: DataBlock, toIndex: number, fromBlock: DataBlock, fromIndex: number, count: number) { + Assert(fromBlock !== toBlock); + 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) { + toBlock[toIndex] = fromBlock[fromIndex]; + toIndex += 1; + fromIndex += 1; + count -= 1; + } + return NormalCompletion(undefined); +} diff --git a/src/abstract-ops/string-objects.mts b/src/abstract-ops/string-objects.mts new file mode 100644 index 0000000..6e50592 --- /dev/null +++ b/src/abstract-ops/string-objects.mts @@ -0,0 +1,156 @@ +import { + Descriptor, + ObjectValue, + SymbolValue, + JSStringValue, + UndefinedValue, + Value, + type PropertyKeyValue, + type ObjectInternalMethods, +} from '../value.mts'; +import { X } from '../completion.mts'; +import type { StringObject } from '../intrinsics/String.mts'; +import type { Mutable } from '../helpers.mts'; +import { + Assert, + CanonicalNumericIndexString, + DefinePropertyOrThrow, + IsIntegralNumber, + IsPropertyKey, + MakeBasicObject, + OrdinaryGetOwnProperty, + OrdinaryDefineOwnProperty, + IsCompatiblePropertyDescriptor, + ToIntegerOrInfinity, + ToString, + isArrayIndex, + F, R, +} from './all.mts'; + +const InternalMethods = { + * GetOwnProperty(P) { + const S = this; + Assert(IsPropertyKey(P)); + const desc = OrdinaryGetOwnProperty(S, P); + if (!(desc instanceof UndefinedValue)) { + return desc; + } + return X(StringGetOwnProperty(S, P)); + }, + * DefineOwnProperty(P, Desc) { + const S = this; + Assert(IsPropertyKey(P)); + const stringDesc = X(StringGetOwnProperty(S, P)); + if (!(stringDesc instanceof UndefinedValue)) { + const extensible = S.Extensible; + return X(IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc)); + } + return X(OrdinaryDefineOwnProperty(S, P, Desc)); + }, + * OwnPropertyKeys() { + const O = this; + const keys = []; + const str = O.StringData; + Assert(str instanceof JSStringValue); + const len = str.stringValue().length; + + // 5. For each non-negative 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(F(i)))); + } + + // For each own property key P of O such that P is an array index and + // ToIntegerOrInfinity(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(ToIntegerOrInfinity(P)) >= 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 (P instanceof JSStringValue && 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 (P instanceof SymbolValue) { + keys.push(P); + } + } + + return keys; + }, +} satisfies Partial>; + +/** https://tc39.es/ecma262/#sec-stringcreate */ +export function StringCreate(value: JSStringValue, prototype: ObjectValue) { + // 1. Assert: Type(value) is String. + Assert(value instanceof JSStringValue); + // 2. Let S be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[StringData]] »). + const S = X(MakeBasicObject(['Prototype', 'Extensible', 'StringData'])) as Mutable; + // 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 = InternalMethods.GetOwnProperty; + // 6. Set S.[[DefineOwnProperty]] as specified in 9.4.3.2. + S.DefineOwnProperty = InternalMethods.DefineOwnProperty; + // 7. Set S.[[OwnPropertyKeys]] as specified in 9.4.3.3. + S.OwnPropertyKeys = InternalMethods.OwnPropertyKeys; + // 8. Let length be the number of code unit elements in value. + const length = value.stringValue().length; + // 9. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: length, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(S, Value('length'), Descriptor({ + Value: F(length), + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 10. Return S. + return S; +} + +/** https://tc39.es/ecma262/#sec-stringgetownproperty */ +export function StringGetOwnProperty(S: ObjectValue, P: PropertyKeyValue) { + Assert(S instanceof ObjectValue && 'StringData' in S); + Assert(IsPropertyKey(P)); + if (!(P instanceof JSStringValue)) { + return Value.undefined; + } + const index = X(CanonicalNumericIndexString(P)); + if (index instanceof UndefinedValue) { + return Value.undefined; + } + if (IsIntegralNumber(index) === Value.false) { + return Value.undefined; + } + if (Object.is(R(index), -0)) { + return Value.undefined; + } + const str = S.StringData; + Assert(str instanceof JSStringValue); + const len = str.stringValue().length; + if (R(index) < 0 || len <= R(index)) { + return Value.undefined; + } + const resultStr = str.stringValue()[R(index)]; + return Descriptor({ + Value: Value(resultStr), + Writable: Value.false, + Enumerable: Value.true, + Configurable: Value.false, + }); +} diff --git a/src/abstract-ops/symbol-objects.mts b/src/abstract-ops/symbol-objects.mts new file mode 100644 index 0000000..187a68a --- /dev/null +++ b/src/abstract-ops/symbol-objects.mts @@ -0,0 +1,30 @@ +import { GlobalSymbolRegistry } from '../intrinsics/Symbol.mjs'; +import { + UndefinedValue, SymbolValue, Value, JSStringValue, +} from '../value.mts'; +import { Assert, SameValue } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-symboldescriptivestring */ +export function SymbolDescriptiveString(sym: SymbolValue) { + Assert(sym instanceof SymbolValue); + let desc = sym.Description; + if (desc instanceof UndefinedValue) { + desc = Value(''); + } + return Value(`Symbol(${desc.stringValue()})`); +} + +/** https://tc39.es/ecma262/#sec-keyforsymbol */ +export function KeyForSymbol(sym: SymbolValue): JSStringValue | UndefinedValue { + // 1. 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; + } + } + + // 2. Assert: The GlobalSymbolRegistry List does not currently contain an entry for sym. + // 3. Return undefined. + return Value.undefined; +} diff --git a/src/abstract-ops/temporal/addition.mts b/src/abstract-ops/temporal/addition.mts new file mode 100644 index 0000000..bbde2ee --- /dev/null +++ b/src/abstract-ops/temporal/addition.mts @@ -0,0 +1,274 @@ +// Addition/Edition to the main spec. +// Code here should move elsewhere after Temporal is merged. + +import type { ISODateTimeRecord } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { ParseTimeZoneIdentifier } from '../../parser/TemporalParser.mts'; +import { HourFromTime, MinFromTime, SecFromTime } from '../date-objects.mts'; +import { R as MathematicalValue } from '../spec-types.mjs'; +import { __ts_cast__ } from '../../helpers.mts'; +import { FormatTimeString, ToIntegerWithTruncation } from './temporal.mts'; +import { FormatOffsetTimeZoneIdentifier, type TimeZoneIdentifierRecord } from './time-zone.mts'; +import { mark_TimeZoneAwareNotImplemented, temporal_todo } from './not-implemented.mts'; +import { + Assert, + Get, + JSStringValue, + MakeDate, + MakeDay, + MakeTime, + ObjectValue, OrdinaryObjectCreate, Q, R, Throw, TimeValueToISODateTimeRecord, ToBoolean, ToNumber, ToString, UndefinedValue, Value, X, type PlainEvaluator, type PropertyKeyValue, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-year-week-record-specification-type */ +export interface YearWeekRecord { + readonly Week: number | undefined; + readonly Year: number | undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-tointegerifintegral */ +export function* ToIntegerIfIntegral(argument: Value): PlainEvaluator { + const number = Q(yield* ToNumber(argument)); + if (!Number.isInteger(MathematicalValue(number))) { + return Throw.RangeError('$1 is not an integral number', argument); + } + return R(number); +} + +/** https://tc39.es/proposal-temporal/#sec-getoptionsobject */ +export function GetOptionsObject(options: Value) { + if (options instanceof UndefinedValue) { + return OrdinaryObjectCreate(Value.null); + } + if (options instanceof ObjectValue) { + return options; + } + return Throw.TypeError('$1 is not an object', options); +} + +/** https://tc39.es/proposal-temporal/#sec-getoption */ +export function GetOption(options: ObjectValue, property: PropertyKeyValue | string, type: 'string', values: T | undefined, defaultValue: '~required~' | D): PlainEvaluator; +export function GetOption(options: ObjectValue, property: PropertyKeyValue | string, type: 'boolean', values: undefined, defaultValue: '~required~' | D): PlainEvaluator; +export function* GetOption(options: ObjectValue, property: PropertyKeyValue | string, type: 'boolean' | 'string', values: readonly string[] | undefined, defaultValue: '~required~' | string | boolean | undefined): PlainEvaluator { + if (typeof property === 'string') { + property = Value(property); + } + let value = Q(yield* Get(options, property)); + if (value === Value.undefined) { + if (defaultValue === '~required~') { + let propertyNameToString: string; + if (typeof property === 'string') { + propertyNameToString = property; + } else if (property instanceof JSStringValue) { + propertyNameToString = property.stringValue(); + } else if (property.Description instanceof JSStringValue) { + propertyNameToString = `Symbol(${property.Description.stringValue()})`; + } else { + propertyNameToString = 'Symbol'; + } + return Throw.RangeError('"$1" is required on object $2', propertyNameToString, options); + } + return defaultValue!; + } + if (type === 'boolean') { + value = Q(ToBoolean(value)); + } else { + Assert(type === 'string'); + value = Q(yield* ToString(value)); + } + if (values !== undefined) { + const str = (value as JSStringValue).stringValue(); + if (!values.includes(str)) { + return Throw.RangeError('"$1" on object $2 is not valid ($3)', property, options, str); + } + } + return value instanceof JSStringValue ? value.stringValue() : value.booleanValue(); +} + +/** https://tc39.es/proposal-temporal/#sec-getroundingmodeoption */ +export function* GetRoundingModeOption( + options: ObjectValue, + fallback: RoundingMode, +): PlainEvaluator { + const allowedStrings = ['ceil', 'floor', 'expand', 'trunc', 'halfCeil', 'halfFloor', 'halfExpand', 'halfTrunc', 'halfEven'] as const; + const stringFallback = ({ + [RoundingMode.Ceil]: 'ceil', + [RoundingMode.Floor]: 'floor', + [RoundingMode.Expand]: 'expand', + [RoundingMode.Trunc]: 'trunc', + [RoundingMode.HalfCeil]: 'halfCeil', + [RoundingMode.HalfFloor]: 'halfFloor', + [RoundingMode.HalfExpand]: 'halfExpand', + [RoundingMode.HalfTrunc]: 'halfTrunc', + [RoundingMode.HalfEven]: 'halfEven', + } as const)[fallback]; + const stringValue = Q(yield* GetOption(options, Value('roundingMode'), 'string', allowedStrings, stringFallback)); + return { + ceil: RoundingMode.Ceil, + floor: RoundingMode.Floor, + expand: RoundingMode.Expand, + trunc: RoundingMode.Trunc, + halfCeil: RoundingMode.HalfCeil, + halfFloor: RoundingMode.HalfFloor, + halfExpand: RoundingMode.HalfExpand, + halfTrunc: RoundingMode.HalfTrunc, + halfEven: RoundingMode.HalfEven, + }[stringValue]; +} + +/** https://tc39.es/proposal-temporal/#table-temporal-rounding-modes */ +export enum RoundingMode { + Ceil, + Floor, + Expand, + Trunc, + HalfCeil, + HalfFloor, + HalfExpand, + HalfTrunc, + HalfEven +} +/** https://tc39.es/proposal-temporal/#table-unsigned-rounding-modes */ +export enum UnsignedRoundingMode { + Infinity, Zero, HalfInfinity, HalfZero, HalfEven +} +/** https://tc39.es/proposal-temporal/#sec-getroundingincrementoption */ +export function* GetRoundingIncrementOption( + options: ObjectValue, +): PlainEvaluator { + const value = Q(yield* Get(options, Value('roundingIncrement'))); + if (value === Value.undefined) { + return 1; + } + const integerIncrement = Q(yield* ToIntegerWithTruncation(value)); + if (integerIncrement < 1 || integerIncrement > 10 ** 9) { + return Throw.RangeError('"roundingIncrement" ($1) is out of range', integerIncrement); + } + return integerIncrement; +} + +/** https://tc39.es/proposal-temporal/#sec-getutcepochnanoseconds */ +export function GetUTCEpochNanoseconds( + isoDateTime: ISODateTimeRecord, +): bigint { + const date = MakeDay(Value(isoDateTime.ISODate.Year), Value(isoDateTime.ISODate.Month - 1), Value(isoDateTime.ISODate.Day)); + const time = MakeTime(Value(isoDateTime.Time.Hour), Value(isoDateTime.Time.Minute), Value(isoDateTime.Time.Second), Value(isoDateTime.Time.Millisecond)); + const ms = R(MakeDate(date, time)); + Assert(Math.floor(ms) === ms); + return BigInt(ms) * BigInt(10e6) + BigInt(isoDateTime.Time.Microsecond) * BigInt(10e3) + BigInt(isoDateTime.Time.Nanosecond); +} + +/** https://tc39.es/proposal-temporal/#sec-time-zone-identifiers */ +export type TimeZoneIdentifier = string & { readonly TimeZoneIdentifier: never; }; + +/** https://tc39.es/proposal-temporal/#sec-getnamedtimezoneepochnanoseconds */ +export function GetNamedTimeZoneEpochNanoseconds( + timeZoneIdentifier: TimeZoneIdentifier, + isoDateTime: ISODateTimeRecord, +): bigint[] { + mark_TimeZoneAwareNotImplemented(); + Assert(timeZoneIdentifier === 'UTC'); + const epochNanoseconds = GetUTCEpochNanoseconds(isoDateTime); + return [epochNanoseconds]; +} + +/** https://tc39.es/ecma262/#sec-getnamedtimezoneoffsetnanoseconds */ +export function GetNamedTimeZoneOffsetNanoseconds(timeZoneIdentifier: string, _epochNanoseconds: bigint) { + mark_TimeZoneAwareNotImplemented(); + Assert(timeZoneIdentifier === 'UTC'); + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-systemtimezoneidentifier */ +export function SystemTimeZoneIdentifier(): TimeZoneIdentifier { + mark_TimeZoneAwareNotImplemented(); + // 1. If the implementation only supports the UTC time zone, return "UTC". + return 'UTC' as TimeZoneIdentifier; + // 2. Let systemTimeZoneString be the String representing the host environment's current time zone as a time zone identifier in normalized format, either a primary time zone identifier or an offset time zone identifier. + // 3. Return systemTimeZoneString. +} + +/** https://tc39.es/proposal-temporal/#sec-localtime */ +export function LocalTime_TemporalEdited(t: number): number { + const systemTimeZoneIdentifier = SystemTimeZoneIdentifier(); + const parseResult = X(ParseTimeZoneIdentifier(systemTimeZoneIdentifier)); + let offsetNs: number; + if (parseResult.OffsetMinutes !== undefined) { + offsetNs = parseResult.OffsetMinutes * (60 * 1e9); + } else { + offsetNs = GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, BigInt(t * 1e6)); + } + const offsetMs = Math.trunc(offsetNs / 1e6); + return t + offsetMs; +} + +/** https://tc39.es/proposal-temporal/#sec-utc-t */ +export function UTC_TemporalEdited(t: number): number { + if (!Number.isFinite(t)) { + return NaN; + } + const systemTimeZoneIdentifier = SystemTimeZoneIdentifier(); + const parseResult = X(ParseTimeZoneIdentifier(systemTimeZoneIdentifier)); + let offsetNs: number; + if (parseResult.OffsetMinutes !== undefined) { + offsetNs = parseResult.OffsetMinutes * (60 * 1e9); + } else { + const isoDateTime = TimeValueToISODateTimeRecord(t); + const possibleInstants = GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, isoDateTime); + let disambiguatedInstant: bigint; + if (possibleInstants.length > 0) { + disambiguatedInstant = possibleInstants[0]; + } else { + // TODO(temporal): review + // ii. Let possibleInstantsBefore be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(tBefore)), ℝ(MonthFromTime(tBefore)) + 1, ℝ(DateFromTime(tBefore)), ℝ(HourFromTime(tBefore)), ℝ(MinFromTime(tBefore)), ℝ(SecFromTime(tBefore)), ℝ(msFromTime(tBefore)), 0, 0TimeValueToISODateTimeRecord(tBefore)), where tBefore is the largest integral Number < t for which possibleInstantsBefore is not empty (i.e., tBefore represents the last local time before the transition). + let tBefore = Math.floor(t) - 1; + let possibleInstantsBefore: bigint[] = []; + while (possibleInstantsBefore.length === 0) { + possibleInstantsBefore = GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, TimeValueToISODateTimeRecord(tBefore)); + tBefore -= 1; + } + // iii. Let disambiguatedInstant be the last element of possibleInstantsBefore. + disambiguatedInstant = possibleInstantsBefore[possibleInstantsBefore.length - 1]; + } + offsetNs = GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, disambiguatedInstant); + } + const offsetMs = Math.trunc(offsetNs / 1e6); + return t - offsetMs; +} + +/** https://tc39.es/proposal-temporal/#sec-timestring */ +export function TimeString(tv: number): string { + const timeString = FormatTimeString(R(HourFromTime(Value(tv))), R(MinFromTime(Value(tv))), R(SecFromTime(Value(tv))), 0, 0); + return `${timeString} GMT`; +} + +/** https://tc39.es/proposal-temporal/#sec-timezoneestring */ +export function TimeZoneString_TemporalEdited(tv: number): string { + const systemTimeZoneIdentifier = SystemTimeZoneIdentifier(); + let offsetMinutes = X(ParseTimeZoneIdentifier(systemTimeZoneIdentifier)).OffsetMinutes; + if (offsetMinutes === undefined) { + const offsetNs = GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, BigInt(tv * 1e6)); + offsetMinutes = Math.trunc(offsetNs / (60 * 1e9)); + } + const offsetString = FormatOffsetTimeZoneIdentifier(offsetMinutes, 'unseparated'); + const tzName = ''; + return offsetString + tzName; +} + +/** https://tc39.es/proposal-temporal/#sec-isoffsettimezoneidentifier */ +export function IsOffsetTimeZoneIdentifier(_offsetString: string): boolean { + temporal_todo(); +} + +/** https://tc39.es/ecma262/#sec-tozeropaddeddecimalstring */ +export function ToZeroPaddedDecimalString(n: number, minLength: number) { + return n.toString().padStart(minLength, '0'); +} + +/** https://tc39.es/ecma262/#sec-availablenamedtimezoneidentifiers */ +export function AvailableNamedTimeZoneIdentifiers(): TimeZoneIdentifierRecord[] { + mark_TimeZoneAwareNotImplemented(); + return [{ + Identifier: 'UTC' as TimeZoneIdentifier, + PrimaryIdentifier: 'UTC' as TimeZoneIdentifier, + }]; +} diff --git a/src/abstract-ops/temporal/all.mts b/src/abstract-ops/temporal/all.mts new file mode 100644 index 0000000..265b1c6 --- /dev/null +++ b/src/abstract-ops/temporal/all.mts @@ -0,0 +1,12 @@ +export * from './calendar.mts'; +export * from './duration.mts'; +export * from './instant.mts'; +export * from './now.mts'; +export * from './plain-date-time.mts'; +export * from './plain-date.mts'; +export * from './plain-month-day.mts'; +export * from './plain-time.mts'; +export * from './plain-year-month.mts'; +export * from './temporal.mts'; +export * from './time-zone.mts'; +export * from './zoned-datetime.mts'; diff --git a/src/abstract-ops/temporal/calendar.mts b/src/abstract-ops/temporal/calendar.mts new file mode 100644 index 0000000..3ff7337 --- /dev/null +++ b/src/abstract-ops/temporal/calendar.mts @@ -0,0 +1,724 @@ +import { CanonicalizeUValue } from '../../ecma402/not-implemented.mts'; +import { __ts_cast__, isArray, type Mutable } from '../../helpers.mts'; +import { ParseMonthCode, ParseTemporalCalendarString } from '../../parser/TemporalParser.mts'; +import { isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { isTemporalPlainMonthDayObject } from '../../intrinsics/Temporal/PlainMonthDay.mts'; +import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { isTemporalPlainDateObject, type ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts'; +import { isTemporalPlainYearMonthObject } from '../../intrinsics/Temporal/PlainYearMonth.mts'; +import { ToZeroPaddedDecimalString } from './addition.mts'; +import type { YearWeekRecord } from './addition.mts'; +import { + EpochDaysToEpochMs, + EpochTimeForYear, + EpochTimeToDayInYear, + EpochTimeToWeekDay, + ISODateToEpochDays, + MathematicalDaysInYear, + MathematicalInLeapYear, + TemporalUnit, + ToIntegerWithTruncation, ToOffsetString, ToPositiveIntegerWithTruncation, type DateUnit, +} from './temporal.mts'; +import { ToTemporalTimeZoneIdentifier } from './time-zone.mts'; +import { mark_OtherCalendarNotImplemented, unreachable_OtherCalendarNotImplemented } from './not-implemented.mts'; +import { + AddDaysToISODate, + Assert, + BalanceISOYearMonth, + CompareISODate, + CreateDateDurationRecord, + CreateISODateRecord, + F, + Get, + ISODateSurpasses, + ISODateWithinLimits, + JSStringValue, + NumberValue, + ObjectValue, + Q, + R, + RegulateISODate, + Throw, + ToString, + Value, + X, + ZeroDateDuration, + type DateDurationRecord, + type PlainCompletion, type PlainEvaluator, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-calendar-types */ +export type CalendarType = 'iso8601'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar */ +export function CanonicalizeCalendar(id: string): PlainCompletion { + const calendars = AvailableCalendars(); + if (!calendars.includes(id.toLowerCase() as CalendarType)) { + return Throw.RangeError('$1 is not a supported calendar', id); + } + return CanonicalizeUValue('ca', id) as CalendarType; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-availablecalendars */ +export function AvailableCalendars(): CalendarType[] { + mark_OtherCalendarNotImplemented(); + return ['iso8601']; +} + +export type MonthCode = string & { __brand: 'MonthCode' }; + +/** https://tc39.es/proposal-temporal/#sec-temporal-createmonthcode */ +export function CreateMonthCode(monthNumber: number, isLeapMonth: boolean): MonthCode { + if (!isLeapMonth) Assert(monthNumber > 0); + const numberPart = ToZeroPaddedDecimalString(monthNumber, 2); + if (isLeapMonth) { + return `M${numberPart}L` as MonthCode; + } + return `M${numberPart}` as MonthCode; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendar-date-records */ +export interface CalendarDateRecord { + readonly Era: string | undefined; + readonly EraYear: number | undefined; + readonly Year: number; + readonly Month: number; + readonly MonthCode: string; + readonly Day: number; + readonly DayOfWeek: number; + readonly DayOfYear: number; + readonly WeekOfYear: YearWeekRecord; + readonly DaysInWeek: number; + readonly DaysInMonth: number; + readonly DaysInYear: number; + readonly MonthsInYear: number; + readonly InLeapYear: boolean; +} + +/** https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields */ +export interface CalendarFieldsRecord { + readonly Era: string | undefined; + readonly EraYear: number | undefined; + Year: number | undefined; + Month: number | undefined; + MonthCode: string | undefined; + Day: number | undefined; + Hour: number | undefined; + Minute: number | undefined; + Second: number | undefined; + Millisecond: number | undefined; + Microsecond: number | undefined; + Nanosecond: number | undefined; + OffsetString: string | undefined; + readonly TimeZone: string | undefined; +} + +export enum Table19_Conversion { + ToString = 'to-string', + ToIntegerWithTruncation = 'to-integer-with-truncation', + ToPositiveIntegerWithTruncation = 'to-positive-integer-with-truncation', + ToTemporalTimeZoneIdentifier = 'to-temporal-time-zone-identifier', + ToMonthCode = 'to-month-code', + ToOffsetString = 'to-offset-string', +} + +export type CalendarFieldsRecordEnumerationKey = 'era' | 'era-year' | 'year' | 'month' | 'month-code' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' | 'offset' | 'time-zone'; + +export const Table19_CalendarFieldsRecordFields = [ + /* eslint-disable object-curly-newline */ + { FieldName: 'Era', DefaultValue: undefined, PropertyKey: 'era', EnumerationKey: 'era', Conversion: Table19_Conversion.ToString }, + { FieldName: 'EraYear', DefaultValue: undefined, PropertyKey: 'eraYear', EnumerationKey: 'era-year', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Year', DefaultValue: undefined, PropertyKey: 'year', EnumerationKey: 'year', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Month', DefaultValue: undefined, PropertyKey: 'month', EnumerationKey: 'month', Conversion: Table19_Conversion.ToPositiveIntegerWithTruncation }, + { FieldName: 'MonthCode', DefaultValue: undefined, PropertyKey: 'monthCode', EnumerationKey: 'month-code', Conversion: Table19_Conversion.ToMonthCode }, + { FieldName: 'Day', DefaultValue: undefined, PropertyKey: 'day', EnumerationKey: 'day', Conversion: Table19_Conversion.ToPositiveIntegerWithTruncation }, + { FieldName: 'Hour', DefaultValue: 0, PropertyKey: 'hour', EnumerationKey: 'hour', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Minute', DefaultValue: 0, PropertyKey: 'minute', EnumerationKey: 'minute', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Second', DefaultValue: 0, PropertyKey: 'second', EnumerationKey: 'second', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Millisecond', DefaultValue: 0, PropertyKey: 'millisecond', EnumerationKey: 'millisecond', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Microsecond', DefaultValue: 0, PropertyKey: 'microsecond', EnumerationKey: 'microsecond', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'Nanosecond', DefaultValue: 0, PropertyKey: 'nanosecond', EnumerationKey: 'nanosecond', Conversion: Table19_Conversion.ToIntegerWithTruncation }, + { FieldName: 'OffsetString', DefaultValue: undefined, PropertyKey: 'offsetString', EnumerationKey: 'offset', Conversion: Table19_Conversion.ToOffsetString }, + { FieldName: 'TimeZone', DefaultValue: undefined, PropertyKey: 'timeZone', EnumerationKey: 'time-zone', Conversion: Table19_Conversion.ToTemporalTimeZoneIdentifier }, + /* eslint-enable object-curly-newline */ +] as const satisfies { + FieldName: keyof CalendarFieldsRecord; + DefaultValue: string | number | undefined; + PropertyKey: string; + EnumerationKey: CalendarFieldsRecordEnumerationKey; + Conversion: Table19_Conversion; +}[]; + +/** https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields */ +export function* PrepareCalendarFields( + calendar: CalendarType, + fields: ObjectValue, + calendarFieldNames: readonly CalendarFieldsRecordEnumerationKey[], + nonCalendarFieldNames: readonly CalendarFieldsRecordEnumerationKey[], + requiredFieldNames: 'partial' | readonly CalendarFieldsRecordEnumerationKey[], +): PlainEvaluator { + // Assert: If requiredFieldNames is a List, requiredFieldNames contains zero or one of each of the elements of calendarFieldNames and nonCalendarFieldNames. + if (isArray(requiredFieldNames)) { + Assert(calendarFieldNames.every((name) => requiredFieldNames.filter((requiredName) => name === requiredName).length <= 1)); + Assert(nonCalendarFieldNames.every((name) => requiredFieldNames.filter((requiredName) => name === requiredName).length <= 1)); + } + let fieldNames: CalendarFieldsRecordEnumerationKey[] = [...calendarFieldNames, ...nonCalendarFieldNames]; + const extraFieldNames = CalendarExtraFields(calendar, calendarFieldNames); + fieldNames = [...fieldNames, ...extraFieldNames]; + // Assert: fieldNames contains no duplicate elements. + Assert(fieldNames.length === new Set(fieldNames).size); + const result: Mutable = { + Era: undefined, + EraYear: undefined, + Year: undefined, + Month: undefined, + MonthCode: undefined, + Day: undefined, + Hour: undefined, + Minute: undefined, + Second: undefined, + Millisecond: undefined, + Microsecond: undefined, + Nanosecond: undefined, + OffsetString: undefined, + TimeZone: undefined, + }; + let any = false; + + // Let sortedPropertyNames be a List whose elements are the values in the Property Key column of Table 19 corresponding to the elements of fieldNames, sorted according to lexicographic code unit order. + const sortedPropertyNames = [...Table19_CalendarFieldsRecordFields].sort((a, b) => (a.PropertyKey < b.PropertyKey ? -1 : 1)); + + for (const { + FieldName, PropertyKey, Conversion, DefaultValue, EnumerationKey, + } of sortedPropertyNames) { + __ts_cast__(FieldName); + // Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property. + const key = EnumerationKey; + let value = Q(yield* Get(fields, Value(PropertyKey))); + + if (value !== Value.undefined) { + any = true; + + if (Conversion === Table19_Conversion.ToIntegerWithTruncation) { + value = F(Q(yield* ToIntegerWithTruncation(value))); + } else if (Conversion === Table19_Conversion.ToPositiveIntegerWithTruncation) { + value = F(Q(yield* ToPositiveIntegerWithTruncation(value))); + } else if (Conversion === Table19_Conversion.ToString) { + value = Q(yield* ToString(value)); + } else if (Conversion === Table19_Conversion.ToTemporalTimeZoneIdentifier) { + value = Value(Q(ToTemporalTimeZoneIdentifier(value))); + } else if (Conversion === Table19_Conversion.ToMonthCode) { + const parsed = Q(yield* ParseMonthCode(value)); + value = Value(CreateMonthCode(parsed.MonthNumber, parsed.IsLeapMonth)); + } else { + Assert(Conversion === Table19_Conversion.ToOffsetString); + value = Value(Q(yield* ToOffsetString(value))); + } + + let assignValue; + if (value instanceof NumberValue) { + assignValue = R(value); + } else if (value instanceof JSStringValue) { + assignValue = value.stringValue(); + } + if (assignValue === undefined) { + throw new Error('invalid type'); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + result[FieldName] = assignValue as any; + } else if (isArray(requiredFieldNames)) { + if (requiredFieldNames.includes(key)) { + return Throw.TypeError('$1 is a required on object $2', key, fields); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + result[FieldName] = DefaultValue as any; + } + } + + if (requiredFieldNames === 'partial' && !any) { + return Throw.TypeError('$1 is not a TemporalTimeLike object', fields); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent */ +export function CalendarFieldKeysPresent(fields: CalendarFieldsRecord): CalendarFieldsRecordEnumerationKey[] { + const list: CalendarFieldsRecordEnumerationKey[] = []; + for (const { FieldName, EnumerationKey } of Table19_CalendarFieldsRecordFields) { + const value = fields[FieldName]; + const enumerationKey = EnumerationKey; + if (value !== undefined) { + list.push(enumerationKey); + } + } + return list; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields */ +export function CalendarMergeFields(calendar: CalendarType, fields: CalendarFieldsRecord, additionalFields: CalendarFieldsRecord): CalendarFieldsRecord { + const additionalKeys = CalendarFieldKeysPresent(additionalFields); + const overriddenKeys = CalendarFieldKeysToIgnore(calendar, additionalKeys); + const merged: Mutable = { + Era: undefined, + EraYear: undefined, + Year: undefined, + Month: undefined, + MonthCode: undefined, + Day: undefined, + Hour: undefined, + Minute: undefined, + Second: undefined, + Millisecond: undefined, + Microsecond: undefined, + Nanosecond: undefined, + OffsetString: undefined, + TimeZone: undefined, + }; + const fieldsKeys = CalendarFieldKeysPresent(fields); + for (const { EnumerationKey, FieldName } of Table19_CalendarFieldsRecordFields) { + const key = EnumerationKey; + if (fieldsKeys.includes(key) && !overriddenKeys.includes(key)) { + const propValue = fields[FieldName]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + merged[FieldName] = propValue as any; + } + if (additionalKeys.includes(key)) { + const propValue = additionalFields[FieldName]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + merged[FieldName] = propValue as any; + } + } + return merged; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisodateadd */ +export function NonISODateAdd( + _calendar: CalendarType, + _isoDate: ISODateRecord, + _duration: DateDurationRecord, + _overflow: 'constrain' | 'reject', +): never { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd */ +export function CalendarDateAdd( + calendar: CalendarType, + isoDate: ISODateRecord, + duration: DateDurationRecord, + overflow: 'constrain' | 'reject', +): PlainCompletion { + let result: ISODateRecord; + if (calendar === 'iso8601') { + const intermediate = Q(BalanceISOYearMonth(isoDate.Year + duration.Years, isoDate.Month + duration.Months)); + const regulated = Q(RegulateISODate(intermediate.Year, intermediate.Month, isoDate.Day, overflow)); + const days = regulated.Day + duration.Days + 7 * duration.Weeks; + result = Q(AddDaysToISODate(regulated, days)); + } else { + result = Q(NonISODateAdd(calendar, isoDate, duration, overflow)); + } + if (!ISODateWithinLimits(result)) { + return Throw.RangeError('Resulting ISODate is out of range'); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisodateuntil */ +export function NonISODateUntil( + _calendar: CalendarType, + _one: ISODateRecord, + _two: ISODateRecord, + _largestUnit: DateUnit, +): never { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil */ +export function CalendarDateUntil( + calendar: CalendarType, + one: ISODateRecord, + two: ISODateRecord, + largestUnit: DateUnit, +): DateDurationRecord { + if (calendar === 'iso8601') { + const sign = -CompareISODate(one, two) as 1 | -1 | 0; + if (sign === 0) { + return ZeroDateDuration(); + } + let years = 0; + if (largestUnit === TemporalUnit.Year) { + let candidateYears = sign; + while (!ISODateSurpasses(sign, one, two, candidateYears, 0, 0, 0)) { + years = candidateYears; + candidateYears += sign; + } + } + let months = 0; + if (largestUnit === TemporalUnit.Month) { + let candidateMonths = sign; + while (!ISODateSurpasses(sign, one, two, years, candidateMonths, 0, 0)) { + months = candidateMonths; + candidateMonths += sign; + } + } + let weeks = 0; + if (largestUnit === TemporalUnit.Week) { + let candidateWeeks = sign; + while (!ISODateSurpasses(sign, one, two, years, months, candidateWeeks, 0)) { + weeks = candidateWeeks; + candidateWeeks += sign; + } + } + let days = 0; + let candidateDays = sign; + while (!ISODateSurpasses(sign, one, two, years, months, weeks, candidateDays)) { + days = candidateDays; + candidateDays += sign; + } + return X(CreateDateDurationRecord(years, months, weeks, days)); + } + return NonISODateUntil(calendar, one, two, largestUnit); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier */ +export function ToTemporalCalendarIdentifier(temporalCalendarLike: Value): PlainCompletion { + if (temporalCalendarLike instanceof ObjectValue) { + if ( + isTemporalPlainDateObject(temporalCalendarLike) + || isTemporalPlainDateTimeObject(temporalCalendarLike) + || isTemporalPlainMonthDayObject(temporalCalendarLike) + || isTemporalPlainYearMonthObject(temporalCalendarLike) + || isTemporalZonedDateTimeObject(temporalCalendarLike)) { + return temporalCalendarLike.Calendar; + } + } + if (!(temporalCalendarLike instanceof JSStringValue)) { + return Throw.TypeError('temporalCalendarLike must be a string or a Temporal object, but got $1', temporalCalendarLike); + } + const identifier = Q(ParseTemporalCalendarString(temporalCalendarLike.stringValue())); + return Q(CanonicalizeCalendar(identifier)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendaridentifierwithisodefault */ +export function* GetTemporalCalendarIdentifierWithISODefault(item: ObjectValue): PlainEvaluator { + if (isTemporalPlainDateObject(item) + || isTemporalPlainDateTimeObject(item) + || isTemporalPlainMonthDayObject(item) + || isTemporalPlainYearMonthObject(item) + || isTemporalZonedDateTimeObject(item)) { + return item.Calendar; + } + const calendarLike = Q(yield* Get(item, Value('calendar'))); + if (calendarLike === Value.undefined) { + return 'iso8601'; + } + return Q(ToTemporalCalendarIdentifier(calendarLike)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatefromfields */ +export function* CalendarDateFromFields( + calendar: CalendarType, + fields: CalendarFieldsRecord, + overflow: 'constrain' | 'reject', +): PlainEvaluator { + Q(yield* CalendarResolveFields(calendar, fields, 'date')); + const result = Q(CalendarDateToISO(calendar, fields, overflow)); + if (!ISODateWithinLimits(result)) { + return Throw.RangeError('Resulting ISODate is out of range'); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields */ +export function* CalendarYearMonthFromFields( + calendar: CalendarType, + fields: CalendarFieldsRecord, + overflow: 'constrain' | 'reject', +): PlainEvaluator { + Q(yield* CalendarResolveFields(calendar, fields, 'year-month')); + // Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the month's first day is skipped by this calendar.) + const firstDayIndex = 1; + fields.Day = firstDayIndex; + const result = Q(CalendarDateToISO(calendar, fields, overflow)); + if (!ISODateWithinLimits(result)) { + return Throw.RangeError('Resulting ISODate is out of range'); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields */ +export function* CalendarMonthDayFromFields( + calendar: CalendarType, + fields: CalendarFieldsRecord, + overflow: 'constrain' | 'reject', +): PlainEvaluator { + Q(yield* CalendarResolveFields(calendar, fields, 'month-day')); + const result = Q(CalendarMonthDayToISOReferenceDate(calendar, fields, overflow)); + if (!ISODateWithinLimits(result)) { + return Throw.RangeError('Resulting ISODate is out of range'); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation */ +export function FormatCalendarAnnotation( + id: CalendarType, + showCalendar: 'auto' | 'always' | 'never' | 'critical', +): string { + if (showCalendar === 'never') { + return ''; + } + if (showCalendar === 'auto' && id === 'iso8601') { + return ''; + } + const flag = showCalendar === 'critical' ? '!' : ''; + return `[${flag}u-ca=${id}]`; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarequals */ +export function CalendarEquals(one: CalendarType, two: CalendarType): boolean { + if (CanonicalizeUValue('ca', one) === CanonicalizeUValue('ca', two)) { + return true; + } + return false; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth */ +export function ISODaysInMonth(year: number, month: number): number { + if (month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12) { + return 31; + } + if (month === 4 || month === 6 || month === 9 || month === 11) { + return 30; + } + Assert(month === 2); + return 28 + MathematicalInLeapYear(EpochTimeForYear(year)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isoweekofyear */ +export function ISOWeekOfYear(isoDate: ISODateRecord): YearWeekRecord { + const year = isoDate.Year; + const wednesday = 3; + const thursday = 4; + const friday = 5; + const saturday = 6; + const daysInWeek = 7; + const maxWeekNumber = 53; + const dayOfYear = ISODayOfYear(isoDate); + const dayOfWeek = ISODayOfWeek(isoDate); + const week = Math.floor((dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek); + if (week < 1) { + // NOTE: This is the last week of the previous year. + const jan1st = CreateISODateRecord(year, 1, 1); + const dayOfJan1st = ISODayOfWeek(jan1st); + if (dayOfJan1st === friday) { + return { Week: maxWeekNumber, Year: year - 1 }; + } + if (dayOfJan1st === saturday && MathematicalInLeapYear(EpochTimeForYear(year - 1)) === 1) { + return { Week: maxWeekNumber, Year: year - 1 }; + } + return { Week: maxWeekNumber - 1, Year: year - 1 }; + } + if (week === maxWeekNumber) { + const daysInYear = MathematicalDaysInYear(year); + const daysLaterInYear = daysInYear - dayOfYear; + const daysAfterThursday = thursday - dayOfWeek; + if (daysLaterInYear < daysAfterThursday) { + return { Week: 1, Year: year + 1 }; + } + } + return { Week: week, Year: year }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodayofyear */ +export function ISODayOfYear(isoDate: ISODateRecord): number { + const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day); + return EpochTimeToDayInYear(EpochDaysToEpochMs(epochDays, 0)) + 1; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodayofweek */ +export function ISODayOfWeek(isoDate: ISODateRecord): number { + const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day); + const dayOfWeek = EpochTimeToWeekDay(EpochDaysToEpochMs(epochDays, 0)); + if (dayOfWeek === 0) { + return 7; + } + return dayOfWeek; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendardatetoiso */ +export function NonISOCalendarDateToISO( + _calendar: CalendarType, + _fields: CalendarFieldsRecord, + _overflow: 'constrain' | 'reject', +): PlainCompletion { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso */ +export function CalendarDateToISO( + calendar: CalendarType, + fields: CalendarFieldsRecord, + overflow: 'constrain' | 'reject', +): PlainCompletion { + if (calendar === 'iso8601') { + Assert(fields.Year !== undefined && fields.Month !== undefined && fields.Day !== undefined); + return Q(RegulateISODate(fields.Year, fields.Month, fields.Day, overflow)); + } + return Q(NonISOCalendarDateToISO(calendar, fields, overflow)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisomonthdaytoisoreferencedate */ +export function NonISOMonthDayToISOReferenceDate( + _calendar: CalendarType, + _fields: CalendarFieldsRecord, + _overflow: 'constrain' | 'reject', +): never { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate */ +export function CalendarMonthDayToISOReferenceDate( + calendar: CalendarType, + fields: CalendarFieldsRecord, + overflow: 'constrain' | 'reject', +): PlainCompletion { + if (calendar === 'iso8601') { + Assert(fields.Month !== undefined && fields.Day !== undefined); + const referenceISOYear = 1972; + const year = fields.Year === undefined ? referenceISOYear : fields.Year; + const result = Q(RegulateISODate(year, fields.Month, fields.Day, overflow)); + return CreateISODateRecord(referenceISOYear, result.Month, result.Day); + } + return Q(NonISOMonthDayToISOReferenceDate(calendar, fields, overflow)); +} + + +// NonISOCalendarISOToDate +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendarisotodate */ +export function NonISOCalendarISOToDate( + _calendar: CalendarType, + _isoDate: ISODateRecord, +): CalendarDateRecord { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate */ +export function CalendarISOToDate( + calendar: CalendarType, + isoDate: ISODateRecord, +): CalendarDateRecord { + if (calendar === 'iso8601') { + const inLeapYear = MathematicalInLeapYear(EpochTimeForYear(isoDate.Year)) === 1; + return { + Era: undefined, + EraYear: undefined, + Year: isoDate.Year, + Month: isoDate.Month, + MonthCode: CreateMonthCode(isoDate.Month, false), + Day: isoDate.Day, + DayOfWeek: ISODayOfWeek(isoDate), + DayOfYear: ISODayOfYear(isoDate), + WeekOfYear: ISOWeekOfYear(isoDate), + DaysInWeek: 7, + DaysInMonth: ISODaysInMonth(isoDate.Year, isoDate.Month), + DaysInYear: MathematicalDaysInYear(isoDate.Year), + MonthsInYear: 12, + InLeapYear: inLeapYear, + }; + } + return NonISOCalendarISOToDate(calendar, isoDate); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarextrafields */ +export function CalendarExtraFields( + calendar: CalendarType, + _fields: readonly CalendarFieldsRecordEnumerationKey[], +): CalendarFieldsRecordEnumerationKey[] { + if (calendar === 'iso8601') { + return []; + } + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisofieldkeystoignore */ +export function NonISOFieldKeysToIgnore( + _calendar: CalendarType, + _keys: readonly CalendarFieldsRecordEnumerationKey[], +): CalendarFieldsRecordEnumerationKey[] { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore */ +export function CalendarFieldKeysToIgnore( + calendar: CalendarType, + keys: readonly CalendarFieldsRecordEnumerationKey[], +): CalendarFieldsRecordEnumerationKey[] { + if (calendar === 'iso8601') { + const ignoredKeys: CalendarFieldsRecordEnumerationKey[] = []; + for (const key of keys) { + ignoredKeys.push(key); + if (key === 'month') { + ignoredKeys.push('month-code'); + } else if (key === 'month-code') { + ignoredKeys.push('month'); + } + } + return ignoredKeys; + } + return NonISOFieldKeysToIgnore(calendar, keys); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nonisoresolvefields */ +export function NonISOResolveFields( + _calendar: CalendarType, + _fields: CalendarFieldsRecord, + _type: 'date' | 'year-month' | 'month-day', +): CalendarFieldsRecord { + mark_OtherCalendarNotImplemented(); + unreachable_OtherCalendarNotImplemented(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-calendarresolvefields */ +export function* CalendarResolveFields( + calendar: CalendarType, + fields: CalendarFieldsRecord, + type: 'date' | 'year-month' | 'month-day', +): PlainEvaluator { + if (calendar === 'iso8601') { + if ((type === 'date' || type === 'year-month') && fields.Year === undefined) { + return Throw.TypeError('"year" is required'); + } + if ((type === 'date' || type === 'month-day') && fields.Day === undefined) { + return Throw.TypeError('"day" is required'); + } + const month = fields.Month; + const monthCode = fields.MonthCode; + if (monthCode === undefined) { + if (month === undefined) { + return Throw.TypeError('"month-code" or "month" is required'); + } + } + Assert(typeof monthCode === 'string'); + const parsedMonthCode = Q(yield* ParseMonthCode(monthCode)); + if (parsedMonthCode.IsLeapMonth) { + return Throw.RangeError('Invalid leap month'); + } + if (parsedMonthCode.MonthNumber > 12) { + return Throw.RangeError('Invalid month'); + } + if (month !== undefined && month !== parsedMonthCode.MonthNumber) { + return Throw.RangeError('Invalid month'); + } + fields.Month = parsedMonthCode.MonthNumber; + } else { + Q(NonISOResolveFields(calendar, fields, type)); + } +} diff --git a/src/abstract-ops/temporal/duration.mts b/src/abstract-ops/temporal/duration.mts new file mode 100644 index 0000000..4607901 --- /dev/null +++ b/src/abstract-ops/temporal/duration.mts @@ -0,0 +1,1126 @@ +import { __ts_cast__ } from '../../helpers.mts'; +import { type TemporalDurationObject, isTemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import { type TemporalPlainDateObject } from '../../intrinsics/Temporal/PlainDate.mts'; +import { type ISODateTimeRecord } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { ParseTemporalDurationString } from '../../parser/TemporalParser.mts'; +import { abs } from '../math.mts'; +import { + type TimeZoneIdentifier, GetUTCEpochNanoseconds, RoundingMode, ToIntegerIfIntegral, +} from './addition.mts'; +import { CalendarDateAdd, type CalendarType, CalendarDateUntil } from './calendar.mts'; +import { + TemporalUnit, TemporalUnitCategory, RoundNumberToIncrement, ISODateToEpochDays, type TimeUnit, Table21_LengthInNanoSeconds, type DateUnit, GetUnsignedRoundingMode, ApplyUnsignedRoundingMode, IsCalendarUnit, __IsTimeUnit, LargerOfTwoTemporalUnits, __IsDateUnit, FormatFractionalSeconds, +} from './temporal.mts'; +import { GetEpochNanosecondsFor } from './time-zone.mts'; +import { + X, type ValueEvaluator, Assert, type PlainCompletion, surroundingAgent, Value, ObjectValue, JSStringValue, type Mutable, Q, type PlainEvaluator, Get, type FunctionObject, OrdinaryCreateFromConstructor, HoursPerDay, + nsPerDay, + AddDaysToISODate, + CombineISODateAndTimeRecord, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records */ +export interface DateDurationRecord { + readonly Years: number; + readonly Months: number; + readonly Weeks: number; + Days: number; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-partial-duration-records */ +export interface PartialDurationRecord { + readonly Years: number | undefined; + readonly Months: number | undefined; + readonly Weeks: number | undefined; + readonly Days: number | undefined; + readonly Hours: number | undefined; + readonly Minutes: number | undefined; + readonly Seconds: number | undefined; + readonly Milliseconds: number | undefined; + readonly Microseconds: number | undefined; + readonly Nanoseconds: number | undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-internal-duration-records */ +export interface InternalDurationRecord { + readonly Date: DateDurationRecord; + readonly Time: TimeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-internal-duration-records */ +export type TimeDuration = number & { readonly TimeDuration: never }; + +/** https://tc39.es/proposal-temporal/#sec-temporal-zerodateduration */ +export function ZeroDateDuration(): DateDurationRecord { + return X(CreateDateDurationRecord(0, 0, 0, 0)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecord */ +export function ToInternalDurationRecord(duration: TemporalDurationObject): InternalDurationRecord { + const dateDuration = X(CreateDateDurationRecord(duration.Years, duration.Months, duration.Weeks, duration.Days)); + const timeDuration = TimeDurationFromComponents(duration.Hours, duration.Minutes, duration.Seconds, duration.Milliseconds, duration.Microseconds, duration.Nanoseconds); + return CombineDateAndTimeDuration(dateDuration, timeDuration); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecordwith24hourdays */ +export function ToInternalDurationRecordWith24HourDays(duration: TemporalDurationObject): InternalDurationRecord { + let timeDuration = TimeDurationFromComponents(duration.Hours, duration.Minutes, duration.Seconds, duration.Milliseconds, duration.Microseconds, duration.Nanoseconds); + timeDuration = X(Add24HourDaysToTimeDuration(timeDuration, duration.Days)); + const dateDuration = X(CreateDateDurationRecord(duration.Years, duration.Months, duration.Weeks, 0)); + return CombineDateAndTimeDuration(dateDuration, timeDuration); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-todatedurationrecordwithouttime */ +export function ToDateDurationRecordWithoutTime(duration: TemporalDurationObject): DateDurationRecord { + const internalDuration = ToInternalDurationRecordWith24HourDays(duration); + const days = Math.trunc(internalDuration.Time / nsPerDay); + return X(CreateDateDurationRecord(internalDuration.Date.Years, internalDuration.Date.Months, internalDuration.Date.Weeks, days)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationfrominternal */ +export function TemporalDurationFromInternal(internalDuration: InternalDurationRecord, largestUnit: TemporalUnit): ValueEvaluator { + let days = 0n; + let hours = 0n; + let minutes = 0n; + let seconds = 0n; + let milliseconds = 0n; + let microseconds = 0n; + const sign = TimeDurationSign(internalDuration.Time); + let nanoseconds = BigInt(abs(internalDuration.Time)); + if (TemporalUnitCategory(largestUnit) === 'date') { + microseconds = nanoseconds / 1000n; + nanoseconds %= 1000n; + milliseconds = microseconds / 1000n; + microseconds %= 1000n; + seconds = milliseconds / 1000n; + milliseconds %= 1000n; + minutes = seconds / 60n; + seconds %= 60n; + hours = minutes / 60n; + minutes %= 60n; + days = hours / 24n; + hours %= 24n; + } else if (largestUnit === TemporalUnit.Hour) { + microseconds = nanoseconds / 1000n; + nanoseconds %= 1000n; + milliseconds = microseconds / 1000n; + microseconds %= 1000n; + seconds = milliseconds / 1000n; + milliseconds %= 1000n; + minutes = seconds / 60n; + seconds %= 60n; + hours = minutes / 60n; + minutes %= 60n; + } else if (largestUnit === TemporalUnit.Minute) { + microseconds = nanoseconds / 1000n; + nanoseconds %= 1000n; + milliseconds = microseconds / 1000n; + microseconds %= 1000n; + seconds = milliseconds / 1000n; + milliseconds %= 1000n; + minutes = seconds / 60n; + seconds %= 60n; + } else if (largestUnit === TemporalUnit.Second) { + microseconds = nanoseconds / 1000n; + nanoseconds %= 1000n; + milliseconds = microseconds / 1000n; + microseconds %= 1000n; + seconds = milliseconds / 1000n; + milliseconds %= 1000n; + } else if (largestUnit === TemporalUnit.Millisecond) { + microseconds = nanoseconds / 1000n; + nanoseconds %= 1000n; + milliseconds = microseconds / 1000n; + microseconds %= 1000n; + } else if (largestUnit === TemporalUnit.Microsecond) { + microseconds = nanoseconds / 1000n; + nanoseconds %= 1000n; + } else { + Assert(largestUnit === TemporalUnit.Nanosecond); + } + return CreateTemporalDuration(internalDuration.Date.Years, internalDuration.Date.Months, internalDuration.Date.Weeks, internalDuration.Date.Days + Number(days) * sign, Number(hours) * sign, Number(minutes) * sign, Number(seconds) * sign, Number(milliseconds) * sign, Number(microseconds) * sign, Number(nanoseconds) * sign); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createdatedurationrecord */ +export function CreateDateDurationRecord(years: number, months: number, weeks: number, days: number): PlainCompletion { + if (!IsValidDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0)) { + return surroundingAgent.Throw('RangeError', 'InvalidDuration'); + } + return { + Years: years, + Months: months, + Weeks: weeks, + Days: days, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adjustdatedurationrecord */ +export function AdjustDateDurationRecord( + dateDuration: DateDurationRecord, + days: number, + weeks?: number, + months?: number, +): PlainCompletion { + weeks ||= dateDuration.Weeks; + months ||= dateDuration.Months; + return CreateDateDurationRecord(dateDuration.Years, months, weeks, days); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-combinedateandtimeduration */ +export function CombineDateAndTimeDuration(dateDuration: DateDurationRecord, timeDuration: TimeDuration): InternalDurationRecord { + const dateSign = DateDurationSign(dateDuration); + const timeSign = TimeDurationSign(timeDuration); + if (dateSign !== 0 && timeSign !== 0) { + Assert(dateSign === timeSign); + } + return { + Date: dateDuration, + Time: timeDuration, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration */ +export function* ToTemporalDuration(item: Value): ValueEvaluator { + if (isTemporalDurationObject(item)) { + return X(CreateTemporalDuration(item.Years, item.Months, item.Weeks, item.Days, item.Hours, item.Minutes, item.Seconds, item.Milliseconds, item.Microseconds, item.Nanoseconds)); + } + if (!(item instanceof ObjectValue)) { + if (!(item instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'CannotConvertToTemporalDuration', item); + } + return ParseTemporalDurationString(item.stringValue()); + } + const result: Mutable = { + Years: 0, + Months: 0, + Weeks: 0, + Days: 0, + Hours: 0, + Microseconds: 0, + Milliseconds: 0, + Minutes: 0, + Nanoseconds: 0, + Seconds: 0, + }; + const partial = Q(yield* ToTemporalPartialDurationRecord(item)); + if (partial.Years !== undefined) { + result.Years = partial.Years; + } + if (partial.Months !== undefined) { + result.Months = partial.Months; + } + if (partial.Weeks !== undefined) { + result.Weeks = partial.Weeks; + } + if (partial.Days !== undefined) { + result.Days = partial.Days; + } + if (partial.Hours !== undefined) { + result.Hours = partial.Hours; + } + if (partial.Minutes !== undefined) { + result.Minutes = partial.Minutes; + } + if (partial.Seconds !== undefined) { + result.Seconds = partial.Seconds; + } + if (partial.Milliseconds !== undefined) { + result.Milliseconds = partial.Milliseconds; + } + if (partial.Microseconds !== undefined) { + result.Microseconds = partial.Microseconds; + } + if (partial.Nanoseconds !== undefined) { + result.Nanoseconds = partial.Nanoseconds; + } + return yield* CreateTemporalDuration(result.Years!, result.Months!, result.Weeks!, result.Days!, result.Hours!, result.Minutes!, result.Seconds!, result.Milliseconds!, result.Microseconds!, result.Nanoseconds!); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-durationsign */ +export function DurationSign(duration: TemporalDurationObject): -1 | 0 | 1 { + if (duration.Years < 0) { + return -1; + } + if (duration.Years > 0) { + return 1; + } + if (duration.Months < 0) { + return -1; + } + if (duration.Months > 0) { + return 1; + } + if (duration.Weeks < 0) { + return -1; + } + if (duration.Weeks > 0) { + return 1; + } + if (duration.Days < 0) { + return -1; + } + if (duration.Days > 0) { + return 1; + } + if (duration.Hours < 0) { + return -1; + } + if (duration.Hours > 0) { + return 1; + } + if (duration.Minutes < 0) { + return -1; + } + if (duration.Minutes > 0) { + return 1; + } + if (duration.Seconds < 0) { + return -1; + } + if (duration.Seconds > 0) { + return 1; + } + if (duration.Milliseconds < 0) { + return -1; + } + if (duration.Milliseconds > 0) { + return 1; + } + if (duration.Microseconds < 0) { + return -1; + } + if (duration.Microseconds > 0) { + return 1; + } + if (duration.Nanoseconds < 0) { + return -1; + } + if (duration.Nanoseconds > 0) { + return 1; + } + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-datedurationsign */ +export function DateDurationSign(dateDuration: DateDurationRecord): -1 | 0 | 1 { + if (dateDuration.Years < 0) { + return -1; + } + if (dateDuration.Years > 0) { + return 1; + } + if (dateDuration.Months < 0) { + return -1; + } + if (dateDuration.Months > 0) { + return 1; + } + if (dateDuration.Weeks < 0) { + return -1; + } + if (dateDuration.Weeks > 0) { + return 1; + } + if (dateDuration.Days < 0) { + return -1; + } + if (dateDuration.Days > 0) { + return 1; + } + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-internaldurationsign */ +export function InternalDurationSign(internalDuration: InternalDurationRecord): -1 | 0 | 1 { + const dateSign = DateDurationSign(internalDuration.Date); + if (dateSign !== 0) { + return dateSign; + } + return TimeDurationSign(internalDuration.Time); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration */ +export function IsValidDuration( + years: number, + months: number, + weeks: number, + days: number, + hours: number, + minutes: number, + seconds: number, + milliseconds: number, + microseconds: number, + nanoseconds: number, +): boolean { + let sign = 0; + for (const v of [years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds]) { + if (!Number.isFinite(v)) { + return false; + } + if (v < 0) { + if (sign > 0) { + return false; + } + sign = -1; + } else if (v > 0) { + if (sign < 0) { + return false; + } + sign = 1; + } + } + if (Math.abs(years) >= 2 ** 32) { + return false; + } + if (Math.abs(months) >= 2 ** 32) { + return false; + } + if (Math.abs(weeks) >= 2 ** 32) { + return false; + } + // Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds + ℝ(𝔽(milliseconds)) × 10**-3 + ℝ(𝔽(microseconds)) × 10**-6 + ℝ(𝔽(nanoseconds)) × 10**-9. + // If abs(normalizedSeconds) ≥ 2**53, return false. + let normalizedSeconds = BigInt(days) * 86400n + BigInt(hours) * 3600n + BigInt(minutes) * 60n + BigInt(seconds); + if (abs(normalizedSeconds) >= 2 ** 53) { + return false; + } + normalizedSeconds *= BigInt(10e9); // Convert to nanoseconds + normalizedSeconds += BigInt(milliseconds) * 1000000n + BigInt(microseconds) * 1000n + BigInt(nanoseconds); + if (abs(normalizedSeconds) >= BigInt(2 ** 53) * BigInt(10e9)) { + return false; + } + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-defaulttemporallargestunit */ +export function DefaultTemporalLargestUnit(duration: TemporalDurationObject): TemporalUnit { + if (duration.Years !== 0) { + return TemporalUnit.Year; + } + if (duration.Months !== 0) { + return TemporalUnit.Month; + } + if (duration.Weeks !== 0) { + return TemporalUnit.Week; + } + if (duration.Days !== 0) { + return TemporalUnit.Day; + } + if (duration.Hours !== 0) { + return TemporalUnit.Hour; + } + if (duration.Minutes !== 0) { + return TemporalUnit.Minute; + } + if (duration.Seconds !== 0) { + return TemporalUnit.Second; + } + if (duration.Milliseconds !== 0) { + return TemporalUnit.Millisecond; + } + if (duration.Microseconds !== 0) { + return TemporalUnit.Microsecond; + } + return TemporalUnit.Nanosecond; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalpartialdurationrecord */ +export function* ToTemporalPartialDurationRecord(temporalDurationLike: Value): PlainEvaluator { + if (!(temporalDurationLike instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', temporalDurationLike); + } + const result: Mutable = { + Days: undefined, + Hours: undefined, + Microseconds: undefined, + Milliseconds: undefined, + Minutes: undefined, + Months: undefined, + Nanoseconds: undefined, + Seconds: undefined, + Weeks: undefined, + Years: undefined, + }; + const days = Q(yield* Get(temporalDurationLike, Value('days'))); + if (days !== Value.undefined) { + result.Days = Q(yield* ToIntegerIfIntegral(days)); + } + const hours = Q(yield* Get(temporalDurationLike, Value('hours'))); + if (days !== Value.undefined) { + result.Hours = Q(yield* ToIntegerIfIntegral(hours)); + } + const microseconds = Q(yield* Get(temporalDurationLike, Value('microseconds'))); + if (microseconds !== Value.undefined) { + result.Microseconds = Q(yield* ToIntegerIfIntegral(microseconds)); + } + const milliseconds = Q(yield* Get(temporalDurationLike, Value('milliseconds'))); + if (milliseconds !== Value.undefined) { + result.Milliseconds = Q(yield* ToIntegerIfIntegral(milliseconds)); + } + const minutes = Q(yield* Get(temporalDurationLike, Value('minutes'))); + if (minutes !== Value.undefined) { + result.Minutes = Q(yield* ToIntegerIfIntegral(minutes)); + } + const months = Q(yield* Get(temporalDurationLike, Value('months'))); + if (months !== Value.undefined) { + result.Months = Q(yield* ToIntegerIfIntegral(months)); + } + const nanoseconds = Q(yield* Get(temporalDurationLike, Value('nanoseconds'))); + if (nanoseconds !== Value.undefined) { + result.Nanoseconds = Q(yield* ToIntegerIfIntegral(nanoseconds)); + } + const seconds = Q(yield* Get(temporalDurationLike, Value('seconds'))); + if (seconds !== Value.undefined) { + result.Seconds = Q(yield* ToIntegerIfIntegral(seconds)); + } + const weeks = Q(yield* Get(temporalDurationLike, Value('weeks'))); + if (weeks !== Value.undefined) { + result.Weeks = Q(yield* ToIntegerIfIntegral(weeks)); + } + const years = Q(yield* Get(temporalDurationLike, Value('years'))); + if (years !== Value.undefined) { + result.Years = Q(yield* ToIntegerIfIntegral(years)); + } + + if (years === Value.undefined + && months === Value.undefined + && weeks === Value.undefined + && days === Value.undefined + && hours === Value.undefined + && minutes === Value.undefined + && seconds === Value.undefined + && milliseconds === Value.undefined + && microseconds === Value.undefined + && nanoseconds === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'InvalidDuration'); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporalduration */ +export function* CreateTemporalDuration( + years: number, + months: number, + weeks: number, + days: number, + hours: number, + minutes: number, + seconds: number, + milliseconds: number, + microseconds: number, + nanoseconds: number, + newTarget?: FunctionObject, +): ValueEvaluator { + if (!IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)) { + return surroundingAgent.Throw('RangeError', 'InvalidDuration'); + } + if (newTarget === undefined) { + newTarget = surroundingAgent.currentRealmRecord.Intrinsics['%Temporal.Duration%']; + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.Duration.prototype%', [ + 'InitializedTemporalDuration', + 'Years', + 'Months', + 'Weeks', + 'Days', + 'Hours', + 'Minutes', + 'Seconds', + 'Milliseconds', + 'Microseconds', + 'Nanoseconds', + ])) as Mutable; + object.Years = years; + object.Months = months; + object.Weeks = weeks; + object.Days = days; + object.Hours = hours; + object.Minutes = minutes; + object.Seconds = seconds; + object.Milliseconds = milliseconds; + object.Microseconds = microseconds; + object.Nanoseconds = nanoseconds; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createnegatedtemporalduration */ +export function CreateNegatedTemporalDuration(duration: TemporalDurationObject): TemporalDurationObject { + return X(CreateTemporalDuration( + -duration.Years, + -duration.Months, + -duration.Weeks, + -duration.Days, + -duration.Hours, + -duration.Minutes, + -duration.Seconds, + -duration.Milliseconds, + -duration.Microseconds, + -duration.Nanoseconds, + )); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromcomponents */ +export function TimeDurationFromComponents( + hours: number, + minutes: number, + seconds: number, + milliseconds: number, + microseconds: number, + nanoseconds: number, +): TimeDuration { + minutes += hours * 60; + seconds += minutes * 60; + milliseconds += seconds * 1000; + microseconds += milliseconds * 1000; + nanoseconds += microseconds * 1000; + Assert(abs(nanoseconds) <= maxTimeDuration); + return nanoseconds as TimeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-addtimeduration */ +export function AddTimeDuration(one: TimeDuration, two: TimeDuration): PlainCompletion { + const result = BigInt(one) + BigInt(two); + if (abs(result) > maxTimeDuration) { + return surroundingAgent.Throw('RangeError', 'InvalidDuration'); + } + return Number(result) as TimeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-add24hourdaystotimeduration */ +export function Add24HourDaysToTimeDuration(d: TimeDuration, days: number): PlainCompletion { + const result = BigInt(d) + BigInt(days) * BigInt(nsPerDay); + if (abs(result) > maxTimeDuration) { + return surroundingAgent.Throw('RangeError', 'InvalidDuration'); + } + return Number(result) as TimeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-addtimedurationtoepochnanoseconds */ +export function AddTimeDurationToEpochNanoseconds(d: TimeDuration, epochNs: bigint): bigint { + return epochNs + BigInt(d); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-comparetimeduration */ +export function CompareTimeDuration(one: TimeDuration, two: TimeDuration): -1 | 0 | 1 { + if (one > two) { + return 1; + } + if (one < two) { + return -1; + } + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromepochnanosecondsdifference */ +export function TimeDurationFromEpochNanosecondsDifference(one: bigint, two: bigint): TimeDuration { + const result = Number(one) - Number(two); + Assert(abs(result) <= maxTimeDuration); + return result as TimeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-roundtimedurationtoincrement */ +export function RoundTimeDurationToIncrement( + d: TimeDuration, + increment: number, + roundingMode: RoundingMode, +): PlainCompletion { + const rounded = RoundNumberToIncrement(d, increment, roundingMode); + if (abs(rounded) > maxTimeDuration) { + return surroundingAgent.Throw('RangeError', 'InvalidDuration'); + } + return rounded as TimeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-timedurationsign */ +export function TimeDurationSign(d: TimeDuration): -1 | 0 | 1 { + if (d < 0) { + return -1; + } + if (d > 0) { + return 1; + } + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-datedurationdays */ +export function DateDurationDays(dateDuration: DateDurationRecord, plainRelativeTo: TemporalPlainDateObject): PlainCompletion { + const yearsMonthsWeeksDuration = X(AdjustDateDurationRecord(dateDuration, 0)); + if (DateDurationSign(yearsMonthsWeeksDuration) === 0) { + return dateDuration.Days; + } + const later = Q(CalendarDateAdd(plainRelativeTo.Calendar, plainRelativeTo.ISODate, yearsMonthsWeeksDuration, 'constrain')); + const epochDays1 = ISODateToEpochDays(plainRelativeTo.ISODate.Year, plainRelativeTo.ISODate.Month - 1, plainRelativeTo.ISODate.Day); + const epochDays2 = ISODateToEpochDays(later.Year, later.Month - 1, later.Day); + const yearsMonthsWeeksInDays = epochDays2 - epochDays1; + return dateDuration.Days + Number(yearsMonthsWeeksInDays); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-roundtimeduration */ +export function RoundTimeDuration( + timeDuration: TimeDuration, + increment: number, + unit: TimeUnit, + roundingMode: RoundingMode, +): PlainCompletion { + const divisor = Table21_LengthInNanoSeconds[unit]; + return RoundTimeDurationToIncrement(timeDuration, divisor * increment, roundingMode); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totaltimeduration */ +export function TotalTimeDuration(timeDuration: TimeDuration, unit: TimeUnit | TemporalUnit.Day): number { + const divisor = Table21_LengthInNanoSeconds[unit]; + // TODO(temporal): Floating point problem + // 2. NOTE: The following step cannot be implemented directly using floating-point arithmetic when 𝔽(timeDuration) is not a safe integer. The division can be implemented in C++ with the __float128 type if the compiler supports it, or with software emulation such as in the SoftFP library. + return timeDuration / divisor; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-duration-nudge-result-records */ +export interface DurationNudgeResultRecord { + readonly Duration: InternalDurationRecord; + readonly NudgedEpochNs: bigint; + readonly DidExpandCalendarUnit: boolean; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-computenudgewindow */ +export function ComputeNudgeWindow( + sign: -1 | 1, + duration: InternalDurationRecord, + originEpochNs: bigint, + isoDateTime: ISODateTimeRecord, + timeZone: TimeZoneIdentifier | undefined, + calendar: CalendarType, + increment: number, + unit: DateUnit, + additionalShift: boolean, +): PlainCompletion<{ + R1: number; + R2: number; + StartEpochNs: bigint; + EndEpochNs: bigint; + // TODO(temporal): spec error? actually DateDurationRecord, but InternalDurationRecord in spec + StartDuration: DateDurationRecord; + // TODO(temporal): spec error? actually DateDurationRecord, but InternalDurationRecord in spec + EndDuration: DateDurationRecord; +}> { + let r1: number; + let r2: number; + let startDuration; + let endDuration; + if (unit === TemporalUnit.Year) { + const years = RoundNumberToIncrement(duration.Date.Years, increment, RoundingMode.Trunc); + if (!additionalShift) { + r1 = years; + } else { + r1 = years + increment * sign; + } + r2 = r1 + increment * sign; + startDuration = Q(CreateDateDurationRecord(r1, 0, 0, 0)); + endDuration = Q(CreateDateDurationRecord(r2, 0, 0, 0)); + } else if (unit === TemporalUnit.Month) { + const months = RoundNumberToIncrement(duration.Date.Months, increment, RoundingMode.Trunc); + if (!additionalShift) { + r1 = months; + } else { + r1 = months + increment * sign; + } + r2 = r1 + increment * sign; + startDuration = Q(AdjustDateDurationRecord(duration.Date, 0, 0, r1)); + endDuration = Q(AdjustDateDurationRecord(duration.Date, 0, 0, r2)); + } else if (unit === TemporalUnit.Week) { + const yearsMonths = X(AdjustDateDurationRecord(duration.Date, 0, 0)); + const weeksStart = Q(CalendarDateAdd(calendar, isoDateTime.ISODate, yearsMonths, 'constrain')); + const weeksEnd = AddDaysToISODate(weeksStart, duration.Date.Days); + const untilResult = CalendarDateUntil(calendar, weeksStart, weeksEnd, TemporalUnit.Week); + const weeks = RoundNumberToIncrement(duration.Date.Weeks + untilResult.Weeks, increment, RoundingMode.Trunc); + r1 = weeks; + r2 = weeks + increment * sign; + startDuration = Q(AdjustDateDurationRecord(duration.Date, 0, r1)); + endDuration = Q(AdjustDateDurationRecord(duration.Date, 0, r2)); + } else { + Assert(unit === TemporalUnit.Day); + const days = RoundNumberToIncrement(duration.Date.Days, increment, RoundingMode.Trunc); + r1 = days; + r2 = days + increment * sign; + startDuration = Q(AdjustDateDurationRecord(duration.Date, r1)); + endDuration = Q(AdjustDateDurationRecord(duration.Date, r2)); + } + if (sign === 1) Assert(r1 >= 0 && r1 < r2); + if (sign === -1) Assert(r1 <= 0 && r1 > r2); + let startEpochNs; + if (r1 === 0) { + startEpochNs = originEpochNs; + } else { + const start = Q(CalendarDateAdd(calendar, isoDateTime.ISODate, startDuration, 'constrain')); + const startDateTime = CombineISODateAndTimeRecord(start, isoDateTime.Time); + if (timeZone === undefined) { + startEpochNs = GetUTCEpochNanoseconds(startDateTime); + } else { + startEpochNs = Q(GetEpochNanosecondsFor(timeZone, startDateTime, 'compatible')); + } + } + const end = Q(CalendarDateAdd(calendar, isoDateTime.ISODate, endDuration, 'constrain')); + const endDateTime = CombineISODateAndTimeRecord(end, isoDateTime.Time); + let endEpochNs; + if (timeZone === undefined) { + endEpochNs = GetUTCEpochNanoseconds(endDateTime); + } else { + endEpochNs = Q(GetEpochNanosecondsFor(timeZone, endDateTime, 'compatible')); + } + return { + R1: r1, + R2: r2, + StartEpochNs: startEpochNs, + EndEpochNs: endEpochNs, + StartDuration: startDuration, + EndDuration: endDuration, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nudgetocalendarunit */ +export function NudgeToCalendarUnit( + sign: -1 | 1, + duration: InternalDurationRecord, + originEpochNs: bigint, + destEpochNs: bigint, + isoDateTime: ISODateTimeRecord, + timeZone: TimeZoneIdentifier | undefined, + calendar: CalendarType, + increment: number, + unit: DateUnit, + roundingMode: RoundingMode, +): PlainCompletion<{ NudgeResult: DurationNudgeResultRecord; Total: number }> { + let didExpandCalendarUnit = false; + let nudgeWindow = Q(ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, false)); + let startEpochNs = nudgeWindow.StartEpochNs; + let endEpochNs = nudgeWindow.EndEpochNs; + if (sign === 1) { + if (!(startEpochNs <= destEpochNs && destEpochNs <= endEpochNs)) { + nudgeWindow = Q(ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, true)); + Assert(nudgeWindow.StartEpochNs <= destEpochNs && destEpochNs <= nudgeWindow.EndEpochNs); + didExpandCalendarUnit = true; + } + } else if (!(endEpochNs <= destEpochNs && destEpochNs <= startEpochNs)) { + nudgeWindow = Q(ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, true)); + Assert(nudgeWindow.EndEpochNs <= destEpochNs && destEpochNs <= nudgeWindow.StartEpochNs); + didExpandCalendarUnit = true; + } + const r1 = nudgeWindow.R1; + const r2 = nudgeWindow.R2; + startEpochNs = nudgeWindow.StartEpochNs; + endEpochNs = nudgeWindow.EndEpochNs; + const startDuration = nudgeWindow.StartDuration; + const endDuration = nudgeWindow.EndDuration; + Assert(startEpochNs !== endEpochNs); + // TODO(temporal): Floating point problem + const progress = Number(destEpochNs - startEpochNs) / Number(endEpochNs - startEpochNs); + const total = r1 + Number(progress) * increment * sign; + // 16. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic. This division can be implemented as if expressing total as the quotient of two time durations (which may not be safe integers), performing all other calculations before the division, and finally performing one division operation with a floating-point result for total. The division can be implemented in C++ with the __float128 type if the compiler supports it, or with software emulation such as in the SoftFP library. + Assert(0 <= progress && progress <= 1); + const isNegative = sign < 0 ? 'negative' : 'positive'; + const unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, isNegative); + let roundedUnit; + if (progress === 1) { + roundedUnit = abs(r2); + } else { + Assert(abs(r1) <= abs(total) && abs(total) <= abs(r2)); + roundedUnit = ApplyUnsignedRoundingMode(abs(total), abs(r1), abs(r2), unsignedRoundingMode); + } + let resultDuration; + let nudgedEpochNs; + if (roundedUnit === abs(r2)) { + didExpandCalendarUnit = true; + resultDuration = endDuration; + nudgedEpochNs = endEpochNs; + } else { + resultDuration = startDuration; + nudgedEpochNs = startEpochNs; + } + resultDuration = CombineDateAndTimeDuration(resultDuration, 0 as TimeDuration); + const nudgeResult: DurationNudgeResultRecord = { + Duration: resultDuration, + NudgedEpochNs: nudgedEpochNs, + DidExpandCalendarUnit: didExpandCalendarUnit, + }; + return { NudgeResult: nudgeResult, Total: total }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nudgetozonedtime */ +export function NudgeToZonedTime( + sign: -1 | 1, + duration: InternalDurationRecord, + isoDateTime: ISODateTimeRecord, + timeZone: TimeZoneIdentifier, + calendar: CalendarType, + increment: number, + unit: TimeUnit, + roundingMode: RoundingMode, +): PlainCompletion { + const start = Q(CalendarDateAdd(calendar, isoDateTime.ISODate, duration.Date, 'constrain')); + const startDateTime = CombineISODateAndTimeRecord(start, isoDateTime.Time); + const endDate = AddDaysToISODate(start, sign); + const endDateTime = CombineISODateAndTimeRecord(endDate, isoDateTime.Time); + const startEpochNs = Q(GetEpochNanosecondsFor(timeZone, startDateTime, 'compatible')); + const endEpochNs = Q(GetEpochNanosecondsFor(timeZone, endDateTime, 'compatible')); + const daySpan = TimeDurationFromEpochNanosecondsDifference(endEpochNs, startEpochNs); + Assert(TimeDurationSign(daySpan) === sign); + const unitLength = Table21_LengthInNanoSeconds[unit]; + let roundedTimeDuration = Q(RoundTimeDurationToIncrement(duration.Time, increment * unitLength, roundingMode)); + const beyondDaySpan = X(AddTimeDuration(roundedTimeDuration, (-daySpan) as TimeDuration)); + let didRoundBeyondDay; + let dayDelta; + let nudgedEpochNs; + if (TimeDurationSign(beyondDaySpan) !== -sign) { + didRoundBeyondDay = true; + dayDelta = sign; + roundedTimeDuration = Q(RoundTimeDurationToIncrement(beyondDaySpan, increment * unitLength, roundingMode)); + nudgedEpochNs = AddTimeDurationToEpochNanoseconds(roundedTimeDuration, endEpochNs); + } else { + didRoundBeyondDay = false; + dayDelta = 0; + nudgedEpochNs = AddTimeDurationToEpochNanoseconds(roundedTimeDuration, startEpochNs); + } + const dateDuration = X(AdjustDateDurationRecord(duration.Date, duration.Date.Days + dayDelta)); + const resultDuration = CombineDateAndTimeDuration(dateDuration, roundedTimeDuration); + return { + Duration: resultDuration, + NudgedEpochNs: nudgedEpochNs, + DidExpandCalendarUnit: didRoundBeyondDay, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-nudgetodayortime */ +export function NudgeToDayOrTime( + duration: InternalDurationRecord, + destEpochNs: bigint, + largestUnit: TemporalUnit, + increment: number, + smallestUnit: TimeUnit | TemporalUnit.Day, + roundingMode: RoundingMode, +): PlainCompletion { + const timeDuration = X(Add24HourDaysToTimeDuration(duration.Time, duration.Date.Days)); + const unitLength = Table21_LengthInNanoSeconds[smallestUnit]; + const roundedTime = Q(RoundTimeDurationToIncrement(timeDuration, unitLength * increment, roundingMode)); + const diffTime = X(AddTimeDuration(roundedTime, (-timeDuration) as TimeDuration)); + const wholeDays = Math.trunc(TotalTimeDuration(timeDuration, TemporalUnit.Day)); + const roundedWholeDays = Math.trunc(TotalTimeDuration(roundedTime, TemporalUnit.Day)); + const dayDelta = roundedWholeDays - wholeDays; + let dayDeltaSign; + if (dayDelta < 0) dayDeltaSign = -1; + else if (dayDelta > 0) dayDeltaSign = 1; + else dayDeltaSign = 0; + const didExpandDays = dayDeltaSign === TimeDurationSign(timeDuration); + const nudgedEpochNs = AddTimeDurationToEpochNanoseconds(diffTime, destEpochNs); + let days = 0; + let remainder = roundedTime; + if (TemporalUnitCategory(largestUnit) === 'date') { + days = roundedWholeDays; + remainder = X(AddTimeDuration(roundedTime, TimeDurationFromComponents(-roundedWholeDays * HoursPerDay, 0, 0, 0, 0, 0))); + } + const dateDuration = X(AdjustDateDurationRecord(duration.Date, days)); + const resultDuration = CombineDateAndTimeDuration(dateDuration, remainder); + return { + Duration: resultDuration, + NudgedEpochNs: nudgedEpochNs, + DidExpandCalendarUnit: didExpandDays, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-bubblerelativeduration */ +export function BubbleRelativeDuration( + sign: -1 | 1, + duration: InternalDurationRecord, + nudgedEpochNs: bigint, + isoDateTime: ISODateTimeRecord, + timeZone: TimeZoneIdentifier | undefined, + calendar: CalendarType, + largestUnit: DateUnit, + smallestUnit: DateUnit, +): PlainCompletion { + if (smallestUnit === largestUnit) { + return duration; + } + const order = [ + TemporalUnit.Year, + TemporalUnit.Month, + TemporalUnit.Week, + TemporalUnit.Day, + ]; + const largestUnitIndex = order.indexOf(largestUnit); + const smallestUnitIndex = order.indexOf(smallestUnit); + let unitIndex = smallestUnitIndex - 1; + let done = false; + while (unitIndex >= largestUnitIndex && !done) { + const unit = order[unitIndex]; + if (unit !== TemporalUnit.Week || largestUnit === TemporalUnit.Week) { + let endDuration: DateDurationRecord; + if (unit === TemporalUnit.Year) { + const years = duration.Date.Years + sign; + endDuration = Q(CreateDateDurationRecord(years, 0, 0, 0)); + } else if (unit === TemporalUnit.Month) { + const months = duration.Date.Months + sign; + endDuration = Q(AdjustDateDurationRecord(duration.Date, 0, 0, months)); + } else { + Assert(unit === TemporalUnit.Week); + const weeks = duration.Date.Weeks + sign; + endDuration = Q(AdjustDateDurationRecord(duration.Date, 0, weeks)); + } + const end = Q(CalendarDateAdd(calendar, isoDateTime.ISODate, endDuration, 'constrain')); + const endDateTime = CombineISODateAndTimeRecord(end, isoDateTime.Time); + let endEpochNs; + if (timeZone === undefined) { + endEpochNs = GetUTCEpochNanoseconds(endDateTime); + } else { + endEpochNs = Q(GetEpochNanosecondsFor(timeZone, endDateTime, 'compatible')); + } + const beyondEnd = nudgedEpochNs - endEpochNs; + let beyondEndSign; + if (beyondEnd < 0) beyondEndSign = -1; + else if (beyondEnd > 0) beyondEndSign = 1; + else beyondEndSign = 0; + if (beyondEndSign !== -sign) { + duration = CombineDateAndTimeDuration(endDuration, 0 as TimeDuration); + } else { + done = true; + } + } + unitIndex -= 1; + } + return duration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-roundrelativeduration */ +export function RoundRelativeDuration( + duration: InternalDurationRecord, + originEpochNs: bigint, + destEpochNs: bigint, + isoDateTime: ISODateTimeRecord, + timeZone: TimeZoneIdentifier | undefined, + calendar: CalendarType, + largestUnit: TemporalUnit, + increment: number, + smallestUnit: TemporalUnit, + roundingMode: RoundingMode, +): PlainCompletion { + let irregularLengthUnit = false; + if (IsCalendarUnit(smallestUnit)) { + irregularLengthUnit = true; + } + if (timeZone !== undefined && smallestUnit === TemporalUnit.Day) { + irregularLengthUnit = true; + } + let sign: -1 | 1; + if (InternalDurationSign(duration) < 0) { + sign = -1; + } else { + sign = 1; + } + let nudgeResult; + if (irregularLengthUnit) { + const record = Q(NudgeToCalendarUnit(sign, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit as DateUnit, roundingMode)); + nudgeResult = record.NudgeResult; + } else if (timeZone !== undefined) { + Assert(__IsTimeUnit(smallestUnit)); + nudgeResult = Q(NudgeToZonedTime(sign, duration, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode)); + } else { + Assert(__IsTimeUnit(smallestUnit) || smallestUnit === TemporalUnit.Day); + nudgeResult = Q(NudgeToDayOrTime(duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode)); + } + duration = nudgeResult.Duration; + if (nudgeResult.DidExpandCalendarUnit && smallestUnit !== TemporalUnit.Week) { + const startUnit = LargerOfTwoTemporalUnits(smallestUnit, TemporalUnit.Day); + Assert(__IsDateUnit(startUnit) && __IsDateUnit(largestUnit)); + duration = Q(BubbleRelativeDuration(sign, duration, nudgeResult.NudgedEpochNs, isoDateTime, timeZone, calendar, largestUnit, startUnit)); + } + return duration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totalrelativeduration */ +export function TotalRelativeDuration( + duration: InternalDurationRecord, + originEpochNs: bigint, + destEpochNs: bigint, + isoDateTime: ISODateTimeRecord, + timeZone: TimeZoneIdentifier | undefined, + calendar: CalendarType, + unit: TemporalUnit, +): PlainCompletion { + if (IsCalendarUnit(unit) || (timeZone !== undefined && unit === TemporalUnit.Day)) { + const sign = InternalDurationSign(duration); + // https://github.com/tc39/proposal-temporal/issues/3131 + const record = Q(NudgeToCalendarUnit(sign as 1, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, 1, unit, RoundingMode.Trunc)); + return record.Total; + } + __ts_cast__>(unit); + const timeDuration = X(Add24HourDaysToTimeDuration(duration.Time, duration.Date.Days)); + return TotalTimeDuration(timeDuration, unit); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring */ +export function TemporalDurationToString( + duration: TemporalDurationObject, + precision: number | 'auto', +): string { + const sign = DurationSign(duration); + let datePart = ''; + if (duration.Years !== 0) { + datePart += `${Math.abs(duration.Years)}Y`; + } + if (duration.Months !== 0) { + datePart += `${Math.abs(duration.Months)}M`; + } + if (duration.Weeks !== 0) { + datePart += `${Math.abs(duration.Weeks)}W`; + } + if (duration.Days !== 0) { + datePart += `${Math.abs(duration.Days)}D`; + } + let timePart = ''; + if (duration.Hours !== 0) { + timePart += `${Math.abs(duration.Hours)}H`; + } + if (duration.Minutes !== 0) { + timePart += `${Math.abs(duration.Minutes)}M`; + } + let zeroMinutesAndHigher = false; + const _ = DefaultTemporalLargestUnit(duration); + if (_ === TemporalUnit.Second || _ === TemporalUnit.Millisecond || _ === TemporalUnit.Microsecond || _ === TemporalUnit.Nanosecond) { + zeroMinutesAndHigher = true; + } + const secondsDuration = TimeDurationFromComponents(0, 0, duration.Seconds, duration.Milliseconds, duration.Microseconds, duration.Nanoseconds); + if (secondsDuration !== 0 || zeroMinutesAndHigher || precision !== 'auto') { + const secondsPart = Math.abs(Math.trunc(secondsDuration / 10e9)).toString(); + const subSecondsPart = FormatFractionalSeconds(Math.abs(secondsDuration % 10e9), precision); + timePart += `${secondsPart + subSecondsPart}S`; + } + const signPart = sign < 0 ? '-' : ''; + let result = `${signPart}P${datePart}`; + if (timePart !== '') { + result += `T${timePart}`; + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurations */ +export function* AddDurations( + operation: 'add' | 'subtract', + duration: TemporalDurationObject, + _other: Value, +): ValueEvaluator { + const other = Q(yield* ToTemporalDuration(_other)); + if (operation === 'subtract') { + _other = CreateNegatedTemporalDuration(other); + } + const largestUnit1 = DefaultTemporalLargestUnit(duration); + const largestUnit2 = DefaultTemporalLargestUnit(other); + const largestUnit = LargerOfTwoTemporalUnits(largestUnit1, largestUnit2); + if (IsCalendarUnit(largestUnit)) { + return surroundingAgent.Throw('RangeError', 'InvalidDuration'); + } + const d1 = ToInternalDurationRecordWith24HourDays(duration); + const d2 = ToInternalDurationRecordWith24HourDays(other); + const timeResult = Q(AddTimeDuration(d1.Time, d2.Time)); + const result = CombineDateAndTimeDuration(ZeroDateDuration(), timeResult); + return Q(yield* TemporalDurationFromInternal(result, largestUnit)); +}/** https://tc39.es/proposal-temporal/#eqn-maxTimeDuration */ + +export const maxTimeDuration = 9007199254740991999999999n; diff --git a/src/abstract-ops/temporal/instant.mts b/src/abstract-ops/temporal/instant.mts new file mode 100644 index 0000000..30f7778 --- /dev/null +++ b/src/abstract-ops/temporal/instant.mts @@ -0,0 +1,177 @@ +import type { TemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import { type TemporalInstantObject, isTemporalInstantObject } from '../../intrinsics/Temporal/Instant.mts'; +import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { ParseISODateTime, ParseDateTimeUTCOffset } from '../../parser/TemporalParser.mts'; +import { + GetUTCEpochNanoseconds, RoundingMode, type TimeZoneIdentifier, GetOptionsObject, +} from './addition.mts'; +import { + type FunctionObject, type ValueEvaluator, Assert, surroundingAgent, Q, OrdinaryCreateFromConstructor, type Mutable, Value, ObjectValue, X, ToPrimitive, JSStringValue, Throw, CheckISODaysRange, type TimeDuration, type PlainCompletion, AddTimeDurationToEpochNanoseconds, type TimeUnit, type InternalDurationRecord, TimeDurationFromEpochNanosecondsDifference, RoundTimeDuration, CombineDateAndTimeDuration, ZeroDateDuration, Table21_LengthInNanoSeconds, RoundNumberToIncrementAsIfPositive, GetISODateTimeFor, GetOffsetNanosecondsFor, FormatDateTimeUTCOffsetRounded, GetDifferenceSettings, TemporalUnit, TemporalDurationFromInternal, CreateNegatedTemporalDuration, ToTemporalDuration, DefaultTemporalLargestUnit, TemporalUnitCategory, ToInternalDurationRecordWith24HourDays, + BalanceISODateTime, + ISODateTimeToString, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#eqn-nsPerDay */ +export const nsPerDay = 8.64e13; +/** https://tc39.es/proposal-temporal/#eqn-nsMaxInstant */ +export const nsMaxInstant = 8.64e21; +/** https://tc39.es/proposal-temporal/#eqn-nsMinInstant */ +export const nsMinInstant = -8.64e21; + +/** https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds */ +export function IsValidEpochNanoseconds(epochNanoseconds: bigint | number): boolean { + if (epochNanoseconds < nsMinInstant || epochNanoseconds > nsMaxInstant) { + return false; + } + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporalinstant */ +export function* CreateTemporalInstant(epochNanoseconds: bigint, newTarget?: FunctionObject): ValueEvaluator { + Assert(IsValidEpochNanoseconds(epochNanoseconds)); + if (newTarget === undefined) { + newTarget = surroundingAgent.intrinsic('%Temporal.Instant%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.Instant.prototype%', [ + 'InitializedTemporalInstant', + 'EpochNanoseconds', + ])) as Mutable; + object.EpochNanoseconds = epochNanoseconds; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant */ +export function* ToTemporalInstant(item: Value): ValueEvaluator { + if (item instanceof ObjectValue) { + if (isTemporalInstantObject(item) || isTemporalZonedDateTimeObject(item)) { + return X(CreateTemporalInstant(item.EpochNanoseconds)); + } + item = Q(yield* ToPrimitive(item, 'string')); + } + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('$1 is not a string', item); + } + const parsed = Q(ParseISODateTime(item.stringValue(), ['TemporalInstantString'])); + // Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or parsed.[[TimeZone]].[[Z]] is true, but not both. + { + const a = parsed.TimeZone.OffsetString !== undefined; + const b = parsed.TimeZone.Z; + Assert((a || b) && !(a && b)); + } + const OffsetString = parsed.TimeZone.OffsetString!; + const offsetNanoseconds = parsed.TimeZone.Z ? 0 : X(ParseDateTimeUTCOffset(OffsetString)); + const time = parsed.Time; + Assert(time !== 'start-of-day'); + const balanced = BalanceISODateTime(parsed.Year!, parsed.Month, parsed.Day, time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond, time.Nanosecond - offsetNanoseconds); + Q(CheckISODaysRange(balanced.ISODate)); + const epochNanoseconds = GetUTCEpochNanoseconds(balanced); + if (!IsValidEpochNanoseconds(epochNanoseconds)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds); + } + return X(CreateTemporalInstant(epochNanoseconds)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-compareepochnanoseconds */ +export function CompareEpochNanoseconds(epochNanosecondsOne: bigint, epochNanosecondsTwo: bigint): -1 | 0 | 1 { + if (epochNanosecondsOne > epochNanosecondsTwo) { + return 1; + } + if (epochNanosecondsOne < epochNanosecondsTwo) { + return -1; + } + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-addinstant */ +export function AddInstant(epochNanoseconds: bigint, timeDuration: TimeDuration): PlainCompletion { + const result = AddTimeDurationToEpochNanoseconds(timeDuration, epochNanoseconds); + if (!IsValidEpochNanoseconds(result)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant */ +export function DifferenceInstant( + ns1: bigint, + ns2: bigint, + roundingIncrement: number, + smallestUnit: TimeUnit, + roundingMode: RoundingMode, +): InternalDurationRecord { + let timeDuration = TimeDurationFromEpochNanosecondsDifference(ns2, ns1); + timeDuration = X(RoundTimeDuration(timeDuration, roundingIncrement, smallestUnit, roundingMode)); + return CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant */ +export function RoundTemporalInstant( + ns: bigint, + increment: number, + unit: TimeUnit, + roundingMode: RoundingMode, +): bigint { + const unitLength = Table21_LengthInNanoSeconds[unit]; + const incrementNs = increment * unitLength; + return BigInt(RoundNumberToIncrementAsIfPositive(Number(ns), incrementNs, roundingMode)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporalinstant-tostring */ +export function TemporalInstantToString( + instant: TemporalInstantObject, + timeZone: TimeZoneIdentifier | undefined, + precision: number | 'minute' | 'auto', +): string { + let outputTimeZone = timeZone; + if (outputTimeZone === undefined) { + outputTimeZone = 'UTC' as TimeZoneIdentifier; + } + const epochNs = instant.EpochNanoseconds; + const isoDateTime = GetISODateTimeFor(outputTimeZone, epochNs); + const dateTimeString = ISODateTimeToString(isoDateTime, 'iso8601', precision, 'never'); + let timeZoneString; + if (timeZone === undefined) { + timeZoneString = 'Z'; + } else { + const offsetNanoseconds = GetOffsetNanosecondsFor(outputTimeZone, epochNs); + timeZoneString = FormatDateTimeUTCOffsetRounded(offsetNanoseconds); + } + return dateTimeString + timeZoneString; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant */ +export function* DifferenceTemporalInstant( + operation: 'since' | 'until', + instant: TemporalInstantObject, + _other: Value, + options: Value, +): ValueEvaluator { + const other = Q(yield* ToTemporalInstant(_other)); + const resolvedOptions = Q(GetOptionsObject(options)); + const settings = Q(yield* GetDifferenceSettings(operation, resolvedOptions, 'time', [], TemporalUnit.Nanosecond, TemporalUnit.Second)); + const internalDuration = DifferenceInstant(instant.EpochNanoseconds, other.EpochNanoseconds, settings.RoundingIncrement, settings.SmallestUnit as TimeUnit, settings.RoundingMode); + let result = X(TemporalDurationFromInternal(internalDuration, settings.LargestUnit)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoinstant */ +export function* AddDurationToInstant( + operation: 'add' | 'subtract', + instant: TemporalInstantObject, + temporalDurationLike: Value, +): ValueEvaluator { + let duration = Q(yield* ToTemporalDuration(temporalDurationLike)); + if (operation === 'subtract') { + duration = CreateNegatedTemporalDuration(duration); + } + const largestUnit = DefaultTemporalLargestUnit(duration); + if (TemporalUnitCategory(largestUnit) === 'date') { + return Throw.RangeError('Cannot add a date to an instant'); + } + const internalDuration = ToInternalDurationRecordWith24HourDays(duration); + const ns = Q(AddInstant(instant.EpochNanoseconds, internalDuration.Time)); + return X(CreateTemporalInstant(ns)); +} diff --git a/src/abstract-ops/temporal/not-implemented.mts b/src/abstract-ops/temporal/not-implemented.mts new file mode 100644 index 0000000..2fc3561 --- /dev/null +++ b/src/abstract-ops/temporal/not-implemented.mts @@ -0,0 +1,15 @@ +export function mark_TimeZoneAwareNotImplemented() { + 'Time zone aware operations are not implemented in this engine.'; +} + +export function mark_OtherCalendarNotImplemented() { + 'Other calendar than iso8601 are not implemented in this engine.'; +} + +export function unreachable_OtherCalendarNotImplemented(): never { + throw new Error('Calendar other than ISO8601 is not supported, but this error should never triggered by the user code.'); +} + +export function temporal_todo(): never { + throw new Error('This Temporal operation is not implemented yet.'); +} diff --git a/src/abstract-ops/temporal/now.mts b/src/abstract-ops/temporal/now.mts new file mode 100644 index 0000000..4c6f3d9 --- /dev/null +++ b/src/abstract-ops/temporal/now.mts @@ -0,0 +1,37 @@ +import type { ISODateTimeRecord } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { SystemTimeZoneIdentifier } from './addition.mts'; +import { temporal_todo } from './not-implemented.mts'; +import { + ObjectValue, GetGlobalObject, Value, type PlainCompletion, Q, ToTemporalTimeZoneIdentifier, GetISODateTimeFor, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-hostsystemutcepochnanoseconds */ +export function HostSystemUTCEpochNanoseconds(_global: ObjectValue): number { + temporal_todo(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochmilliseconds */ +export function SystemUTCEpochMilliseconds(): number { + const global = GetGlobalObject(); + const nowNs = HostSystemUTCEpochNanoseconds(global); + return Math.floor(nowNs / (10 ** 6)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochnanoseconds */ +export function SystemUTCEpochNanoseconds(): bigint { + const global = GetGlobalObject(); + const nowNs = HostSystemUTCEpochNanoseconds(global); + return BigInt(nowNs); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-systemdatetime */ +export function SystemDateTime(temporalTimeZoneLike: Value): PlainCompletion { + let timeZone; + if (temporalTimeZoneLike === Value.undefined) { + timeZone = SystemTimeZoneIdentifier(); + } else { + timeZone = Q(ToTemporalTimeZoneIdentifier(temporalTimeZoneLike)); + } + const epochNs = SystemUTCEpochNanoseconds(); + return GetISODateTimeFor(timeZone, epochNs); +} diff --git a/src/abstract-ops/temporal/plain-date-time.mts b/src/abstract-ops/temporal/plain-date-time.mts new file mode 100644 index 0000000..c86dfe4 --- /dev/null +++ b/src/abstract-ops/temporal/plain-date-time.mts @@ -0,0 +1,235 @@ +import type { TemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import { type ISODateRecord, isTemporalPlainDateObject } from '../../intrinsics/Temporal/PlainDate.mts'; +import { type ISODateTimeRecord, type TemporalPlainDateTimeObject, isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { ParseISODateTime } from '../../parser/TemporalParser.mts'; +import { abs } from '../math.mts'; +import { + GetOptionsObject, + GetUTCEpochNanoseconds, ToZeroPaddedDecimalString, type RoundingMode, +} from './addition.mts'; +import { + CreateISODateRecord, R, YearFromTime, MonthFromTime, DateFromTime, CreateTimeRecord, HourFromTime, MinFromTime, SecFromTime, msFromTime, type TimeRecord, ISODateToEpochDays, nsMinInstant, nsPerDay, nsMaxInstant, type CalendarType, type CalendarFieldsRecord, type PlainEvaluator, Q, CalendarDateFromFields, RegulateTime, Value, ObjectValue, GetTemporalOverflowOption, X, GetISODateTimeFor, MidnightTimeRecord, GetTemporalCalendarIdentifierWithISODefault, PrepareCalendarFields, JSStringValue, Throw, CanonicalizeCalendar, BalanceTime, AddDaysToISODate, type FunctionObject, surroundingAgent, OrdinaryCreateFromConstructor, type Mutable, PadISOYear, FormatTimeString, FormatCalendarAnnotation, CompareISODate, CompareTimeRecord, type TimeUnit, TemporalUnit, Assert, RoundTime, type InternalDurationRecord, DifferenceTime, TimeDurationSign, Add24HourDaysToTimeDuration, LargerOfTwoTemporalUnits, CalendarDateUntil, type DateUnit, CombineDateAndTimeDuration, type PlainCompletion, ZeroDateDuration, type TimeDuration, RoundRelativeDuration, TotalRelativeDuration, type ValueEvaluator, CalendarEquals, GetDifferenceSettings, CreateTemporalDuration, TemporalDurationFromInternal, CreateNegatedTemporalDuration, ToTemporalDuration, ToInternalDurationRecordWith24HourDays, AddTime, AdjustDateDurationRecord, CalendarDateAdd, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-timevaluetoisodatetimerecord */ +export function TimeValueToISODateTimeRecord(t: number): ISODateTimeRecord { + const isoDate = CreateISODateRecord( + R(YearFromTime(t)), + R(MonthFromTime(t)) + 1, + R(DateFromTime(t)), + ); + const time = CreateTimeRecord(R(HourFromTime(t)), R(MinFromTime(t)), R(SecFromTime(t)), R(msFromTime(t)), 0, 0); + return { ISODate: isoDate, Time: time }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-combineisodateandtimerecord */ +export function CombineISODateAndTimeRecord(isoDate: ISODateRecord, time: TimeRecord): ISODateTimeRecord { + return { ISODate: isoDate, Time: time }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodatetimewithinlimits */ +export function ISODateTimeWithinLimits(isoDateTime: ISODateTimeRecord): boolean { + if (abs(ISODateToEpochDays(isoDateTime.ISODate.Year, isoDateTime.ISODate.Month - 1, isoDateTime.ISODate.Day)) > 1e8 + 1) { + return false; + } + const ns = GetUTCEpochNanoseconds(isoDateTime); + if (ns <= nsMinInstant - nsPerDay) { + return false; + } + if (ns >= nsMaxInstant + nsPerDay) { + return false; + } + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-interprettemporaldatetimefields */ +export function* InterpretTemporalDateTimeFields(calendar: CalendarType, fields: CalendarFieldsRecord, overflow: 'constrain' | 'reject'): PlainEvaluator { + const isoDate = Q(yield* CalendarDateFromFields(calendar, fields, overflow)); + const time = Q(RegulateTime(fields.Hour!, fields.Minute!, fields.Second!, fields.Millisecond!, fields.Microsecond!, fields.Nanosecond!, overflow)); + return CombineISODateAndTimeRecord(isoDate, time); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetime */ +export function* ToTemporalDateTime(item: Value, options: Value = Value.undefined): PlainEvaluator { + if (item instanceof ObjectValue) { + if (isTemporalPlainDateTimeObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalDateTime(item.ISODateTime, item.Calendar)); + } + if (isTemporalZonedDateTimeObject(item)) { + const isoDateTime = GetISODateTimeFor(item.TimeZone, item.EpochNanoseconds); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalDateTime(isoDateTime, item.Calendar)); + } + if (isTemporalPlainDateObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDateTime = CombineISODateAndTimeRecord(item.ISODate, MidnightTimeRecord()); + return Q(yield* CreateTemporalDateTime(isoDateTime, item.Calendar)); + } + const calendar = Q(yield* GetTemporalCalendarIdentifierWithISODefault(item)); + const fields = Q(yield* PrepareCalendarFields(calendar, item, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'], [])); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const result = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, overflow)); + return Q(yield* CreateTemporalDateTime(result, calendar)); + } + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('$1 is not a string', item); + } + const result = Q(ParseISODateTime(item.stringValue(), ['TemporalDateTimeString[~Zoned]'])); + const time = result.Time === 'start-of-day' ? MidnightTimeRecord() : result.Time; + const calendar = result.Calendar ?? 'iso8601'; + const calendarType = Q(CanonicalizeCalendar(calendar)); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = CreateISODateRecord(result.Year!, result.Month, result.Day); + const isoDateTime = CombineISODateAndTimeRecord(isoDate, time); + return Q(yield* CreateTemporalDateTime(isoDateTime, calendarType)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-balanceisodatetime */ +export function BalanceISODateTime(year: number, month: number, day: number, hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number): ISODateTimeRecord { + const balancedTime = BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond); + const balancedDate = AddDaysToISODate(CreateISODateRecord(year, month, day), balancedTime.Days); + return CombineISODateAndTimeRecord(balancedDate, balancedTime); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldatetime */ +export function* CreateTemporalDateTime(isoDateTime: ISODateTimeRecord, calendar: CalendarType, newTarget?: FunctionObject): PlainEvaluator { + if (!ISODateTimeWithinLimits(isoDateTime)) { + return Throw.RangeError('PlainDateTime outside of range'); + } + if (newTarget === undefined) { + newTarget = surroundingAgent.intrinsic('%Temporal.PlainDateTime%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.PlainDateTime.prototype%', [ + 'InitializedTemporalDateTime', + 'ISODateTime', + 'Calendar', + ])) as Mutable; + object.ISODateTime = isoDateTime; + object.Calendar = calendar; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodatetimetostring */ +export function ISODateTimeToString(isoDateTime: ISODateTimeRecord, calendar: CalendarType, precision: number | 'minute' | 'auto', showCalendar: 'auto' | 'always' | 'never' | 'critical'): string { + const yearString = PadISOYear(isoDateTime.ISODate.Year); + const monthString = ToZeroPaddedDecimalString(isoDateTime.ISODate.Month, 2); + const dayString = ToZeroPaddedDecimalString(isoDateTime.ISODate.Day, 2); + const subSecondNanoseconds = isoDateTime.Time.Millisecond * 1e6 + isoDateTime.Time.Microsecond * 1e3 + isoDateTime.Time.Nanosecond; + const timeString = FormatTimeString(isoDateTime.Time.Hour, isoDateTime.Time.Minute, isoDateTime.Time.Second, subSecondNanoseconds, precision); + const calendarString = FormatCalendarAnnotation(calendar, showCalendar); + return `${yearString}-${monthString}-${dayString}T${timeString}${calendarString}`; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-compareisodatetime */ +export function CompareISODateTime(isoDateTime1: ISODateTimeRecord, isoDateTime2: ISODateTimeRecord): 1 | -1 | 0 { + const dateResult = CompareISODate(isoDateTime1.ISODate, isoDateTime2.ISODate); + if (dateResult !== 0) { + return dateResult; + } + return CompareTimeRecord(isoDateTime1.Time, isoDateTime2.Time); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-roundisodatetime */ +export function RoundISODateTime(isoDateTime: ISODateTimeRecord, increment: number, unit: TimeUnit | TemporalUnit.Day, roundingMode: RoundingMode): ISODateTimeRecord { + Assert(ISODateTimeWithinLimits(isoDateTime)); + const roundedTime = RoundTime(isoDateTime.Time, increment, unit, roundingMode); + const balanceResult = AddDaysToISODate(isoDateTime.ISODate, roundedTime.Days); + return CombineISODateAndTimeRecord(balanceResult, roundedTime); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differenceisodatetime */ +export function DifferenceISODateTime(isoDateTime1: ISODateTimeRecord, isoDateTime2: ISODateTimeRecord, calendar: CalendarType, largestUnit: TemporalUnit): InternalDurationRecord { + Assert(ISODateTimeWithinLimits(isoDateTime1)); + Assert(ISODateTimeWithinLimits(isoDateTime2)); + let timeDuration = DifferenceTime(isoDateTime1.Time, isoDateTime2.Time); + const timeSign = TimeDurationSign(timeDuration); + const dateSign = CompareISODate(isoDateTime1.ISODate, isoDateTime2.ISODate); + let adjustedDate = isoDateTime2.ISODate; + if (timeSign === dateSign) { + adjustedDate = AddDaysToISODate(adjustedDate, timeSign); + timeDuration = X(Add24HourDaysToTimeDuration(timeDuration, -timeSign)); + } + const dateLargestUnit = LargerOfTwoTemporalUnits(TemporalUnit.Day, largestUnit); + const dateDifference = CalendarDateUntil(calendar, isoDateTime1.ISODate, adjustedDate, dateLargestUnit as DateUnit); + if (largestUnit !== dateLargestUnit) { + timeDuration = X(Add24HourDaysToTimeDuration(timeDuration, dateDifference.Days)); + dateDifference.Days = 0; + } + return CombineDateAndTimeDuration(dateDifference, timeDuration); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differenceplaindatetimewithrounding */ +export function DifferencePlainDateTimeWithRounding(isoDateTime1: ISODateTimeRecord, isoDateTime2: ISODateTimeRecord, calendar: CalendarType, largestUnit: TemporalUnit, roundingIncrement: number, smallestUnit: TemporalUnit, roundingMode: RoundingMode): PlainCompletion { + if (CompareISODateTime(isoDateTime1, isoDateTime2) === 0) { + return CombineDateAndTimeDuration(ZeroDateDuration(), 0 as TimeDuration); + } + if (!ISODateTimeWithinLimits(isoDateTime1) || !ISODateTimeWithinLimits(isoDateTime2)) { + return Throw.RangeError('PlainDateTime outside of range'); + } + const diff = DifferenceISODateTime(isoDateTime1, isoDateTime2, calendar, largestUnit); + if (smallestUnit === TemporalUnit.Nanosecond && roundingIncrement === 1) { + return diff; + } + const originEpochNs = GetUTCEpochNanoseconds(isoDateTime1); + const destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); + return RoundRelativeDuration(diff, originEpochNs, destEpochNs, isoDateTime1, undefined, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differenceplaindatetimewithtotal */ +export function DifferencePlainDateTimeWithTotal(isoDateTime1: ISODateTimeRecord, isoDateTime2: ISODateTimeRecord, calendar: CalendarType, unit: TemporalUnit): PlainCompletion { + if (CompareISODateTime(isoDateTime1, isoDateTime2) === 0) { + return 0; + } + if (!ISODateTimeWithinLimits(isoDateTime1) || !ISODateTimeWithinLimits(isoDateTime2)) { + return Throw.RangeError('PlainDateTime outside of range'); + } + const diff = DifferenceISODateTime(isoDateTime1, isoDateTime2, calendar, unit); + if (unit === TemporalUnit.Nanosecond) { + return diff.Time; + } + const originEpochNs = GetUTCEpochNanoseconds(isoDateTime1); + const destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); + return TotalRelativeDuration(diff, originEpochNs, destEpochNs, isoDateTime1, undefined, calendar, unit); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindatetime */ +export function* DifferenceTemporalPlainDateTime(operation: 'since' | 'until', dateTime: TemporalPlainDateTimeObject, _other: Value, options: Value): ValueEvaluator { + const other = Q(yield* ToTemporalDateTime(_other)); + if (!CalendarEquals(dateTime.Calendar, other.Calendar)) { + return Throw.RangeError('Calendars are not equal'); + } + const resolvedOptions = Q(GetOptionsObject(options)); + const settings = Q(yield* GetDifferenceSettings(operation, resolvedOptions, 'datetime', [], TemporalUnit.Nanosecond, TemporalUnit.Day)); + if (CompareISODateTime(dateTime.ISODateTime, other.ISODateTime) === 0) { + return X(CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + } + const internalDuration = Q(DifferencePlainDateTimeWithRounding(dateTime.ISODateTime, other.ISODateTime, dateTime.Calendar, settings.LargestUnit, settings.RoundingIncrement, settings.SmallestUnit, settings.RoundingMode)); + let result = X(TemporalDurationFromInternal(internalDuration, settings.LargestUnit)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodatetime */ +export function* AddDurationToDateTime(operation: 'add' | 'subtract', dateTime: TemporalPlainDateTimeObject, temporalDurationLike: Value, options: Value): ValueEvaluator { + let duration = Q(yield* ToTemporalDuration(temporalDurationLike)); + if (operation === 'subtract') { + duration = CreateNegatedTemporalDuration(duration); + } + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const internalDuration = ToInternalDurationRecordWith24HourDays(duration); + const timeResult = AddTime(dateTime.ISODateTime.Time, internalDuration.Time); + const dateDuration = Q(AdjustDateDurationRecord(internalDuration.Date, timeResult.Days)); + const addedDate = Q(CalendarDateAdd(dateTime.Calendar, dateTime.ISODateTime.ISODate, dateDuration, overflow)); + const result = CombineISODateAndTimeRecord(addedDate, timeResult); + return Q(yield* CreateTemporalDateTime(result, dateTime.Calendar)); +} diff --git a/src/abstract-ops/temporal/plain-date.mts b/src/abstract-ops/temporal/plain-date.mts new file mode 100644 index 0000000..3bbe2fa --- /dev/null +++ b/src/abstract-ops/temporal/plain-date.mts @@ -0,0 +1,241 @@ +import type { TemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import { type ISODateRecord, type TemporalPlainDateObject, isTemporalPlainDateObject } from '../../intrinsics/Temporal/PlainDate.mts'; +import { isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { ParseISODateTime } from '../../parser/TemporalParser.mts'; +import { abs } from '../math.mts'; +import { GetOptionsObject, GetUTCEpochNanoseconds, ToZeroPaddedDecimalString } from './addition.mts'; +import { + Assert, type CalendarType, type FunctionObject, type ValueEvaluator, Throw, surroundingAgent, Q, OrdinaryCreateFromConstructor, type Mutable, Value, ObjectValue, GetTemporalOverflowOption, X, GetISODateTimeFor, GetTemporalCalendarIdentifierWithISODefault, PrepareCalendarFields, CalendarDateFromFields, JSStringValue, CanonicalizeCalendar, CalendarISOToDate, type PlainCompletion, ISODaysInMonth, ISODateToEpochDays, EpochDaysToEpochMs, EpochTimeToEpochYear, EpochTimeToMonthInYear, EpochTimeToDate, FormatCalendarAnnotation, CalendarEquals, GetDifferenceSettings, TemporalUnit, CreateTemporalDuration, CalendarDateUntil, type DateUnit, CombineDateAndTimeDuration, type TimeDuration, RoundRelativeDuration, TemporalDurationFromInternal, CreateNegatedTemporalDuration, ToTemporalDuration, ToDateDurationRecordWithoutTime, CalendarDateAdd, + BalanceISOYearMonth, + MidnightTimeRecord, + NoonTimeRecord, + CombineISODateAndTimeRecord, + ISODateTimeWithinLimits, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-create-iso-date-record */ +export function CreateISODateRecord(y: number, m: number, d: number): ISODateRecord { + Assert(IsValidISODate(y, m, d)); + return { Year: y, Month: m, Day: d }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldate */ +export function* CreateTemporalDate(isoDate: ISODateRecord, calendar: CalendarType, NewTarget?: FunctionObject): ValueEvaluator { + if (!ISODateWithinLimits(isoDate)) { + return Throw.RangeError('$1-$2-$3 is not a valid date', isoDate.Year, isoDate.Month, isoDate.Day); + } + if (NewTarget === undefined) { + NewTarget = surroundingAgent.intrinsic('%Temporal.PlainDate%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Temporal.PlainDate.prototype%', [ + 'InitializedTemporalDate', + 'ISODate', + 'Calendar', + ])) as Mutable; + object.ISODate = isoDate; + object.Calendar = calendar; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporaldate */ +export function* ToTemporalDate(item: Value, options: Value = Value.undefined): ValueEvaluator { + if (item instanceof ObjectValue) { + if (isTemporalPlainDateObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalDate(item.ISODate, item.Calendar)); + } + if (isTemporalZonedDateTimeObject(item)) { + const isoDateTime = GetISODateTimeFor(item.TimeZone, item.EpochNanoseconds); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalDate(isoDateTime.ISODate, item.Calendar)); + } + if (isTemporalPlainDateTimeObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalDate(item.ISODateTime.ISODate, item.Calendar)); + } + const calendar = Q(yield* GetTemporalCalendarIdentifierWithISODefault(item)); + const fields = Q(yield* PrepareCalendarFields(calendar, item, ['year', 'month', 'month-code', 'day'], [], [])); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = Q(yield* CalendarDateFromFields(calendar, fields, overflow)); + return X(CreateTemporalDate(isoDate, calendar)); + } + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('$1 is not a string', item); + } + const result = Q(ParseISODateTime(item.stringValue(), ['TemporalDateTimeString[~Zoned]'])); + const calendar = result.Calendar ?? 'iso8601'; + const calendarType = Q(CanonicalizeCalendar(calendar)); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = CreateISODateRecord(result.Year!, result.Month, result.Day); + return X(CreateTemporalDate(isoDate, calendarType)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-comparesurpasses */ +export function CompareSurpasses(sign: 1 | -1, year: number, monthOrCode: number | string, day: number, target: { Year: number; Month: number; MonthCode: string; Day: number }): boolean { + if (year !== target.Year) { + if (sign * (year - target.Year) > 0) { + return true; + } + } else if (typeof monthOrCode === 'string' && monthOrCode !== target.MonthCode) { + if (sign > 0) { + // If monthOrCode is lexicographically greater than target.[[MonthCode]], return true. + if (monthOrCode > target.MonthCode) { + return true; + } + } else if (target.MonthCode > monthOrCode) { + // If target.[[MonthCode]] is lexicographically greater than monthOrCode, return true. + return true; + } + } else if (typeof monthOrCode === 'number' && monthOrCode !== target.Month) { + if (sign * (monthOrCode - target.Month) > 0) { + return true; + } + } else if (day !== target.Day) { + if (sign * (day - target.Day) > 0) { + return true; + } + } + return false; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses */ +export function ISODateSurpasses(sign: 1 | -1, baseDate: ISODateRecord, isoDate2: ISODateRecord, years: number, month: number, weeks: number, days: number): boolean { + const parts = CalendarISOToDate('iso8601', baseDate); + const target = CalendarISOToDate('iso8601', isoDate2); + const y0 = parts.Year + years; + if (CompareSurpasses(sign, y0, parts.MonthCode, parts.Day, target)) { + return true; + } + if (month === 0) { + return false; + } + const m0 = parts.Month + month; + const monthsAdded = BalanceISOYearMonth(y0, m0); + if (CompareSurpasses(sign, monthsAdded.Year, monthsAdded.Month, parts.Day, target)) { + return true; + } + if (weeks === 0 && days === 0) { + return false; + } + const regulatedDate = X(RegulateISODate(monthsAdded.Year, monthsAdded.Month, parts.Day, 'constrain')); + const daysInWeek = 7; + const balancedDate = AddDaysToISODate(regulatedDate, daysInWeek * weeks + days); + return CompareSurpasses(sign, balancedDate.Year, balancedDate.Month, balancedDate.Day, target); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate */ +export function RegulateISODate(year: number, month: number, day: number, overflow: 'constrain' | 'reject'): PlainCompletion { + if (overflow === 'constrain') { + month = Math.max(1, Math.min(12, month)); + const daysInMonth = ISODaysInMonth(year, month); + day = Math.max(1, Math.min(daysInMonth, day)); + } else { + Assert(overflow === 'reject'); + if (!IsValidISODate(year, month, day)) { + return Throw.RangeError('$1-$2-$3 is not a valid date', year, month, day); + } + } + return CreateISODateRecord(year, month, day); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate */ +export function IsValidISODate(year: number, month: number, day: number): boolean { + if (month < 1 || month > 12) { + return false; + } + const daysInMonth = ISODaysInMonth(year, month); + if (day < 1 || day > daysInMonth) { + return false; + } + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddaystoisodate */ +export function AddDaysToISODate(isoDate: ISODateRecord, days: number): ISODateRecord { + const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day) + days; + const ms = EpochDaysToEpochMs(epochDays, 0); + return CreateISODateRecord(EpochTimeToEpochYear(ms), EpochTimeToMonthInYear(ms) + 1, EpochTimeToDate(ms)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-padisoyear */ +export function PadISOYear(y: number): string { + if (y >= 0 && y <= 9999) { + return ToZeroPaddedDecimalString(y, 4); + } + const yearSign = y > 0 ? '+' : '-'; + const year = ToZeroPaddedDecimalString(abs(y), 6); + return yearSign + year; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetostring */ +export function TemporalDateToString(temporalDate: TemporalPlainDateObject, showCalendar: 'auto' | 'always' | 'never' | 'critical'): string { + const year = PadISOYear(temporalDate.ISODate.Year); + const month = ToZeroPaddedDecimalString(temporalDate.ISODate.Month, 2); + const day = ToZeroPaddedDecimalString(temporalDate.ISODate.Day, 2); + const calendar = FormatCalendarAnnotation(temporalDate.Calendar, showCalendar); + return `${year}-${month}-${day}${calendar}`; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits */ +export function ISODateWithinLimits(isoDate: ISODateRecord): boolean { + const isoDateTime = CombineISODateAndTimeRecord(isoDate, NoonTimeRecord()); + return ISODateTimeWithinLimits(isoDateTime); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-compareisodate */ +export function CompareISODate(isoDate1: ISODateRecord, isoDate2: ISODateRecord): 1 | -1 | 0 { + if (isoDate1.Year > isoDate2.Year) return 1; + if (isoDate1.Year < isoDate2.Year) return -1; + if (isoDate1.Month > isoDate2.Month) return 1; + if (isoDate1.Month < isoDate2.Month) return -1; + if (isoDate1.Day > isoDate2.Day) return 1; + if (isoDate1.Day < isoDate2.Day) return -1; + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindate */ +export function* DifferenceTemporalPlainDate(operation: 'since' | 'until', temporalDate: TemporalPlainDateObject, _other: Value, options: Value): ValueEvaluator { + const other = Q(yield* ToTemporalDate(_other)); + if (!CalendarEquals(temporalDate.Calendar, other.Calendar)) { + return Throw.RangeError('Calendars are not equal'); + } + const resolvedOptions = Q(GetOptionsObject(options)); + const settings = Q(yield* GetDifferenceSettings(operation, resolvedOptions, 'date', [], TemporalUnit.Day, TemporalUnit.Day)); + if (CompareISODate(temporalDate.ISODate, other.ISODate) === 0) { + return X(CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + } + const dateDifference = CalendarDateUntil(temporalDate.Calendar, temporalDate.ISODate, other.ISODate, settings.LargestUnit as DateUnit); + let duration = CombineDateAndTimeDuration(dateDifference, 0 as TimeDuration); + if (settings.SmallestUnit !== TemporalUnit.Day || settings.RoundingIncrement !== 1) { + const isoDateTime = CombineISODateAndTimeRecord(temporalDate.ISODate, MidnightTimeRecord()); + const originEpochNs = GetUTCEpochNanoseconds(isoDateTime); + const isoDateTimeOther = CombineISODateAndTimeRecord(other.ISODate, MidnightTimeRecord()); + const destEpochNs = GetUTCEpochNanoseconds(isoDateTimeOther); + duration = Q(RoundRelativeDuration(duration, originEpochNs, destEpochNs, isoDateTime, undefined, temporalDate.Calendar, settings.LargestUnit, settings.RoundingIncrement, settings.SmallestUnit, settings.RoundingMode)); + } + let result = X(TemporalDurationFromInternal(duration, TemporalUnit.Day)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate */ +export function* AddDurationToDate(operation: 'add' | 'subtract', temporalDate: TemporalPlainDateObject, temporalDurationLike: Value, options: Value): ValueEvaluator { + const calendar = temporalDate.Calendar; + let duration = Q(yield* ToTemporalDuration(temporalDurationLike)); + if (operation === 'subtract') { + duration = CreateNegatedTemporalDuration(duration); + } + const dateDuration = ToDateDurationRecordWithoutTime(duration); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const result = Q(CalendarDateAdd(calendar, temporalDate.ISODate, dateDuration, overflow)); + return X(CreateTemporalDate(result, calendar)); +} diff --git a/src/abstract-ops/temporal/plain-month-day.mts b/src/abstract-ops/temporal/plain-month-day.mts new file mode 100644 index 0000000..80323eb --- /dev/null +++ b/src/abstract-ops/temporal/plain-month-day.mts @@ -0,0 +1,85 @@ +import type { ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts'; +import { type TemporalPlainMonthDayObject, isTemporalPlainMonthDayObject } from '../../intrinsics/Temporal/PlainMonthDay.mts'; +import { ParseISODateTime } from '../../parser/TemporalParser.mts'; +import { GetOptionsObject, ToZeroPaddedDecimalString } from './addition.mts'; +import { + Value, type ValueEvaluator, ObjectValue, Q, GetTemporalOverflowOption, X, GetTemporalCalendarIdentifierWithISODefault, PrepareCalendarFields, CalendarMonthDayFromFields, JSStringValue, Throw, CanonicalizeCalendar, CreateISODateRecord, ISODateWithinLimits, ISODateToFields, type CalendarType, type FunctionObject, surroundingAgent, OrdinaryCreateFromConstructor, type Mutable, PadISOYear, FormatCalendarAnnotation, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalmonthday */ +export function* ToTemporalMonthDay( + item: Value, + options: Value = Value.undefined, +): ValueEvaluator { + if (item instanceof ObjectValue) { + if (isTemporalPlainMonthDayObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalMonthDay(item.ISODate, item.Calendar)); + } + const calendar = Q(yield* GetTemporalCalendarIdentifierWithISODefault(item)); + const fields = Q(yield* PrepareCalendarFields(calendar, item, ['year', 'month', 'month-code', 'day'], [], [])); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = Q(yield* CalendarMonthDayFromFields(calendar, fields, overflow)); + return X(CreateTemporalMonthDay(isoDate, calendar)); + } + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('$1 is not a string', item); + } + const result = Q(ParseISODateTime(item.stringValue(), ['TemporalMonthDayString'])); + const calendar = result.Calendar ?? 'iso8601'; + const calendarType = Q(CanonicalizeCalendar(calendar)); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + if (calendarType === 'iso8601') { + const referenceISOYear = 1972; + const isoDate = CreateISODateRecord(referenceISOYear, result.Month, result.Day); + return X(CreateTemporalMonthDay(isoDate, calendarType)); + } + let isoDate = CreateISODateRecord(result.Year!, result.Month, result.Day); + if (!ISODateWithinLimits(isoDate)) { + return Throw.RangeError('PlainMonthDay out of range'); + } + const result2 = Q(ISODateToFields(calendarType, isoDate, 'month-day')); + isoDate = Q(yield* CalendarMonthDayFromFields(calendarType, result2, 'constrain')); + return X(CreateTemporalMonthDay(isoDate, calendarType)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporalmonthday */ +export function* CreateTemporalMonthDay( + isoDate: ISODateRecord, + calendar: CalendarType, + newTarget?: FunctionObject, +): ValueEvaluator { + if (!ISODateWithinLimits(isoDate)) { + return Throw.RangeError('PlainMonthDay out of range'); + } + if (newTarget === undefined) { + newTarget = surroundingAgent.intrinsic('%Temporal.PlainMonthDay%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.PlainMonthDay.prototype%', [ + 'InitializedTemporalMonthDay', + 'ISODate', + 'Calendar', + ])) as Mutable; + object.ISODate = isoDate; + object.Calendar = calendar; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporalmonthdaytostring */ +export function TemporalMonthDayToString( + monthDay: TemporalPlainMonthDayObject, + showCalendar: 'auto' | 'always' | 'never' | 'critical', +): string { + const month = ToZeroPaddedDecimalString(monthDay.ISODate.Month, 2); + const day = ToZeroPaddedDecimalString(monthDay.ISODate.Day, 2); + let result = `${month}-${day}`; + if ((showCalendar === 'always' || showCalendar === 'critical') || monthDay.Calendar !== 'iso8601') { + const year = PadISOYear(monthDay.ISODate.Year); + result = `${year}-${result}`; + } + const calendarString = FormatCalendarAnnotation(monthDay.Calendar, showCalendar); + return result + calendarString; +} diff --git a/src/abstract-ops/temporal/plain-time.mts b/src/abstract-ops/temporal/plain-time.mts new file mode 100644 index 0000000..3ebe10a --- /dev/null +++ b/src/abstract-ops/temporal/plain-time.mts @@ -0,0 +1,326 @@ +import type { TemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import { isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { type TemporalPlainTimeObject, isTemporalPlainTimeObject } from '../../intrinsics/Temporal/PlainTime.mts'; +import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { ParseISODateTime } from '../../parser/TemporalParser.mts'; +import { abs } from '../math.mts'; +import { GetOptionsObject, type RoundingMode } from './addition.mts'; +import { + Assert, type TimeDuration, TimeDurationFromComponents, nsPerDay, Value, type ValueEvaluator, ObjectValue, Q, GetTemporalOverflowOption, X, GetISODateTimeFor, JSStringValue, Throw, type PlainEvaluator, UndefinedValue, type PlainCompletion, type FunctionObject, surroundingAgent, OrdinaryCreateFromConstructor, type Mutable, Get, ToIntegerWithTruncation, FormatTimeString, type TimeUnit, TemporalUnit, Table21_LengthInNanoSeconds, RoundNumberToIncrement, GetDifferenceSettings, RoundTimeDuration, CombineDateAndTimeDuration, ZeroDateDuration, TemporalDurationFromInternal, CreateNegatedTemporalDuration, ToTemporalDuration, ToInternalDurationRecord, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-time-records */ +export interface TimeRecord { + readonly Days: number; + readonly Hour: number; + readonly Minute: number; + readonly Second: number; + readonly Millisecond: number; + readonly Microsecond: number; + readonly Nanosecond: number; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtimerecord */ +export function CreateTimeRecord(hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number, deltaDays = 0): TimeRecord { + Assert(IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)); + return { + Days: deltaDays, + Hour: hour, + Minute: minute, + Second: second, + Millisecond: millisecond, + Microsecond: microsecond, + Nanosecond: nanosecond, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-midnighttimerecord */ +export function MidnightTimeRecord(): TimeRecord { + return { + Days: 0, + Hour: 0, + Minute: 0, + Second: 0, + Millisecond: 0, + Microsecond: 0, + Nanosecond: 0, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-noontimerecord */ +export function NoonTimeRecord(): TimeRecord { + return { + Days: 0, + Hour: 12, + Minute: 0, + Second: 0, + Millisecond: 0, + Microsecond: 0, + Nanosecond: 0, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetime */ +export function DifferenceTime(time1: TimeRecord, time2: TimeRecord): TimeDuration { + const hours = time2.Hour - time1.Hour; + const minutes = time2.Minute - time1.Minute; + const seconds = time2.Second - time1.Second; + const milliseconds = time2.Millisecond - time1.Millisecond; + const microseconds = time2.Microsecond - time1.Microsecond; + const nanoseconds = time2.Nanosecond - time1.Nanosecond; + const timeDuration = TimeDurationFromComponents(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + Assert(abs(timeDuration) < nsPerDay); + return timeDuration; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime */ +export function* ToTemporalTime(item: Value, options: Value = Value.undefined): ValueEvaluator { + let result; + if (item instanceof ObjectValue) { + if (isTemporalPlainTimeObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalTime(item.Time)); + } + if (isTemporalPlainDateTimeObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalTime(item.ISODateTime.Time)); + } + if (isTemporalZonedDateTimeObject(item)) { + const isoDateTime = GetISODateTimeFor(item.TimeZone, item.EpochNanoseconds); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalTime(isoDateTime.Time)); + } + const result2 = Q(yield* ToTemporalTimeRecord(item)); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + result = Q(RegulateTime(result2.Hour!, result2.Minute!, result2.Second!, result2.Millisecond!, result2.Microsecond!, result2.Nanosecond!, overflow)); + } else { + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('Invalid time string $1', item); + } + const parseResult = Q(ParseISODateTime(item.stringValue(), ['TemporalTimeString'])); + Assert(parseResult.Time !== 'start-of-day'); + result = parseResult.Time; + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + } + return X(CreateTemporalTime(result)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totimerecordormidnight */ +export function* ToTimeRecordOrMidnight(item: Value): PlainEvaluator { + if (item instanceof UndefinedValue) { + return MidnightTimeRecord(); + } + const plainTime = Q(yield* ToTemporalTime(item)); + return plainTime.Time; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-regulatetime */ +export function RegulateTime(hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number, overflow: 'constrain' | 'reject'): PlainCompletion { + if (overflow === 'constrain') { + hour = Math.max(0, Math.min(23, hour)); + minute = Math.max(0, Math.min(59, minute)); + second = Math.max(0, Math.min(59, second)); + millisecond = Math.max(0, Math.min(999, millisecond)); + microsecond = Math.max(0, Math.min(999, microsecond)); + nanosecond = Math.max(0, Math.min(999, nanosecond)); + } else { + Assert(overflow === 'reject'); + if (!IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)) { + return Throw.RangeError('Invalid time'); + } + } + return CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isvalidtime */ +export function IsValidTime(hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number): boolean { + if (hour < 0 || hour > 23) return false; + if (minute < 0 || minute > 59) return false; + if (second < 0 || second > 59) return false; + if (millisecond < 0 || millisecond > 999) return false; + if (microsecond < 0 || microsecond > 999) return false; + if (nanosecond < 0 || nanosecond > 999) return false; + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-balancetime */ +export function BalanceTime(hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number): TimeRecord { + microsecond += Math.floor(nanosecond / 1000); + nanosecond %= 1000; + millisecond += Math.floor(microsecond / 1000); + microsecond %= 1000; + second += Math.floor(millisecond / 1000); + millisecond %= 1000; + minute += Math.floor(second / 60); + second %= 60; + hour += Math.floor(minute / 60); + minute %= 60; + const deltaDays = Math.floor(hour / 24); + hour %= 24; + return CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond, deltaDays); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltime */ +export function* CreateTemporalTime(time: TimeRecord, newTarget?: FunctionObject): ValueEvaluator { + if (newTarget === undefined) { + newTarget = surroundingAgent.intrinsic('%Temporal.PlainTime%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.PlainTime.prototype%', [ + 'InitializedTemporalTime', + 'Time', + ])) as Mutable; + object.Time = time; + return object; +} + +/** https://tc39.es/proposal-temporal/#table-temporal-temporaltimelike-record-fields */ +export interface TemporalTimeLike { + Hour: number | undefined; + Minute: number | undefined; + Second: number | undefined; + Millisecond: number | undefined; + Microsecond: number | undefined; + Nanosecond: number | undefined; +} +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimerecord */ +export function* ToTemporalTimeRecord(temporalTimeLike: ObjectValue, completeness: 'partial' | 'complete' = 'complete'): PlainEvaluator { + const result: Mutable = { + Hour: undefined, + Minute: undefined, + Second: undefined, + Millisecond: undefined, + Microsecond: undefined, + Nanosecond: undefined, + }; + if (completeness === 'complete') { + result.Hour = 0; + result.Minute = 0; + result.Second = 0; + result.Millisecond = 0; + result.Microsecond = 0; + result.Nanosecond = 0; + } + let any = false; + const hour = Q(yield* Get(temporalTimeLike, Value('hour'))); + if (!(hour instanceof UndefinedValue)) { + result.Hour = Q(yield* ToIntegerWithTruncation(hour)); + any = true; + } + const microsecond = Q(yield* Get(temporalTimeLike, Value('microsecond'))); + if (!(microsecond instanceof UndefinedValue)) { + result.Microsecond = Q(yield* ToIntegerWithTruncation(microsecond)); + any = true; + } + const millisecond = Q(yield* Get(temporalTimeLike, Value('millisecond'))); + if (!(millisecond instanceof UndefinedValue)) { + result.Millisecond = Q(yield* ToIntegerWithTruncation(millisecond)); + any = true; + } + const minute = Q(yield* Get(temporalTimeLike, Value('minute'))); + if (!(minute instanceof UndefinedValue)) { + result.Minute = Q(yield* ToIntegerWithTruncation(minute)); + any = true; + } + const nanosecond = Q(yield* Get(temporalTimeLike, Value('nanosecond'))); + if (!(nanosecond instanceof UndefinedValue)) { + result.Nanosecond = Q(yield* ToIntegerWithTruncation(nanosecond)); + any = true; + } + const second = Q(yield* Get(temporalTimeLike, Value('second'))); + if (!(second instanceof UndefinedValue)) { + result.Second = Q(yield* ToIntegerWithTruncation(second)); + any = true; + } + if (!any) { + return Throw.TypeError('$1 does not look like a TemporalTimeLike object', temporalTimeLike); + } + return result; +} + + +/** https://tc39.es/proposal-temporal/#sec-temporal-timerecordtostring */ +export function TimeRecordToString(time: TimeRecord, precision: number | 'minute' | 'auto'): string { + const subSecondNanoseconds = time.Millisecond * 1e6 + time.Microsecond * 1e3 + time.Nanosecond; + return FormatTimeString(time.Hour, time.Minute, time.Second, subSecondNanoseconds, precision); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-comparetimerecord */ +export function CompareTimeRecord(time1: TimeRecord, time2: TimeRecord): -1 | 0 | 1 { + if (time1.Hour > time2.Hour) return 1; + if (time1.Hour < time2.Hour) return -1; + if (time1.Minute > time2.Minute) return 1; + if (time1.Minute < time2.Minute) return -1; + if (time1.Second > time2.Second) return 1; + if (time1.Second < time2.Second) return -1; + if (time1.Millisecond > time2.Millisecond) return 1; + if (time1.Millisecond < time2.Millisecond) return -1; + if (time1.Microsecond > time2.Microsecond) return 1; + if (time1.Microsecond < time2.Microsecond) return -1; + if (time1.Nanosecond > time2.Nanosecond) return 1; + if (time1.Nanosecond < time2.Nanosecond) return -1; + return 0; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-addtime */ +export function AddTime(time: TimeRecord, timeDuration: TimeDuration): TimeRecord { + return BalanceTime(time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond, time.Nanosecond + Number(timeDuration)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-roundtime */ +export function RoundTime(time: TimeRecord, increment: number, unit: TimeUnit | TemporalUnit.Day, roundingMode: RoundingMode): TimeRecord { + let quantity: number; + if (unit === TemporalUnit.Day || unit === TemporalUnit.Hour) { + quantity = (((((time.Hour * 60 + time.Minute) * 60 + time.Second) * 1000 + time.Millisecond) * 1000 + time.Microsecond) * 1000 + time.Nanosecond); + } else if (unit === TemporalUnit.Minute) { + quantity = ((((time.Minute * 60 + time.Second) * 1000 + time.Millisecond) * 1000 + time.Microsecond) * 1000 + time.Nanosecond); + } else if (unit === TemporalUnit.Second) { + quantity = (((time.Second * 1000 + time.Millisecond) * 1000 + time.Microsecond) * 1000 + time.Nanosecond); + } else if (unit === TemporalUnit.Millisecond) { + quantity = ((time.Millisecond * 1000 + time.Microsecond) * 1000 + time.Nanosecond); + } else if (unit === TemporalUnit.Microsecond) { + quantity = time.Microsecond * 1000 + time.Nanosecond; + } else { + Assert(unit === TemporalUnit.Nanosecond); + quantity = time.Nanosecond; + } + const unitLength = Table21_LengthInNanoSeconds[unit]; + const result = RoundNumberToIncrement(quantity, increment * unitLength, roundingMode) / unitLength; + if (unit === TemporalUnit.Day) return CreateTimeRecord(0, 0, 0, 0, 0, 0, result); + if (unit === TemporalUnit.Hour) return BalanceTime(result, 0, 0, 0, 0, 0); + if (unit === TemporalUnit.Minute) return BalanceTime(time.Hour, result, 0, 0, 0, 0); + if (unit === TemporalUnit.Second) return BalanceTime(time.Hour, time.Minute, result, 0, 0, 0); + if (unit === TemporalUnit.Millisecond) return BalanceTime(time.Hour, time.Minute, time.Second, result, 0, 0); + if (unit === TemporalUnit.Microsecond) return BalanceTime(time.Hour, time.Minute, time.Second, time.Millisecond, result, 0); + Assert(unit === TemporalUnit.Nanosecond); + return BalanceTime(time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond, result); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaintime */ +export function* DifferenceTemporalPlainTime(operation: 'since' | 'until', temporalTime: TemporalPlainTimeObject, _other: Value, options: Value): ValueEvaluator { + const other = Q(yield* ToTemporalTime(_other)); + const resolvedOptions = Q(GetOptionsObject(options)); + const settings = Q(yield* GetDifferenceSettings(operation, resolvedOptions, 'time', [], TemporalUnit.Nanosecond, TemporalUnit.Hour)); + let timeDuration = DifferenceTime(temporalTime.Time, other.Time); + // TODO(temporal): unsafe cast of settings.SmallestUnit + timeDuration = X(RoundTimeDuration(timeDuration, settings.RoundingIncrement, settings.SmallestUnit as TimeUnit, settings.RoundingMode)); + const duration = CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration); + let result = X(TemporalDurationFromInternal(duration, settings.LargestUnit)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurationtotime */ +export function* AddDurationToTime(operation: 'add' | 'subtract', temporalTime: TemporalPlainTimeObject, temporalDurationLike: Value): ValueEvaluator { + let duration = Q(yield* ToTemporalDuration(temporalDurationLike)); + if (operation === 'subtract') duration = CreateNegatedTemporalDuration(duration); + const internalDuration = ToInternalDurationRecord(duration); + const result = AddTime(temporalTime.Time, internalDuration.Time); + return X(CreateTemporalTime(result)); +} diff --git a/src/abstract-ops/temporal/plain-year-month.mts b/src/abstract-ops/temporal/plain-year-month.mts new file mode 100644 index 0000000..45adedf --- /dev/null +++ b/src/abstract-ops/temporal/plain-year-month.mts @@ -0,0 +1,193 @@ +import type { TemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import type { ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts'; +import { type TemporalPlainYearMonthObject, isTemporalPlainYearMonthObject, type ISOYearMonthRecord } from '../../intrinsics/Temporal/PlainYearMonth.mts'; +import { ParseISODateTime } from '../../parser/TemporalParser.mts'; +import { GetOptionsObject, GetUTCEpochNanoseconds, ToZeroPaddedDecimalString } from './addition.mts'; +import { + Value, type ValueEvaluator, ObjectValue, Q, GetTemporalOverflowOption, X, GetTemporalCalendarIdentifierWithISODefault, PrepareCalendarFields, CalendarYearMonthFromFields, JSStringValue, Throw, CanonicalizeCalendar, CreateISODateRecord, ISODateToFields, type CalendarType, type FunctionObject, surroundingAgent, OrdinaryCreateFromConstructor, type Mutable, PadISOYear, FormatCalendarAnnotation, CalendarEquals, GetDifferenceSettings, TemporalUnit, CompareISODate, CreateTemporalDuration, CalendarDateFromFields, CalendarDateUntil, type DateUnit, AdjustDateDurationRecord, CombineDateAndTimeDuration, type TimeDuration, RoundRelativeDuration, TemporalDurationFromInternal, CreateNegatedTemporalDuration, ToTemporalDuration, ToInternalDurationRecord, CalendarDateAdd, + CombineISODateAndTimeRecord, + MidnightTimeRecord, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth */ +export function* ToTemporalYearMonth( + item: Value, + options: Value = Value.undefined, +): ValueEvaluator { + if (item instanceof ObjectValue) { + if (isTemporalPlainYearMonthObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalYearMonth(item.ISODate, item.Calendar)); + } + const calendar = Q(yield* GetTemporalCalendarIdentifierWithISODefault(item)); + const fields = Q(yield* PrepareCalendarFields(calendar, item, ['year', 'month', 'month-code'], [], [])); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = Q(yield* CalendarYearMonthFromFields(calendar, fields, overflow)); + return X(CreateTemporalYearMonth(isoDate, calendar)); + } + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('$1 is not a string', item); + } + const result = Q(ParseISODateTime(item.stringValue(), ['TemporalYearMonthString'])); + const calendar = result.Calendar ?? 'iso8601'; + const calendarType = Q(CanonicalizeCalendar(calendar)); + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + let isoDate = CreateISODateRecord(result.Year!, result.Month, result.Day); + if (!ISOYearMonthWithinLimits(isoDate)) { + return Throw.RangeError('PlainYearMonth out of range'); + } + const result2 = ISODateToFields(calendarType, isoDate, 'year-month'); + isoDate = Q(yield* CalendarYearMonthFromFields(calendarType, result2, 'constrain')); + return X(CreateTemporalYearMonth(isoDate, calendarType)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthwithinlimits */ +export function ISOYearMonthWithinLimits( + isoDate: ISODateRecord, +): boolean { + if (isoDate.Year < -271821 || isoDate.Year > 275760) return false; + if (isoDate.Year === -271821 && isoDate.Month < 4) return false; + if (isoDate.Year === 275760 && isoDate.Month > 9) return false; + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth */ +export function BalanceISOYearMonth( + year: number, + month: number, +): ISOYearMonthRecord { + year += Math.floor((month - 1) / 12); + month = ((month - 1) % 12) + 1; + return { + Year: year, + Month: month, + }; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporalyearmonth */ +export function* CreateTemporalYearMonth( + isoDate: ISODateRecord, + calendar: CalendarType, + newTarget?: FunctionObject, +): ValueEvaluator { + if (!ISOYearMonthWithinLimits(isoDate)) { + return Throw.RangeError('PlainYearMonth out of range'); + } + if (newTarget === undefined) { + newTarget = surroundingAgent.intrinsic('%Temporal.PlainYearMonth%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.PlainYearMonth.prototype%', [ + 'InitializedTemporalYearMonth', + 'ISODate', + 'Calendar', + ])) as Mutable; + object.ISODate = isoDate; + object.Calendar = calendar; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporalyearmonthtostring */ +export function TemporalYearMonthToString( + yearMonth: TemporalPlainYearMonthObject, + showCalendar: 'auto' | 'always' | 'never' | 'critical', +): string { + const year = PadISOYear(yearMonth.ISODate.Year); + const month = ToZeroPaddedDecimalString(yearMonth.ISODate.Month, 2); + let result = `${year}-${month}`; + if (showCalendar === 'always' || showCalendar === 'critical' || yearMonth.Calendar !== 'iso8601') { + const day = ToZeroPaddedDecimalString(yearMonth.ISODate.Day, 2); + result = `${result}-${day}`; + } + const calendarString = FormatCalendarAnnotation(yearMonth.Calendar, showCalendar); + return result + calendarString; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplainyearmonth */ +export function* DifferenceTemporalPlainYearMonth( + operation: 'since' | 'until', + yearMonth: TemporalPlainYearMonthObject, + _other: Value, + options: Value, +): ValueEvaluator { + const other = Q(yield* ToTemporalYearMonth(_other)); + const calendar = yearMonth.Calendar; + if (!CalendarEquals(calendar, other.Calendar)) { + return Throw.RangeError('PlainYearMonth calendars do not match'); + } + const resolvedOptions = Q(GetOptionsObject(options)); + const settings = Q(yield* GetDifferenceSettings( + operation, + resolvedOptions, + 'date', + [TemporalUnit.Week, TemporalUnit.Day], + TemporalUnit.Month, + TemporalUnit.Year, + )); + if (CompareISODate(yearMonth.ISODate, other.ISODate) === 0) { + return X(CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + } + const thisFields = ISODateToFields(calendar, yearMonth.ISODate, 'year-month'); + thisFields.Day = 1; + const thisDate = Q(yield* CalendarDateFromFields(calendar, thisFields, 'constrain')); + const otherFields = ISODateToFields(calendar, other.ISODate, 'year-month'); + otherFields.Day = 1; + const otherDate = Q(yield* CalendarDateFromFields(calendar, otherFields, 'constrain')); + // TODO(temporal): unsafe cast of settings.LargestUnit + const dateDifference = CalendarDateUntil(calendar, thisDate, otherDate, settings.LargestUnit as DateUnit); + const yearsMonthsDifference = X(AdjustDateDurationRecord(dateDifference, 0, 0)); + let duration = CombineDateAndTimeDuration(yearsMonthsDifference, 0 as TimeDuration); + if (settings.SmallestUnit !== TemporalUnit.Month || settings.RoundingIncrement !== 1) { + const isoDateTime = CombineISODateAndTimeRecord(thisDate, MidnightTimeRecord()); + const originEpochNs = GetUTCEpochNanoseconds(isoDateTime); + const isoDateTimeOther = CombineISODateAndTimeRecord(otherDate, MidnightTimeRecord()); + const destEpochNs = GetUTCEpochNanoseconds(isoDateTimeOther); + duration = Q(RoundRelativeDuration( + duration, + originEpochNs, + destEpochNs, + isoDateTime, + undefined, + calendar, + settings.LargestUnit, + settings.RoundingIncrement, + settings.SmallestUnit, + settings.RoundingMode, + )); + } + let result = X(TemporalDurationFromInternal(duration, TemporalUnit.Day)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoyearmonth */ +export function* AddDurationToYearMonth( + operation: 'add' | 'subtract', + yearMonth: TemporalPlainYearMonthObject, + temporalDurationLike: Value, + options: Value, +): ValueEvaluator { + let duration = Q(yield* ToTemporalDuration(temporalDurationLike)); + if (operation === 'subtract') { + duration = CreateNegatedTemporalDuration(duration); + } + const internalDuration = ToInternalDurationRecord(duration); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const durationToAdd = internalDuration.Date; + if (durationToAdd.Weeks !== 0 || durationToAdd.Days !== 0 || internalDuration.Time !== 0) { + return Throw.RangeError('Invalid duration'); + } + const calendar = yearMonth.Calendar; + const fields = ISODateToFields(calendar, yearMonth.ISODate, 'year-month'); + fields.Day = 1; + const date = Q(yield* CalendarDateFromFields(calendar, fields, 'constrain')); + const addedDate = Q(CalendarDateAdd(calendar, date, durationToAdd, overflow)); + const addedDateFields = ISODateToFields(calendar, addedDate, 'year-month'); + const isoDate = Q(yield* CalendarYearMonthFromFields(calendar, addedDateFields, overflow)); + return X(CreateTemporalYearMonth(isoDate, calendar)); +} diff --git a/src/abstract-ops/temporal/temporal.mts b/src/abstract-ops/temporal/temporal.mts new file mode 100644 index 0000000..d26d630 --- /dev/null +++ b/src/abstract-ops/temporal/temporal.mts @@ -0,0 +1,877 @@ +import { ParseDateTimeUTCOffset, ParseISODateTime } from '../../parser/TemporalParser.mts'; +import { R } from '../spec-types.mjs'; +import { type ISODateRecord, type TemporalPlainDateObject, isTemporalPlainDateObject } from '../../intrinsics/Temporal/PlainDate.mts'; +import { isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { type TemporalZonedDateTimeObject, isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { + GetOption, GetRoundingIncrementOption, GetRoundingModeOption, ToZeroPaddedDecimalString, UnsignedRoundingMode, type TimeZoneIdentifier, +} from './addition.mts'; +import { RoundingMode } from './addition.mts'; +import { + CalendarISOToDate, CanonicalizeCalendar, GetTemporalCalendarIdentifierWithISODefault, PrepareCalendarFields, type CalendarFieldsRecord, type CalendarType, +} from './calendar.mts'; +import { ToTemporalTimeZoneIdentifier } from './time-zone.mts'; +import { + ToPrimitive, ToNumber, Throw, CreateISODateRecord, CreateTemporalDate, CreateTemporalZonedDateTime, InterpretISODateTimeOffset, InterpretTemporalDateTimeFields, nsPerDay, type ISODateTimeMatchBehaviour, type ISODateTimeOffsetBehaviour, + Value, ObjectValue, JSStringValue, NumberValue, UndefinedValue, Q, surroundingAgent, Get, ToString, type PlainCompletion, type PlainEvaluator, Assert, type PropertyKeyValue, X, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-isodatetoepochdays */ +// TODO(temporal): Review +export function ISODateToEpochDays(year: number, month: number, date: number): number { + const resolvedYear = year + Math.floor(month / 12); + const resolvedMonth = ((month % 12) + 12) % 12; + // Find a time t such that EpochTimeToEpochYear(t) = resolvedYear, EpochTimeToMonthInYear(t) = resolvedMonth, and EpochTimeToDate(t) = 1. + const y = resolvedYear; + const m = resolvedMonth; + let t = EpochDayNumberForYear(y); + const isLeap = MathematicalDaysInYear(y) === 366; + const monthDays = [ + 31, + isLeap ? 29 : 28, + 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31, + ]; + for (let i = 0; i < m; i += 1) { + t += monthDays[i]; + } + Assert(EpochTimeToEpochYear(t) === resolvedYear && EpochTimeToMonthInYear(t) === resolvedMonth && EpochTimeToDate(t) === 1); + + return EpochTimeToDayNumber(t) + date - 1; +} + +/** https://tc39.es/proposal-temporal/#sec-epochdaystoepochms */ +export function EpochDaysToEpochMs(day: number, time: number): number { + return day * 86400000 + time; +} + +/** https://tc39.es/proposal-temporal/#eqn-EpochTimeToDayNumber */ +export function EpochTimeToDayNumber(t: number): number { + return Math.floor(t / 86400000); +} + +/** https://tc39.es/proposal-temporal/#sec-mathematicaldaysinyear */ +export function MathematicalDaysInYear(y: number): number { + if (y % 4 !== 0) { + return 365; + } + if (y % 100 !== 0) { + return 366; + } + if (y % 400 !== 0) { + return 365; + } + return 366; +} + +/** https://tc39.es/proposal-temporal/#sec-epochdaynumberforyear */ +export function EpochDayNumberForYear(y: number): number { + return 365 * (y - 1970) + + Math.floor((y - 1969) / 4) + - Math.floor((y - 1901) / 100) + + Math.floor((y - 1601) / 400); +} + +/** https://tc39.es/proposal-temporal/#sec-epochtimeforyear */ +export function EpochTimeForYear(y: number): number { + return 86400000 * EpochDayNumberForYear(y); +} + +/** https://tc39.es/proposal-temporal/#sec-epochtimetoepochyear */ +// TODO(temporal): Review +export function EpochTimeToEpochYear(t: number): number { + // EpochTimeToEpochYear(t) = the largest integral Number y (closest to +∞) such that EpochTimeForYear(y) ≤ t + let lower = -271821; + let upper = 275760; + while (lower < upper) { + const mid = Math.floor((lower + upper + 1) / 2); + if (EpochTimeForYear(mid) <= t) { + lower = mid; + } else { + upper = mid - 1; + } + } + return lower; +} + +/** https://tc39.es/proposal-temporal/#sec-mathematicalinleapyear */ +export function MathematicalInLeapYear(t: number): number { + return MathematicalDaysInYear(EpochTimeToEpochYear(t)) === 366 ? 1 : 0; +} + +/** https://tc39.es/proposal-temporal/#sec-epochtimetomonthinyear */ +export function EpochTimeToMonthInYear(t: number): number { + const dayInYear = EpochTimeToDayInYear(t); + const leap = MathematicalInLeapYear(t); + if (dayInYear >= 0 && dayInYear < 31) return 0; + if (dayInYear >= 31 && dayInYear < 59 + leap) return 1; + if (59 + leap <= dayInYear && dayInYear < 90 + leap) return 2; + if (90 + leap <= dayInYear && dayInYear < 120 + leap) return 3; + if (120 + leap <= dayInYear && dayInYear < 151 + leap) return 4; + if (151 + leap <= dayInYear && dayInYear < 181 + leap) return 5; + if (181 + leap <= dayInYear && dayInYear < 212 + leap) return 6; + if (212 + leap <= dayInYear && dayInYear < 243 + leap) return 7; + if (243 + leap <= dayInYear && dayInYear < 273 + leap) return 8; + if (273 + leap <= dayInYear && dayInYear < 304 + leap) return 9; + if (304 + leap <= dayInYear && dayInYear < 334 + leap) return 10; + return 11; +} + +/** https://tc39.es/proposal-temporal/#sec-epochtimetodayinyear */ +export function EpochTimeToDayInYear(t: number): number { + return EpochTimeToDayNumber(t) - EpochDayNumberForYear(EpochTimeToEpochYear(t)); +} + +/** https://tc39.es/proposal-temporal/#sec-epochtimetodate */ +export function EpochTimeToDate(t: number): number { + const m = EpochTimeToMonthInYear(t); + const dayInYear = EpochTimeToDayInYear(t); + const leap = MathematicalInLeapYear(t) ? 1 : 0; + if (m === 0) return dayInYear + 1; + if (m === 1) return dayInYear - 30; + if (m === 2) return dayInYear - 58 - leap; + if (m === 3) return dayInYear - 89 - leap; + if (m === 4) return dayInYear - 119 - leap; + if (m === 5) return dayInYear - 150 - leap; + if (m === 6) return dayInYear - 180 - leap; + if (m === 7) return dayInYear - 211 - leap; + if (m === 8) return dayInYear - 242 - leap; + if (m === 9) return dayInYear - 272 - leap; + if (m === 10) return dayInYear - 303 - leap; + return dayInYear - 333 - leap; +} + +/** https://tc39.es/proposal-temporal/#sec-epochtimetoweekday */ +export function EpochTimeToWeekDay(t: number): number { + return (EpochTimeToDayNumber(t) + 4) % 7; +} + +/** https://tc39.es/proposal-temporal/#sec-checkisodaysrange */ +export function CheckISODaysRange(isoDate: ISODateRecord): PlainCompletion { + const days = Math.abs(ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day)); + if (days > 1e8) { + return Throw.RangeError('ISODate is out of range'); + } + return undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-units */ +export enum TemporalUnit { + Year, Month, Week, Day, + Hour, Minute, Second, Millisecond, Microsecond, Nanosecond +} + +/** https://tc39.es/proposal-temporal/#table-temporal-units */ +export type TimeUnit = TemporalUnit.Hour | TemporalUnit.Minute | TemporalUnit.Second | TemporalUnit.Millisecond | TemporalUnit.Microsecond | TemporalUnit.Nanosecond; + +export function __IsTimeUnit(unit: TemporalUnit): unit is TimeUnit { + return (unit === TemporalUnit.Hour + || unit === TemporalUnit.Minute + || unit === TemporalUnit.Second + || unit === TemporalUnit.Millisecond + || unit === TemporalUnit.Microsecond + || unit === TemporalUnit.Nanosecond + ); +} + +/** https://tc39.es/proposal-temporal/#table-temporal-units */ +export type DateUnit = TemporalUnit.Year | TemporalUnit.Month | TemporalUnit.Week | TemporalUnit.Day; + +/** https://tc39.es/proposal-temporal/#table-temporal-units */ +export const Table21_LengthInNanoSeconds = { + [TemporalUnit.Day]: 8.64e13 satisfies typeof nsPerDay, + [TemporalUnit.Hour]: 3.6e12, + [TemporalUnit.Minute]: 6e10, + [TemporalUnit.Second]: 1e9, + [TemporalUnit.Millisecond]: 1e6, + [TemporalUnit.Microsecond]: 1e3, + [TemporalUnit.Nanosecond]: 1, +} as const; + +export const Table21_CategoryByValue = { + [TemporalUnit.Year]: 'date', + [TemporalUnit.Month]: 'date', + [TemporalUnit.Week]: 'date', + [TemporalUnit.Day]: 'date', + [TemporalUnit.Hour]: 'time', + [TemporalUnit.Minute]: 'time', + [TemporalUnit.Second]: 'time', + [TemporalUnit.Millisecond]: 'time', + [TemporalUnit.Microsecond]: 'time', + [TemporalUnit.Nanosecond]: 'time', +} as const; + +export function __IsDateUnit(unit: TemporalUnit): unit is DateUnit { + return (unit === TemporalUnit.Year + || unit === TemporalUnit.Month + || unit === TemporalUnit.Week + || unit === TemporalUnit.Day + ); +} + +/** https://tc39.es/proposal-temporal/#sec-gettemporaloverflowoption */ +export function* GetTemporalOverflowOption(options: ObjectValue): PlainEvaluator<'constrain' | 'reject'> { + const stringValue = Q(yield* GetOption(options, 'overflow', 'string', ['constrain', 'reject'], 'constrain')); + if (stringValue === 'constrain') { + return 'constrain'; + } + return 'reject'; +} + +/** https://tc39.es/proposal-temporal/#sec-gettemporaldisambiguationoption */ +export function* GetTemporalDisambiguationOption(options: ObjectValue): PlainEvaluator<'compatible' | 'earlier' | 'later' | 'reject'> { + const stringValue = Q(yield* GetOption(options, 'disambiguation', 'string', ['compatible', 'earlier', 'later', 'reject'], 'compatible')); + if (stringValue === 'compatible') return 'compatible'; + if (stringValue === 'earlier') return 'earlier'; + if (stringValue === 'later') return 'later'; + return 'reject'; +} + +/** https://tc39.es/proposal-temporal/#sec-negateroundingmode */ +export function NegateRoundingMode(roundingMode: RoundingMode): RoundingMode { + switch (roundingMode) { + case RoundingMode.Ceil: return RoundingMode.Floor; + case RoundingMode.Floor: return RoundingMode.Ceil; + case RoundingMode.HalfCeil: return RoundingMode.HalfFloor; + case RoundingMode.HalfFloor: return RoundingMode.HalfCeil; + default: return roundingMode; + } +} + +export type TemporalOffsetOption = 'prefer' | 'use' | 'ignore' | 'reject'; +/** https://tc39.es/proposal-temporal/#sec-gettemporaloffsetoption */ +export function* GetTemporalOffsetOption(options: ObjectValue, fallback: TemporalOffsetOption): PlainEvaluator { + // step 1 to 4 + const stringFallback = fallback; + const stringValue = Q(yield* GetOption(options, 'offset', 'string', ['prefer', 'use', 'ignore', 'reject'], stringFallback)); + if (stringValue === 'prefer') return 'prefer'; + if (stringValue === 'use') return 'use'; + if (stringValue === 'ignore') return 'ignore'; + return 'reject'; +} + +export type ShowCalendarNameOption = 'auto' | 'always' | 'never' | 'critical'; +/** https://tc39.es/proposal-temporal/#sec-gettemporalshowcalendarnameoption */ +export function* GetTemporalShowCalendarNameOption(options: ObjectValue): PlainEvaluator { + const stringValue = Q(yield* GetOption(options, 'calendarName', 'string', ['auto', 'always', 'never', 'critical'], 'auto')); + if (stringValue === 'always') return 'always'; + if (stringValue === 'never') return 'never'; + if (stringValue === 'critical') return 'critical'; + return 'auto'; +} + +export type ShowTimeZoneNameOption = 'auto' | 'never' | 'critical'; +/** https://tc39.es/proposal-temporal/#sec-gettemporalshowtimezonenameoption */ +export function* GetTemporalShowTimeZoneNameOption(options: ObjectValue): PlainEvaluator { + const stringValue = Q(yield* GetOption(options, 'timeZoneName', 'string', ['auto', 'never', 'critical'], 'auto')); + if (stringValue === 'never') return 'never'; + if (stringValue === 'critical') return 'critical'; + return 'auto'; +} + +/** https://tc39.es/proposal-temporal/#sec-gettemporalshowoffsetoption */ +export function* GetTemporalShowOffsetOption(options: ObjectValue): PlainEvaluator<'auto' | 'never'> { + const stringValue = Q(yield* GetOption(options, 'offset', 'string', ['auto', 'never'], 'auto')); + if (stringValue === 'never') return 'never'; + return 'auto'; +} + +export type DirectionOption = 'next' | 'previous'; +/** https://tc39.es/proposal-temporal/#sec-getdirectionoption */ +export function* GetDirectionOption(options: ObjectValue): PlainEvaluator { + const stringValue = Q(yield* GetOption(options, 'direction', 'string', ['next', 'previous'], '~required~')); + if (stringValue === 'next') return 'next'; + return 'previous'; +} + +/** https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement */ +export function ValidateTemporalRoundingIncrement(increment: number, dividend: number, inclusive: boolean): PlainCompletion { + let maximum; + if (inclusive) { + maximum = dividend; + } else { + Assert(dividend > 1); + maximum = dividend - 1; + } + if (increment > maximum) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', increment); + } + if (dividend % increment !== 0) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', increment); + } + return undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-gettemporalfractionalseconddigitsoption */ +export function* GetTemporalFractionalSecondDigitsOption(options: ObjectValue): PlainEvaluator<'auto' | number> { + const digitsValue = Q(yield* Get(options, Value('fractionalSecondDigits'))); + if (digitsValue instanceof UndefinedValue) { + return 'auto'; + } + if (!(digitsValue instanceof NumberValue)) { + if (Q(yield* ToString(digitsValue)).stringValue() !== 'auto') { + return surroundingAgent.Throw('RangeError', 'OutOfRange', digitsValue); + } + return 'auto'; + } + if (digitsValue.isNaN() || digitsValue.isInfinity()) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', digitsValue); + } + const digitCount = Math.floor(R(digitsValue)); + if (digitCount < 0 || digitCount > 9) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', digitsValue); + } + return digitCount; +} + +/** https://tc39.es/proposal-temporal/#sec-tosecondsstringprecisionrecord */ +export function ToSecondsStringPrecisionRecord( + smallestUnit: Exclude | 'unset', + fractionalDigitCount: 'auto' | number, +): { + Precision: TemporalUnit.Minute | 'auto' | number, + Unit: TemporalUnit.Minute | TemporalUnit.Second | TemporalUnit.Millisecond | TemporalUnit.Microsecond | TemporalUnit.Nanosecond, + Increment: 1 | 10 | 100 +} { + if (smallestUnit === TemporalUnit.Minute) { + return { Precision: TemporalUnit.Minute, Unit: TemporalUnit.Minute, Increment: 1 }; + } + if (smallestUnit === TemporalUnit.Second) { + return { Precision: 0, Unit: TemporalUnit.Second, Increment: 1 }; + } + if (smallestUnit === TemporalUnit.Millisecond) { + return { Precision: 3, Unit: TemporalUnit.Millisecond, Increment: 1 }; + } + if (smallestUnit === TemporalUnit.Microsecond) { + return { Precision: 6, Unit: TemporalUnit.Microsecond, Increment: 1 }; + } + if (smallestUnit === TemporalUnit.Nanosecond) { + return { Precision: 9, Unit: TemporalUnit.Nanosecond, Increment: 1 }; + } + Assert(smallestUnit === 'unset'); + if (fractionalDigitCount === 'auto') { + return { Precision: 'auto', Unit: TemporalUnit.Nanosecond, Increment: 1 }; + } + if (fractionalDigitCount === 0) { + return { Precision: 0, Unit: TemporalUnit.Second, Increment: 1 }; + } + if (fractionalDigitCount >= 1 && fractionalDigitCount <= 3) { + return { Precision: fractionalDigitCount, Unit: TemporalUnit.Millisecond, Increment: 10 ** (3 - fractionalDigitCount) as 1 | 10 | 100 }; + } + if (fractionalDigitCount >= 4 && fractionalDigitCount <= 6) { + return { Precision: fractionalDigitCount, Unit: TemporalUnit.Microsecond, Increment: 10 ** (6 - fractionalDigitCount) as 1 | 10 | 100 }; + } + Assert(fractionalDigitCount >= 7 && fractionalDigitCount <= 9); + return { Precision: fractionalDigitCount, Unit: TemporalUnit.Nanosecond, Increment: 10 ** (9 - fractionalDigitCount) as 1 | 10 | 100 }; +} + +const table21 = [ + { + Value: TemporalUnit.Year, Singular: 'year', Plural: 'years', + }, + { + Value: TemporalUnit.Month, Singular: 'month', Plural: 'months', + }, + { + Value: TemporalUnit.Week, Singular: 'week', Plural: 'weeks', + }, + { + Value: TemporalUnit.Day, Singular: 'day', Plural: 'days', + }, + { + Value: TemporalUnit.Hour, Singular: 'hour', Plural: 'hours', + }, + { + Value: TemporalUnit.Minute, Singular: 'minute', Plural: 'minutes', + }, + { + Value: TemporalUnit.Second, Singular: 'second', Plural: 'seconds', + }, + { + Value: TemporalUnit.Millisecond, Singular: 'millisecond', Plural: 'milliseconds', + }, + { + Value: TemporalUnit.Microsecond, Singular: 'microsecond', Plural: 'microseconds', + }, + { + Value: TemporalUnit.Nanosecond, Singular: 'nanosecond', Plural: 'nanoseconds', + }, +] as const; +/** https://tc39.es/proposal-temporal/#sec-gettemporalunitvaluedoption */ +export function* GetTemporalUnitValuedOption( + options: ObjectValue, + key: PropertyKeyValue | string, + defaultV: 'required' | 'unset', +): PlainEvaluator { + // 1. Let allowedStrings be a List containing all values in the "Singular property name" and "Plural property name" columns of Table 21, except the header row. + const allowedStrings = table21.map((row) => row.Singular).concat(table21.map((row) => row.Plural)).concat('auto'); + const defaultValue = defaultV === 'unset' ? undefined : defaultV; + const value = Q(yield* GetOption(options, key, 'string', allowedStrings, defaultValue)); + if (value === undefined) { + return 'unset'; + } + if (value === 'auto') { + return 'auto'; + } + // 9. Return the value in the "Value" column of Table 21 corresponding to the row with value in its "Singular property name" or "Plural property name" column. + const returnValue = table21.find((row) => row.Singular === value || row.Plural === value)?.Value; + Assert(returnValue !== undefined); + return returnValue; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-validatetemporalunitvaluedoption */ +export function ValidateTemporalUnitValue(value: TemporalUnit | 'unset' | 'auto', unitGroup: 'date' | 'time' | 'datetime', extraValues?: Array): PlainCompletion { + if (value === 'unset') return undefined; + if (extraValues?.includes(value)) return undefined; + const category = Table21_CategoryByValue[value as TemporalUnit]; + if (!category) { + return Throw.RangeError('Invalid TemporalUnit value $1', value); + } + if (category === 'date' && (unitGroup === 'datetime' || unitGroup === 'date')) return undefined; + if (category === 'time' && (unitGroup === 'datetime' || unitGroup === 'time')) return undefined; + return Throw.RangeError('Invalid TemporalUnit value $1', value); +} + +/** https://tc39.es/proposal-temporal/#sec-gettemporalrelativetooption */ +export function* GetTemporalRelativeToOption(options: ObjectValue): PlainEvaluator<{ + PlainRelativeTo?: TemporalPlainDateObject, + ZonedRelativeTo?: TemporalZonedDateTimeObject, +}> { + const value = Q(yield* Get(options, Value('relativeTo'))); + if (value instanceof UndefinedValue) { + return { PlainRelativeTo: undefined, ZonedRelativeTo: undefined }; + } + let offsetBehaviour: ISODateTimeOffsetBehaviour = 'option'; + let matchBehaviour: ISODateTimeMatchBehaviour = 'match-exactly'; + let timeZone: TimeZoneIdentifier | 'unset'; + let isoDate; + let time; + let calendar: CalendarType | undefined; + let offsetString; + if (value instanceof ObjectValue) { + if (isTemporalZonedDateTimeObject(value)) { + return { PlainRelativeTo: undefined, ZonedRelativeTo: value }; + } + if (isTemporalPlainDateObject(value)) { + return { PlainRelativeTo: value, ZonedRelativeTo: undefined }; + } + if (isTemporalPlainDateTimeObject(value)) { + const plainDate = X(CreateTemporalDate(value.ISODateTime.ISODate, value.Calendar)); + return { PlainRelativeTo: plainDate, ZonedRelativeTo: undefined }; + } + calendar = Q(yield* GetTemporalCalendarIdentifierWithISODefault(value)); + const fields = Q(yield* PrepareCalendarFields(calendar, value, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset', 'time-zone'], [])); + const result = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, 'constrain')); + timeZone = fields.TimeZone as TimeZoneIdentifier; + offsetString = fields.OffsetString; + if (offsetString === undefined) { + offsetBehaviour = 'wall'; + } + isoDate = result.ISODate; + time = result.Time; + } else { + if (!(value instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', value); + } + const result = Q(ParseISODateTime(value.stringValue(), ['TemporalDateTimeString[+Zoned]', 'TemporalDateTimeString[~Zoned]'])); + offsetString = result.TimeZone.OffsetString; + const annotation = result.TimeZone.TimeZoneAnnotation; + if (!annotation) { + timeZone = 'unset'; + } else { + timeZone = Q(ToTemporalTimeZoneIdentifier(annotation)); + if (result.TimeZone.Z === true) { + offsetBehaviour = 'exact'; + } else if (!offsetString) { + offsetBehaviour = 'wall'; + } + matchBehaviour = 'match-minutes'; + } + let _calendar = result.Calendar; + if (!_calendar) { + _calendar = 'iso8601'; + } + calendar = Q(CanonicalizeCalendar(_calendar)); + isoDate = CreateISODateRecord(result.Year!, result.Month, result.Day); + time = result.Time; + } + if (timeZone === 'unset') { + const plainDate = Q(yield* CreateTemporalDate(isoDate, calendar)); + return { PlainRelativeTo: plainDate, ZonedRelativeTo: undefined }; + } + let offsetNs; + if (offsetBehaviour === 'option') { + offsetNs = X(ParseDateTimeUTCOffset(offsetString!)); + } else { + offsetNs = 0; + } + const epochNanoseconds = Q(InterpretISODateTimeOffset(isoDate, time, offsetBehaviour, offsetNs, timeZone, 'compatible', 'reject', matchBehaviour)); + const zonedRelativeTo = X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar)); + return { PlainRelativeTo: undefined, ZonedRelativeTo: zonedRelativeTo }; +} + +/** https://tc39.es/proposal-temporal/#sec-largeroftwotemporalunits */ +export function LargerOfTwoTemporalUnits(u1: TemporalUnit, u2: TemporalUnit): TemporalUnit { + const order = [ + TemporalUnit.Year, + TemporalUnit.Month, + TemporalUnit.Week, + TemporalUnit.Day, + TemporalUnit.Hour, + TemporalUnit.Minute, + TemporalUnit.Second, + TemporalUnit.Millisecond, + TemporalUnit.Microsecond, + TemporalUnit.Nanosecond, + ]; + for (const unit of order) { + if (u1 === unit) { + return unit; + } + if (u2 === unit) { + return unit; + } + } + Assert(false, 'unreachable'); +} + +/** https://tc39.es/proposal-temporal/#sec-iscalendarunit */ +export function IsCalendarUnit(unit: TemporalUnit): unit is TemporalUnit.Year | TemporalUnit.Month | TemporalUnit.Week { + return unit === TemporalUnit.Year || unit === TemporalUnit.Month || unit === TemporalUnit.Week; +} + +/** https://tc39.es/proposal-temporal/#sec-temporalunitcategory */ +export function TemporalUnitCategory(unit: TemporalUnit): 'date' | 'time' { + if (unit === TemporalUnit.Year || unit === TemporalUnit.Month || unit === TemporalUnit.Week || unit === TemporalUnit.Day) { + return 'date'; + } + return 'time'; +} + +/** https://tc39.es/proposal-temporal/#sec-maximumtemporaldurationroundingincrement */ +export function MaximumTemporalDurationRoundingIncrement(unit: TemporalUnit): 24 | 60 | 1000 | 'unset' { + switch (unit) { + case TemporalUnit.Hour: return 24; + case TemporalUnit.Minute: return 60; + case TemporalUnit.Second: return 60; + case TemporalUnit.Millisecond: return 1000; + case TemporalUnit.Microsecond: return 1000; + case TemporalUnit.Nanosecond: return 1000; + default: return 'unset'; + } +} + +/** https://tc39.es/proposal-temporal/#sec-ispartialtemporalobject */ +export function* IsPartialTemporalObject(value: Value): PlainEvaluator { + if (!(value instanceof ObjectValue)) { + return false; + } + if ( + 'InitializedTemporalDate' in value + || 'InitializedTemporalDateTime' in value + || 'InitializedTemporalMonthDay' in value + || 'InitializedTemporalTime' in value + || 'InitializedTemporalYearMonth' in value + || 'InitializedTemporalZonedDateTime' in value + ) { + return false; + } + const calendarProperty = Q(yield* Get(value, Value('calendar'))); + if (!(calendarProperty instanceof UndefinedValue)) { + return false; + } + const timeZoneProperty = Q(yield* Get(value, Value('timeZone'))); + if (!(timeZoneProperty instanceof UndefinedValue)) { + return false; + } + return true; +} + +/** https://tc39.es/proposal-temporal/#sec-formatfractionalseconds */ +export function FormatFractionalSeconds(subSecondNanoseconds: number, precision: number | 'auto'): string { + if (precision === 'auto') { + if (subSecondNanoseconds === 0) { + return ''; + } + let fractionString = ToZeroPaddedDecimalString(subSecondNanoseconds, 9); + // Set fractionString to the longest prefix of fractionString ending with a code unit other than 0x0030 (DIGIT ZERO). + fractionString = fractionString.replace(/0+$/, ''); + return `.${fractionString}`; + } else { + if (precision === 0) { + return ''; + } + let fractionString = ToZeroPaddedDecimalString(subSecondNanoseconds, 9); + fractionString = fractionString.slice(0, precision); + return `.${fractionString}`; + } +} + +/** https://tc39.es/proposal-temporal/#sec-formattimestring */ +export function FormatTimeString( + hour: number, + minute: number, + second: number, + subSecondNanoseconds: number, + precision: number | 'minute' | 'auto', + style?: 'separated' | 'unseparated', +): string { + const separator = style === 'unseparated' ? '' : ':'; + const hh = ToZeroPaddedDecimalString(hour, 2); + const mm = ToZeroPaddedDecimalString(minute, 2); + if (precision === 'minute') { + return hh + separator + mm; + } + const ss = ToZeroPaddedDecimalString(second, 2); + const subSecondsPart = FormatFractionalSeconds(subSecondNanoseconds, precision); + return hh + separator + mm + separator + ss + subSecondsPart; +} + +/** https://tc39.es/proposal-temporal/#sec-getunsignedroundingmode */ +export function GetUnsignedRoundingMode( + roundingMode: RoundingMode, + sign: 'negative' | 'positive', +): UnsignedRoundingMode { + const table = { + [RoundingMode.Ceil]: { positive: UnsignedRoundingMode.Infinity, negative: UnsignedRoundingMode.Zero }, + [RoundingMode.Floor]: { positive: UnsignedRoundingMode.Zero, negative: UnsignedRoundingMode.Infinity }, + [RoundingMode.Expand]: { positive: UnsignedRoundingMode.Infinity, negative: UnsignedRoundingMode.Infinity }, + [RoundingMode.Trunc]: { positive: UnsignedRoundingMode.Zero, negative: UnsignedRoundingMode.Zero }, + [RoundingMode.HalfCeil]: { positive: UnsignedRoundingMode.HalfInfinity, negative: UnsignedRoundingMode.HalfZero }, + [RoundingMode.HalfFloor]: { positive: UnsignedRoundingMode.HalfZero, negative: UnsignedRoundingMode.HalfInfinity }, + [RoundingMode.HalfExpand]: { positive: UnsignedRoundingMode.HalfInfinity, negative: UnsignedRoundingMode.HalfInfinity }, + [RoundingMode.HalfTrunc]: { positive: UnsignedRoundingMode.HalfZero, negative: UnsignedRoundingMode.HalfZero }, + [RoundingMode.HalfEven]: { positive: UnsignedRoundingMode.HalfEven, negative: UnsignedRoundingMode.HalfEven }, + } as const; + return table[roundingMode][sign]; +} + +/** https://tc39.es/proposal-temporal/#sec-applyunsignedroundingmode */ +export function ApplyUnsignedRoundingMode( + x: number, + r1: number, + r2: number, + unsignedRoundingMode?: UnsignedRoundingMode, +): number { + if (x === r1) { + return r1; + } + Assert(r1 < x && x < r2); + Assert(unsignedRoundingMode !== undefined); + if (unsignedRoundingMode === UnsignedRoundingMode.Zero) { + return r1; + } + if (unsignedRoundingMode === UnsignedRoundingMode.Infinity) { + return r2; + } + const d1 = x - r1; + const d2 = r2 - x; + if (d1 < d2) { + return r1; + } + if (d2 < d1) { + return r2; + } + Assert(d1 === d2); + if (unsignedRoundingMode === UnsignedRoundingMode.HalfZero) { + return r1; + } + if (unsignedRoundingMode === UnsignedRoundingMode.HalfInfinity) { + return r2; + } + Assert(unsignedRoundingMode === UnsignedRoundingMode.HalfEven); + const cardinality = (r1 / (r2 - r1)) % 2; + if (cardinality === 0) { + return r1; + } + return r2; +} + +/** https://tc39.es/proposal-temporal/#sec-roundnumbertoincrement */ +export function RoundNumberToIncrement( + x: number, + increment: number, + roundingMode: RoundingMode, +): number { + let quotient = x / increment; + let isNegative: 'negative' | 'positive'; + if (quotient < 0) { + isNegative = 'negative'; + quotient = -quotient; + } else { + isNegative = 'positive'; + } + const unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, isNegative); + // Let r1 be the largest integer such that r1 ≤ quotient. + const r1 = Math.floor(quotient); + // Let r2 be the smallest integer such that r2 > quotient. + const r2 = r1 + 1; + let rounded = ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode); + if (isNegative === 'negative') { + rounded = -rounded; + } + return rounded * increment; +} + +/** https://tc39.es/proposal-temporal/#sec-roundnumbertoincrementasifpositive */ +export function RoundNumberToIncrementAsIfPositive( + x: number, + increment: number, + roundingMode: RoundingMode, +): number { + const quotient = x / increment; + const unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, 'positive'); + // Let r1 be the largest integer such that r1 ≤ quotient. + const r1 = Math.floor(quotient); + // Let r2 be the smallest integer such that r2 > quotient. + const r2 = r1 + 1; + const rounded = ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode); + return rounded * increment; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-topositiveintegerwithtruncation */ +export function* ToPositiveIntegerWithTruncation(argument: Value): PlainEvaluator { + const integer = Q(yield* ToIntegerWithTruncation(argument)); + if (integer <= 0) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', integer); + } + return integer; +} + +// TODO: Review +/** https://tc39.es/proposal-temporal/#sec-temporal-tointegerwithtruncation */ +export function* ToIntegerWithTruncation(argument: Value): PlainEvaluator { + const number = R(Q(yield* ToNumber(argument))); + if (Number.isNaN(number) || number === Infinity || number === -Infinity) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', number); + } + return Math.trunc(number); +} + +// TODO: Review +/** https://tc39.es/proposal-temporal/#sec-temporal-tomonthcode */ +export function* ToMonthCode(argument: Value): PlainEvaluator { + const monthCode = Q(yield* ToPrimitive(argument, 'string')); + if (!(monthCode instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', monthCode); + } + const s = monthCode.stringValue(); + if (s.length !== 3 && s.length !== 4) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', s); + } + if (s.charCodeAt(0) !== 0x004D) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', s); + } + if (s.charCodeAt(1) < 0x0030 || s.charCodeAt(1) > 0x0039) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', s); + } + if (s.charCodeAt(2) < 0x0030 || s.charCodeAt(2) > 0x0039) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', s); + } + if (s.length === 4 && s.charCodeAt(3) !== 0x004C) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', s); + } + const monthCodeDigits = s.slice(1, 3); + const monthCodeInteger = Number(monthCodeDigits); + if (monthCodeInteger === 0 && s.length !== 4) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', s); + } + return s; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-tooffsetstring */ +export function* ToOffsetString(argument: Value): PlainEvaluator { + const offset = Q(yield* ToPrimitive(argument, 'string')); + if (!(offset instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', offset); + } + Q(ParseDateTimeUTCOffset(offset.stringValue())); + return offset.stringValue(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-isodatetofields */ +export function ISODateToFields( + calendar: CalendarType, + isoDate: ISODateRecord, + type: 'date' | 'year-month' | 'month-day', +): CalendarFieldsRecord { + const fields: CalendarFieldsRecord = { + Day: undefined, + Era: undefined, + EraYear: undefined, + Hour: undefined, + Microsecond: undefined, + Millisecond: undefined, + Minute: undefined, + Month: undefined, + MonthCode: undefined, + Nanosecond: undefined, + OffsetString: undefined, + Second: undefined, + TimeZone: undefined, + Year: undefined, + }; + const calendarDate = CalendarISOToDate(calendar, isoDate); + fields.MonthCode = calendarDate.MonthCode; + if (type === 'month-day' || type === 'date') { + fields.Day = calendarDate.Day; + } + if (type === 'year-month' || type === 'date') { + fields.Year = calendarDate.Year; + } + return fields; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings */ +export function* GetDifferenceSettings( + operation: 'since' | 'until', + options: ObjectValue, + unitGroup: 'date' | 'time' | 'datetime', + disallowedUnits: readonly TemporalUnit[], + fallbackSmallestUnit: TemporalUnit, + smallestLargestDefaultUnit: TemporalUnit, +): PlainEvaluator<{ + SmallestUnit: TemporalUnit, + LargestUnit: TemporalUnit, + RoundingMode: RoundingMode, + RoundingIncrement: number +}> { + let largestUnit = Q(yield* GetTemporalUnitValuedOption(options, 'largestUnit', 'unset')); + const roundingIncrement = Q(yield* GetRoundingIncrementOption(options)); + let roundingMode = Q(yield* GetRoundingModeOption(options, RoundingMode.Trunc)); + let smallestUnit = Q(yield* GetTemporalUnitValuedOption(options, 'smallestUnit', 'unset')); + Q(ValidateTemporalUnitValue(smallestUnit, unitGroup, ['auto'])); + if (largestUnit === 'unset') { + largestUnit = 'auto'; + } + if (disallowedUnits.includes(largestUnit as TemporalUnit)) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', largestUnit); + } + Q(ValidateTemporalUnitValue(smallestUnit, unitGroup)); + if (smallestUnit === 'unset') { + smallestUnit = fallbackSmallestUnit; + } + if (disallowedUnits.includes(smallestUnit as TemporalUnit)) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', smallestUnit); + } + const defaultLargestUnit = LargerOfTwoTemporalUnits(smallestLargestDefaultUnit, smallestUnit as TemporalUnit); + if (largestUnit === 'auto') { + largestUnit = defaultLargestUnit; + } + if (LargerOfTwoTemporalUnits(largestUnit, smallestUnit as TemporalUnit) !== largestUnit) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', largestUnit); + } + const maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit); + if (maximum !== 'unset') { + Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false)); + } + if (operation === 'since') { + roundingMode = NegateRoundingMode(roundingMode); + } + return { + SmallestUnit: smallestUnit as TemporalUnit, + LargestUnit: largestUnit, + RoundingMode: roundingMode, + RoundingIncrement: roundingIncrement, + }; +} diff --git a/src/abstract-ops/temporal/time-zone.mts b/src/abstract-ops/temporal/time-zone.mts new file mode 100644 index 0000000..e2e54c7 --- /dev/null +++ b/src/abstract-ops/temporal/time-zone.mts @@ -0,0 +1,300 @@ +import type { ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts'; +import type { ISODateTimeRecord } from '../../intrinsics/Temporal/PlainDateTime.mts'; +import { ParseTemporalTimeZoneString, ParseTimeZoneIdentifier } from '../../parser/TemporalParser.mts'; +import { + HourFromTime, MinFromTime, SecFromTime, msFromTime, +} from '../date-objects.mts'; +import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { R } from '../spec-types.mjs'; +import { abs } from '../math.mts'; +import { + IsOffsetTimeZoneIdentifier, GetNamedTimeZoneEpochNanoseconds, GetUTCEpochNanoseconds, RoundingMode, + AvailableNamedTimeZoneIdentifiers, + GetNamedTimeZoneOffsetNanoseconds, +} from './addition.mts'; +import type { TimeZoneIdentifier } from './addition.mts'; +import { + RoundNumberToIncrement, EpochTimeToDate, EpochTimeToEpochYear, EpochTimeToMonthInYear, CheckISODaysRange, + FormatTimeString, +} from './temporal.mts'; +import { + Assert, JSStringValue, ObjectValue, Value, type PlainCompletion, Q, + Throw, + X, + AddDaysToISODate, + AddTime, + BalanceISODateTime, + CombineISODateAndTimeRecord, + CreateISODateRecord, + CreateTimeRecord, + IsValidEpochNanoseconds, + MidnightTimeRecord, + nsPerDay, + TimeDurationFromComponents, +} from '#self'; + +// https://tc39.es/proposal-temporal/#sec-temporal-getavailablenamedtimezoneidentifier +export function GetAvailableNamedTimeZoneIdentifier(timeZoneIdentifier: TimeZoneIdentifier): TimeZoneIdentifierRecord | undefined { + for (const record of AvailableNamedTimeZoneIdentifiers()) { + if (record.Identifier.toLowerCase() === timeZoneIdentifier.toLowerCase()) { + return record; + } + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-time-zone-identifier-record */ +export interface TimeZoneIdentifierRecord { + readonly Identifier: TimeZoneIdentifier; + readonly PrimaryIdentifier: TimeZoneIdentifier; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getisopartsfromepoch +export function GetISOPartsFromEpoch(epochNanoseconds: number): ISODateTimeRecord { + Assert(IsValidEpochNanoseconds(epochNanoseconds)); + const remainderNs = epochNanoseconds % 1e6; + const epochMilliseconds = (epochNanoseconds - remainderNs) / 1e6; + const year = EpochTimeToEpochYear(epochMilliseconds); + const month = EpochTimeToMonthInYear(epochMilliseconds) + 1; + const day = EpochTimeToDate(epochMilliseconds); + const hour = R(HourFromTime(Value(epochMilliseconds))); + const minute = R(MinFromTime(Value(epochMilliseconds))); + const second = R(SecFromTime(Value(epochMilliseconds))); + const millisecond = R(msFromTime(Value(epochMilliseconds))); + const microsecond = Math.floor(remainderNs / 1000); + Assert(microsecond < 1000); + const nanosecond = remainderNs % 1000; + const isoDate = CreateISODateRecord(year, month, day); + const time = CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond); + return CombineISODateAndTimeRecord(isoDate, time); +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getnamedtimezonenexttransition +export function GetNamedTimeZoneNextTransition(timeZoneIdentifier: TimeZoneIdentifier, _epochNanoseconds: bigint): bigint | null { + Assert(timeZoneIdentifier === 'UTC'); + return null; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getnamedtimezoneprevioustransition +export function GetNamedTimeZonePreviousTransition(timeZoneIdentifier: TimeZoneIdentifier, _epochNanoseconds: bigint): bigint | null { + Assert(timeZoneIdentifier === 'UTC'); + return null; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-formatoffsettimezoneidentifier +export function FormatOffsetTimeZoneIdentifier(offsetMinutes: number, style: 'separated' | 'unseparated' = 'separated'): TimeZoneIdentifier { + const sign = offsetMinutes >= 0 ? '+' : '-'; + const absoluteMinutes = Math.abs(offsetMinutes); + const hour = Math.floor(absoluteMinutes / 60); + const minute = absoluteMinutes % 60; + const timeString = FormatTimeString(hour, minute, 0, 0, 'minute', style); + return sign + timeString as TimeZoneIdentifier; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-formatutcoffsetnanoseconds +export function FormatUTCOffsetNanoseconds(offsetNanoseconds: number): string { + const sign = offsetNanoseconds >= 0 ? '+' : '-'; + const absoluteNanoseconds = Math.abs(offsetNanoseconds); + const hour = Math.floor(absoluteNanoseconds / (3600 * 1e9)); + const minute = Math.floor(absoluteNanoseconds / (60 * 1e9)) % 60; + const second = Math.floor(absoluteNanoseconds / 1e9) % 60; + const subSecondNanoseconds = absoluteNanoseconds % 1e9; + const precision: 'minute' | 'auto' = second === 0 && subSecondNanoseconds === 0 ? 'minute' : 'auto'; + const timeString = FormatTimeString(hour, minute, second, subSecondNanoseconds, precision); + return sign + timeString; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-formatdatetimeutcoffsetrounded +export function FormatDateTimeUTCOffsetRounded(offsetNanoseconds: number): string { + offsetNanoseconds = RoundNumberToIncrement(offsetNanoseconds, 60 * 1e9, RoundingMode.HalfExpand); + const offsetMinutes = offsetNanoseconds / (60 * 1e9); + return FormatOffsetTimeZoneIdentifier(offsetMinutes); +} + +// https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezoneidentifier +export function ToTemporalTimeZoneIdentifier(temporalTimeZoneLike: Value | string): PlainCompletion { + if (temporalTimeZoneLike instanceof ObjectValue && isTemporalZonedDateTimeObject(temporalTimeZoneLike)) { + return temporalTimeZoneLike.TimeZone; + } + if (!(temporalTimeZoneLike instanceof JSStringValue) && typeof temporalTimeZoneLike !== 'string') { + return Throw.TypeError('$1 is not a string', temporalTimeZoneLike); + } + const temporalTimeZoneLikeString = temporalTimeZoneLike instanceof JSStringValue ? temporalTimeZoneLike.stringValue() : temporalTimeZoneLike; + const parseResult = Q(ParseTemporalTimeZoneString(temporalTimeZoneLikeString)); + const offsetMinutes = parseResult.OffsetMinutes; + if (offsetMinutes !== undefined) { + return FormatOffsetTimeZoneIdentifier(offsetMinutes); + } + const name = parseResult.Name; + const timeZoneIdentifierRecord = GetAvailableNamedTimeZoneIdentifier(name! as TimeZoneIdentifier); + if (timeZoneIdentifierRecord === undefined) { + return Throw.RangeError('Invalid time zone identifier: $1', temporalTimeZoneLikeString); + } + return timeZoneIdentifierRecord.Identifier; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor +export function GetOffsetNanosecondsFor(timeZone: TimeZoneIdentifier, epochNs: bigint): number { + const parseResult = X(ParseTimeZoneIdentifier(timeZone)); + if (parseResult.OffsetMinutes !== undefined) { + return parseResult.OffsetMinutes * (60 * 1e9); + } + return GetNamedTimeZoneOffsetNanoseconds(parseResult.Name!, epochNs); +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getisodatetimefor +export function GetISODateTimeFor(timeZone: TimeZoneIdentifier, epochNs: bigint): ISODateTimeRecord { + const offsetNanoseconds = GetOffsetNanosecondsFor(timeZone, epochNs); + const result = GetISOPartsFromEpoch(Number(epochNs)); + return BalanceISODateTime( + result.ISODate.Year, + result.ISODate.Month, + result.ISODate.Day, + result.Time.Hour, + result.Time.Minute, + result.Time.Second, + result.Time.Millisecond, + result.Time.Microsecond, + result.Time.Nanosecond + offsetNanoseconds, + ); +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor +export function GetEpochNanosecondsFor( + timeZone: TimeZoneIdentifier, + isoDateTime: ISODateTimeRecord, + disambiguation: 'compatible' | 'earlier' | 'later' | 'reject', +): PlainCompletion { + const possibleEpochNs = Q(GetPossibleEpochNanoseconds(timeZone, isoDateTime)); + return DisambiguatePossibleEpochNanoseconds(possibleEpochNs, timeZone, isoDateTime, disambiguation); +} + +// https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleepochnanoseconds +export function DisambiguatePossibleEpochNanoseconds( + possibleEpochNs: readonly bigint[], + timeZone: TimeZoneIdentifier, + isoDateTime: ISODateTimeRecord, + disambiguation: 'compatible' | 'earlier' | 'later' | 'reject', +): PlainCompletion { + let n = possibleEpochNs.length; + if (n === 1) { + return possibleEpochNs[0]; + } + if (n !== 0) { + if (disambiguation === 'earlier' || disambiguation === 'compatible') { + return possibleEpochNs[0]; + } + if (disambiguation === 'later') { + return possibleEpochNs[n - 1]; + } + Assert(disambiguation === 'reject'); + return Throw.RangeError('Multiple possible epoch nanoseconds'); + } + Assert(n === 0); + if (disambiguation === 'reject') { + return Throw.RangeError('No possible epoch nanoseconds'); + } + const before: ISODateTimeRecord = null!; + Assert(!!before, 'TODO(temporal): 6. Let before be the latest possible ISO Date-Time Record for which CompareISODateTime(before, isoDateTime) = -1 and ! GetPossibleEpochNanoseconds(timeZone, before) is not empty.'); + const after: ISODateTimeRecord = null!; + Assert(!!after, 'TODO(temporal): 7. Let after be the earliest possible ISO Date-Time Record for which CompareISODateTime(after, isoDateTime) = 1 and ! GetPossibleEpochNanoseconds(timeZone, after) is not empty.'); + const beforePossible = X(GetPossibleEpochNanoseconds(timeZone, before)); + Assert(beforePossible.length === 1); + const afterPossible = X(GetPossibleEpochNanoseconds(timeZone, after)); + Assert(afterPossible.length === 1); + const offsetBefore = GetOffsetNanosecondsFor(timeZone, beforePossible[0]); + const offsetAfter = GetOffsetNanosecondsFor(timeZone, afterPossible[0]); + const naneseconds = offsetAfter - offsetBefore; + Assert(abs(naneseconds) <= nsPerDay); + if (disambiguation === 'earlier') { + const timeDuration = TimeDurationFromComponents(0, 0, 0, 0, 0, -naneseconds); + const earlierTime = AddTime(isoDateTime.Time, timeDuration); + const earlierDate = AddDaysToISODate(isoDateTime.ISODate, earlierTime.Days); + const earlierDateTime = CombineISODateAndTimeRecord(earlierDate, earlierTime); + possibleEpochNs = Q(GetPossibleEpochNanoseconds(timeZone, earlierDateTime)); + Assert(possibleEpochNs.length > 0); + return possibleEpochNs[0]; + } + Assert(disambiguation === 'compatible' || disambiguation === 'later'); + const timeDuration = TimeDurationFromComponents(0, 0, 0, 0, 0, naneseconds); + const laterTime = AddTime(isoDateTime.Time, timeDuration); + const laterDate = AddDaysToISODate(isoDateTime.ISODate, laterTime.Days); + const laterDateTime = CombineISODateAndTimeRecord(laterDate, laterTime); + possibleEpochNs = Q(GetPossibleEpochNanoseconds(timeZone, laterDateTime)); + n = possibleEpochNs.length; + Assert(n > 0); + return possibleEpochNs[n - 1]; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-getpossibleepochnanoseconds +export function GetPossibleEpochNanoseconds( + timeZone: TimeZoneIdentifier, + isoDateTime: ISODateTimeRecord, +): PlainCompletion { + const parseResult = X(ParseTimeZoneIdentifier(timeZone)); + let possibleEpochNanoseconds: bigint[]; + if (parseResult.OffsetMinutes !== undefined) { + const balanced = BalanceISODateTime( + isoDateTime.ISODate.Year, + isoDateTime.ISODate.Month, + isoDateTime.ISODate.Day, + isoDateTime.Time.Hour, + isoDateTime.Time.Minute - parseResult.OffsetMinutes, + isoDateTime.Time.Second, + isoDateTime.Time.Millisecond, + isoDateTime.Time.Microsecond, + isoDateTime.Time.Nanosecond, + ); + Q(CheckISODaysRange(balanced.ISODate)); + const epochNanoseconds = GetUTCEpochNanoseconds(balanced); + possibleEpochNanoseconds = [epochNanoseconds]; + } else { + possibleEpochNanoseconds = GetNamedTimeZoneEpochNanoseconds(parseResult.Name! as TimeZoneIdentifier, isoDateTime); + } + for (const epochNanoseconds of possibleEpochNanoseconds) { + if (!IsValidEpochNanoseconds(epochNanoseconds)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds); + } + } + return possibleEpochNanoseconds; +} + +// It determines the exact time that corresponds to the first valid wall-clock time in the calendar date isoDate in timeZone. +/** https://tc39.es/proposal-temporal/#sec-temporal-getstartofday */ +export function GetStartOfDay( + timeZone: TimeZoneIdentifier, + isoDate: ISODateRecord, +): PlainCompletion { + const isoDateTime = CombineISODateAndTimeRecord(isoDate, MidnightTimeRecord()); + const possibleEpochNs = Q(GetPossibleEpochNanoseconds(timeZone, isoDateTime)); + if (possibleEpochNs.length) { + return possibleEpochNs[0]; + } + Assert(IsOffsetTimeZoneIdentifier(timeZone) === false); + // TODO(temporal) + const isoDateTimeAfter: ISODateTimeRecord = null!; + Assert(!!isoDateTimeAfter, 'TODO: isoDateTimeAfter is the ISO Date-Time Record for which DifferenceISODateTime(isoDateTime, isoDateTimeAfter, "iso8601", hour).[[Time]] is the smallest possible value > 0 for which possibleEpochNsAfter is not empty (i.e., isoDateTimeAfter represents the first local time after the transition).'); + // const possibleEpochNsAfter = GetNamedTimeZoneEpochNanoseconds(timeZone, isoDateTimeAfter!); + // Assert(possibleEpochNsAfter.length === 1); + // return possibleEpochNsAfter[0]; + return 0n; +} + +// https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals +export function TimeZoneEquals(one: TimeZoneIdentifier, two: TimeZoneIdentifier): boolean { + if (one === two) { + return true; + } + if (!IsOffsetTimeZoneIdentifier(one) && !IsOffsetTimeZoneIdentifier(two)) { + const recordOne = GetAvailableNamedTimeZoneIdentifier(one); + const recordTwo = GetAvailableNamedTimeZoneIdentifier(two); + Assert(recordOne !== undefined); + Assert(recordTwo !== undefined); + if (recordOne.PrimaryIdentifier === recordTwo.PrimaryIdentifier) { + return true; + } + } + // TODO(temporal) + // 3. Assert: If one and two are both offset time zone identifiers, they do not represent the same number of offset minutes. + return false; +} diff --git a/src/abstract-ops/temporal/zoned-datetime.mts b/src/abstract-ops/temporal/zoned-datetime.mts new file mode 100644 index 0000000..afde03f --- /dev/null +++ b/src/abstract-ops/temporal/zoned-datetime.mts @@ -0,0 +1,370 @@ +import type { TemporalDurationObject } from '../../intrinsics/Temporal/Duration.mts'; +import type { ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts'; +import { type TemporalZonedDateTimeObject, isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts'; +import { ParseISODateTime, ParseDateTimeUTCOffset } from '../../parser/TemporalParser.mts'; +import { + GetOptionsObject, + type TimeZoneIdentifier, GetUTCEpochNanoseconds, RoundingMode, +} from './addition.mts'; +import { + type PlainCompletion, Assert, Q, GetStartOfDay, GetEpochNanosecondsFor, CheckISODaysRange, IsValidEpochNanoseconds, Throw, GetPossibleEpochNanoseconds, RoundNumberToIncrement, DisambiguatePossibleEpochNanoseconds, Value, type ValueEvaluator, type CalendarType, ObjectValue, GetTemporalDisambiguationOption, GetTemporalOffsetOption, GetTemporalOverflowOption, X, GetTemporalCalendarIdentifierWithISODefault, PrepareCalendarFields, JSStringValue, ToTemporalTimeZoneIdentifier, CanonicalizeCalendar, CreateISODateRecord, type FunctionObject, surroundingAgent, OrdinaryCreateFromConstructor, type Mutable, RoundTemporalInstant, TemporalUnit, GetOffsetNanosecondsFor, GetISODateTimeFor, FormatDateTimeUTCOffsetRounded, FormatCalendarAnnotation, type InternalDurationRecord, DateDurationSign, AddInstant, CalendarDateAdd, CombineDateAndTimeDuration, ZeroDateDuration, type TimeDuration, CompareISODate, TimeDurationFromEpochNanosecondsDifference, TimeDurationSign, AddDaysToISODate, LargerOfTwoTemporalUnits, CalendarDateUntil, type DateUnit, TemporalUnitCategory, DifferenceInstant, type TimeUnit, RoundRelativeDuration, TotalTimeDuration, TotalRelativeDuration, CalendarEquals, GetDifferenceSettings, TemporalDurationFromInternal, CreateNegatedTemporalDuration, TimeZoneEquals, CreateTemporalDuration, ToTemporalDuration, ToInternalDurationRecord, + BalanceISODateTime, + CombineISODateAndTimeRecord, + DifferenceTime, + InterpretTemporalDateTimeFields, + ISODateTimeToString, + ISODateTimeWithinLimits, + type TimeRecord, +} from '#self'; + +export type ISODateTimeOffsetBehaviour = 'option' | 'exact' | 'wall'; +export type ISODateTimeMatchBehaviour = 'match-exactly' | 'match-minutes'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-interpretisodatetimeoffset */ +export function InterpretISODateTimeOffset( + isoDate: ISODateRecord, + time: TimeRecord | 'start-of-day', + offsetBehaviour: ISODateTimeOffsetBehaviour, + offsetNanoseconds: number, + timeZone: TimeZoneIdentifier, + disambiguation: 'earlier' | 'later' | 'compatible' | 'reject', + offsetOption: 'ignore' | 'use' | 'prefer' | 'reject', + matchBehaviour: ISODateTimeMatchBehaviour, +): PlainCompletion { + if (time === 'start-of-day') { + Assert(offsetBehaviour === 'wall'); + Assert(offsetNanoseconds === 0); + return Q(GetStartOfDay(timeZone, isoDate)); + } + const isoDateTime = CombineISODateAndTimeRecord(isoDate, time); + if (offsetBehaviour === 'wall' || (offsetBehaviour === 'option' && offsetOption === 'ignore')) { + return Q(GetEpochNanosecondsFor(timeZone, isoDateTime, disambiguation)); + } + if (offsetBehaviour === 'exact' || (offsetBehaviour === 'option' && offsetOption === 'use')) { + const balanced = BalanceISODateTime(isoDate.Year, isoDate.Month, isoDate.Day, time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond, time.Nanosecond - offsetNanoseconds); + Q(CheckISODaysRange(balanced.ISODate)); + const epochNanoseconds = GetUTCEpochNanoseconds(balanced); + if (!IsValidEpochNanoseconds(epochNanoseconds)) { + return Throw.RangeError('Invalid date'); + } + return epochNanoseconds; + } + Assert(offsetBehaviour === 'option'); + Assert(offsetOption === 'prefer' || offsetOption === 'reject'); + Q(CheckISODaysRange(isoDate)); + const utcEpochNanoseconds = GetUTCEpochNanoseconds(isoDateTime); + const possibleEpochNs = Q(GetPossibleEpochNanoseconds(timeZone, isoDateTime)); + for (const candidate of possibleEpochNs) { + const candidateOffset = utcEpochNanoseconds - candidate; + if (candidateOffset === BigInt(offsetNanoseconds)) { + return candidate; + } + if (matchBehaviour === 'match-minutes') { + const roundedCandidateNanoseconds = RoundNumberToIncrement(Number(candidateOffset), 60 * 1e9, RoundingMode.HalfExpand); + if (roundedCandidateNanoseconds === offsetNanoseconds) { + return candidate; + } + } + } + if (offsetOption === 'reject') { + return Throw.RangeError('No matching offset found for the given date and time'); + } + return Q(DisambiguatePossibleEpochNanoseconds(possibleEpochNs, timeZone, isoDateTime, disambiguation)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalzoneddatetime */ +export function* ToTemporalZonedDateTime( + item: Value, + options: Value = Value.undefined, +): ValueEvaluator { + let hasUTCDesignator = false; + let matchBehaviour: ISODateTimeMatchBehaviour = 'match-exactly'; + let calendar: CalendarType; + let isoDate: ISODateRecord; + let time: TimeRecord | 'start-of-day'; + let timeZone: TimeZoneIdentifier; + let offsetString: string | undefined; + let disambiguation: 'earlier' | 'later' | 'compatible' | 'reject'; + let offsetOption: 'ignore' | 'use' | 'prefer' | 'reject'; + if (item instanceof ObjectValue) { + if (isTemporalZonedDateTimeObject(item)) { + const resolvedOptions = Q(GetOptionsObject(options)); + Q(yield* GetTemporalDisambiguationOption(resolvedOptions)); + Q(yield* GetTemporalOffsetOption(resolvedOptions, 'reject')); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + return X(CreateTemporalZonedDateTime(item.EpochNanoseconds, item.TimeZone, item.Calendar)); + } + calendar = Q(yield* GetTemporalCalendarIdentifierWithISODefault(item)); + const fields = Q(yield* PrepareCalendarFields(calendar, item, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset', 'time-zone'], ['time-zone'])); + timeZone = fields.TimeZone! as TimeZoneIdentifier; + offsetString = fields.OffsetString; + const resolvedOptions = Q(GetOptionsObject(options)); + disambiguation = Q(yield* GetTemporalDisambiguationOption(resolvedOptions)); + offsetOption = Q(yield* GetTemporalOffsetOption(resolvedOptions, 'reject')); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const result = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, overflow)); + isoDate = result.ISODate; + time = result.Time; + } else { + if (!(item instanceof JSStringValue)) { + return Throw.TypeError('$1 is not a string', item); + } + const result = Q(ParseISODateTime(item.stringValue(), ['TemporalDateTimeString[+Zoned]'])); + const annotation = result.TimeZone.TimeZoneAnnotation; + Assert(annotation !== undefined); + timeZone = Q(ToTemporalTimeZoneIdentifier(annotation)); + offsetString = result.TimeZone.OffsetString; + if (result.TimeZone.Z) { + hasUTCDesignator = true; + } + let calendar = result.Calendar; + if (calendar === undefined) { + calendar = 'iso8601'; + } + calendar = Q(CanonicalizeCalendar(calendar)); + matchBehaviour = 'match-minutes'; + if (offsetString) { + // TODO(temporal): + // i. Let offsetParseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[+SubMinutePrecision]). + // ii. Assert: offsetParseResult is a Parse Node. + // iii. If offsetParseResult contains more than one MinuteSecond Parse Node, set matchBehaviour to match-exactly. + } + const resolvedOptions = Q(GetOptionsObject(options)); + disambiguation = Q(yield* GetTemporalDisambiguationOption(resolvedOptions)); + offsetOption = Q(yield* GetTemporalOffsetOption(resolvedOptions, 'reject')); + Q(yield* GetTemporalOverflowOption(resolvedOptions)); + isoDate = CreateISODateRecord(result.Year!, result.Month, result.Day); + time = result.Time; + } + let offsetBehaviour: ISODateTimeOffsetBehaviour; + if (hasUTCDesignator) { + offsetBehaviour = 'exact'; + } else if (offsetString === undefined) { + offsetBehaviour = 'wall'; + } else { + offsetBehaviour = 'option'; + } + let offsetNanoseconds = 0; + if (offsetBehaviour === 'option') { + offsetNanoseconds = X(ParseDateTimeUTCOffset(offsetString!)); + } + const epochNanoseconds = Q(InterpretISODateTimeOffset(isoDate, time, offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour)); + return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar!)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-createtemporalzoneddatetime */ +export function* CreateTemporalZonedDateTime( + epochNanoseconds: bigint, + timeZone: TimeZoneIdentifier, + calendar: CalendarType, + newTarget?: FunctionObject, +): ValueEvaluator { + Assert(IsValidEpochNanoseconds(epochNanoseconds)); + if (newTarget === undefined) { + newTarget = surroundingAgent.intrinsic('%Temporal.ZonedDateTime%'); + } + const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.ZonedDateTime.prototype%', [ + 'InitializedTemporalZonedDateTime', + 'EpochNanoseconds', + 'TimeZone', + 'Calendar', + ])) as Mutable; + object.EpochNanoseconds = epochNanoseconds; + object.TimeZone = timeZone; + object.Calendar = calendar; + return object; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-temporalzoneddatetimetostring */ +export function TemporalZonedDateTimeToString( + zonedDateTime: TemporalZonedDateTimeObject, + precision: number | 'minute' | 'auto', + showCalendar: 'auto' | 'always' | 'never' | 'critical', + showTimeZone: 'auto' | 'never' | 'critical', + showOffset: 'auto' | 'never', + increment = 1, + unit: TemporalUnit.Minute | TemporalUnit.Second | TemporalUnit.Millisecond | TemporalUnit.Microsecond | TemporalUnit.Nanosecond = TemporalUnit.Nanosecond, + roundingMode = RoundingMode.Trunc, +): string { + let epochNs = zonedDateTime.EpochNanoseconds; + epochNs = RoundTemporalInstant(epochNs, increment, unit, roundingMode); + const timeZone = zonedDateTime.TimeZone; + const offsetNanoseconds = GetOffsetNanosecondsFor(timeZone, epochNs); + const isoDateTime = GetISODateTimeFor(timeZone, epochNs); + const dateTimeString = ISODateTimeToString(isoDateTime, 'iso8601', precision, 'never'); + const offsetString = showOffset === 'never' ? '' : FormatDateTimeUTCOffsetRounded(offsetNanoseconds); + let timeZoneString; + if (showTimeZone === 'never') { + timeZoneString = ''; + } else { + const flag = showTimeZone === 'critical' ? '!' : ''; + timeZoneString = `[${flag}${timeZone}]`; + } + const calendarString = FormatCalendarAnnotation(zonedDateTime.Calendar, showCalendar); + return dateTimeString + offsetString + timeZoneString + calendarString; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-addzoneddatetime */ +export function AddZonedDateTime( + epochNanoseconds: bigint, + timeZone: TimeZoneIdentifier, + calendar: CalendarType, + duration: InternalDurationRecord, + overflow: 'constrain' | 'reject', +): PlainCompletion { + if (DateDurationSign(duration.Date) === 0) { + return AddInstant(epochNanoseconds, duration.Time); + } + const isoDateTime = GetISODateTimeFor(timeZone, epochNanoseconds); + const addedDate = Q(CalendarDateAdd(calendar, isoDateTime.ISODate, duration.Date, overflow)); + const intermediateDateTime = CombineISODateAndTimeRecord(addedDate, isoDateTime.Time); + if (!ISODateTimeWithinLimits(intermediateDateTime)) { + return Throw.RangeError('Resulting date-time is out of range'); + } + const intermediateNs = X(GetEpochNanosecondsFor(timeZone, intermediateDateTime, 'compatible')); + return AddInstant(intermediateNs, duration.Time); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetime */ +export function DifferenceZonedDateTime( + ns1: bigint, + ns2: bigint, + timeZone: TimeZoneIdentifier, + calendar: CalendarType, + largestUnit: TemporalUnit, +): PlainCompletion { + if (ns1 === ns2) { + return CombineDateAndTimeDuration(ZeroDateDuration(), 0 as TimeDuration); + } + const startDateTime = GetISODateTimeFor(timeZone, ns1); + const endDateTime = GetISODateTimeFor(timeZone, ns2); + if (CompareISODate(startDateTime.ISODate, endDateTime.ISODate) === 0) { + const timeDuration = TimeDurationFromEpochNanosecondsDifference(ns2, ns1); + return CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration); + } + const sign = ns2 - ns1 > 0 ? 1 : -1; + const maxDayCorrection = sign === -1 ? 2 : 1; + let dayCorrection = 0; + let timeDuration = DifferenceTime(startDateTime.Time, endDateTime.Time); + if (TimeDurationSign(timeDuration) === sign) dayCorrection += 1; + let success = false; + let intermediateDateTime; + while (dayCorrection <= maxDayCorrection && !success) { + const intermediateDate = AddDaysToISODate(endDateTime.ISODate, dayCorrection * sign); + intermediateDateTime = CombineISODateAndTimeRecord(intermediateDate, startDateTime.Time); + const intermediateNs = Q(GetEpochNanosecondsFor(timeZone, intermediateDateTime, 'compatible')); + timeDuration = TimeDurationFromEpochNanosecondsDifference(ns2, intermediateNs); + const timeSign = TimeDurationSign(timeDuration); + if (sign !== timeSign) { + success = true; + } + dayCorrection += 1; + } + Assert(success); + const dateLargestUnit = LargerOfTwoTemporalUnits(largestUnit, TemporalUnit.Day); + const dateDifference = CalendarDateUntil(calendar, startDateTime.ISODate, intermediateDateTime!.ISODate, dateLargestUnit as DateUnit); + return CombineDateAndTimeDuration(dateDifference, timeDuration); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetimewithrounding */ +export function DifferenceZonedDateTimeWithRounding( + ns1: bigint, + ns2: bigint, + timeZone: TimeZoneIdentifier, + calendar: CalendarType, + largestUnit: TemporalUnit, + roundingIncrement: number, + smallestUnit: TemporalUnit, + roundingMode: RoundingMode, +): PlainCompletion { + if (TemporalUnitCategory(largestUnit) === 'time') { + return DifferenceInstant(ns1, ns2, roundingIncrement, smallestUnit as TimeUnit, roundingMode); + } + const difference = Q(DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUnit)); + if (smallestUnit === TemporalUnit.Nanosecond && roundingIncrement === 1) { + return difference; + } + const dateTime = GetISODateTimeFor(timeZone, ns1); + return RoundRelativeDuration(difference, ns1, ns2, dateTime, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetimewithtotal */ +export function DifferenceZonedDateTimeWithTotal( + ns1: bigint, + ns2: bigint, + timeZone: TimeZoneIdentifier, + calendar: CalendarType, + unit: TemporalUnit, +): PlainCompletion { + if (TemporalUnitCategory(unit) === 'time') { + const difference = TimeDurationFromEpochNanosecondsDifference(ns2, ns1); + return TotalTimeDuration(difference, unit as TimeUnit); + } + const difference = Q(DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, unit)); + const dateTime = GetISODateTimeFor(timeZone, ns1); + return TotalRelativeDuration(difference, ns1, ns2, dateTime, timeZone, calendar, unit); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalzoneddatetime */ +export function* DifferenceTemporalZonedDateTime( + operation: 'until' | 'since', + zonedDateTime: TemporalZonedDateTimeObject, + _other: Value, + options: Value, +): ValueEvaluator { + const other = Q(yield* ToTemporalZonedDateTime(_other)); + if (!CalendarEquals(zonedDateTime.Calendar, other.Calendar)) { + return Throw.RangeError('Calendars are not equal'); + } + const resolvedOptions = Q(GetOptionsObject(options)); + const settings = Q(yield* GetDifferenceSettings(operation, resolvedOptions, 'datetime', [], TemporalUnit.Nanosecond, TemporalUnit.Hour)); + if (TemporalUnitCategory(settings.LargestUnit) === 'time') { + const internalDuration = DifferenceInstant(zonedDateTime.EpochNanoseconds, other.EpochNanoseconds, settings.RoundingIncrement, settings.SmallestUnit as TimeUnit, settings.RoundingMode); + let result = X(TemporalDurationFromInternal(internalDuration, settings.LargestUnit)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; + } + if (!TimeZoneEquals(zonedDateTime.TimeZone, other.TimeZone)) { + return Throw.RangeError('Time zones are not equal'); + } + if (zonedDateTime.EpochNanoseconds === other.EpochNanoseconds) { + return X(CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + } + const internalDuration = Q(DifferenceZonedDateTimeWithRounding( + zonedDateTime.EpochNanoseconds, + other.EpochNanoseconds, + zonedDateTime.TimeZone, + zonedDateTime.Calendar, + settings.LargestUnit, + settings.RoundingIncrement, + settings.SmallestUnit, + settings.RoundingMode, + )); + let result = X(TemporalDurationFromInternal(internalDuration, TemporalUnit.Hour)); + if (operation === 'since') { + result = CreateNegatedTemporalDuration(result); + } + return result; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-adddurationtozoneddatetime */ +export function* AddDurationToZonedDateTime( + operation: 'add' | 'subtract', + zonedDateTime: TemporalZonedDateTimeObject, + temporalDurationLike: Value, + options: Value, +): ValueEvaluator { + let duration = Q(yield* ToTemporalDuration(temporalDurationLike)); + if (operation === 'subtract') { + duration = CreateNegatedTemporalDuration(duration); + } + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const calendar = zonedDateTime.Calendar; + const timeZone = zonedDateTime.TimeZone; + const internalDuration = ToInternalDurationRecord(duration); + const epochNanoseconds = Q(AddZonedDateTime(zonedDateTime.EpochNanoseconds, timeZone, calendar, internalDuration, overflow)); + return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar)); +} diff --git a/src/abstract-ops/testing-comparison.mts b/src/abstract-ops/testing-comparison.mts new file mode 100644 index 0000000..c6b53aa --- /dev/null +++ b/src/abstract-ops/testing-comparison.mts @@ -0,0 +1,380 @@ +import { + BigIntValue, + BooleanValue, NullValue, UndefinedValue, + SymbolValue, + JSStringValue, + NumberValue, + ObjectValue, + Value, + wellKnownSymbols, +} from '../value.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { + Assert, + Get, + ToBoolean, + ToNumber, + ToNumeric, + ToPrimitive, + StringToBigInt, + isProxyExoticObject, + isArrayExoticObject, R, + SameType, + type FunctionObject, + type PropertyKeyValue, +} from '#self'; + +// This file covers abstract operations defined in +/** https://tc39.es/ecma262/#sec-testing-and-comparison-operations */ + +/** https://tc39.es/ecma262/#sec-requireobjectcoercible */ +export function RequireObjectCoercible(argument: Value) { + if (argument === Value.undefined) { + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'undefined'); + } + if (argument === Value.null) { + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'null'); + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-isarray */ +export function IsArray(argument: Value) { + if (!(argument instanceof ObjectValue)) { + 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; +} + +/** https://tc39.es/ecma262/#sec-iscallable */ +export function IsCallable(argument: Value): argument is FunctionObject { + if (!(argument instanceof ObjectValue)) { + return false; + } + if ('Call' in argument) { + return true; + } + return false; +} + +/** https://tc39.es/ecma262/#sec-isconstructor */ +export function IsConstructor(argument: Value): argument is FunctionObject { + if (!(argument instanceof ObjectValue)) { + return false; + } + if ('Construct' in argument) { + return true; + } + return false; +} + +/** https://tc39.es/ecma262/#sec-isextensible-o */ +export function* IsExtensible(O: ObjectValue) { + Assert(O instanceof ObjectValue); + return yield* O.IsExtensible(); +} + +/** https://tc39.es/ecma262/#sec-isinteger */ +export function IsIntegralNumber(argument: Value) { + if (!(argument instanceof NumberValue)) { + return Value.false; + } + if (argument.isNaN() || argument.isInfinity()) { + return Value.false; + } + if (Math.floor(Math.abs(R(argument))) !== Math.abs(R(argument))) { + return Value.false; + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-ispropertykey */ +export function IsPropertyKey(argument: unknown): argument is PropertyKeyValue { + if (argument instanceof JSStringValue) { + return true; + } + if (argument instanceof SymbolValue) { + return true; + } + return false; +} + +/** https://tc39.es/ecma262/#sec-isregexp */ +export function* IsRegExp(argument: Value): ValueEvaluator { + if (!(argument instanceof ObjectValue)) { + return Value.false; + } + const matcher = Q(yield* Get(argument, wellKnownSymbols.match)); + if (matcher !== Value.undefined) { + return ToBoolean(matcher); + } + if ('RegExpMatcher' in argument) { + return Value.true; + } + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-isstringprefix */ +export function IsStringPrefix(p: JSStringValue, q: JSStringValue) { + Assert(p instanceof JSStringValue); + Assert(q instanceof JSStringValue); + return q.stringValue().startsWith(p.stringValue()); +} + +/** https://tc39.es/ecma262/#sec-samevalue */ +export function SameValue(x: Value, y: Value) { + // If SameType(x, y) is false, return false. + if (!SameType(x, y)) { + return Value.false; + } + // If x is a Number, then + if (x instanceof NumberValue) { + // a. Return Number::sameValue(x, y). + return NumberValue.sameValue(x, y as NumberValue); + } + // 3. Return SameValueNonNumber(x, y). + return X(SameValueNonNumber(x, y)); +} + +/** https://tc39.es/ecma262/#sec-samevaluezero */ +export function SameValueZero(x: Value, y: Value) { + // 1. If SameType(x, y) is false, return false. + if (!SameType(x, y)) { + return Value.false; + } + // 2. If x is a Number, then + if (x instanceof NumberValue) { + // a. Return Number::sameValueZero(x, y). + return NumberValue.sameValueZero(x, y as NumberValue); + } + // 3. Return SameValueNonNumber(x, y). + return SameValueNonNumber(x, y); +} + +/** https://tc39.es/ecma262/#sec-samevaluenonnumber */ +export function SameValueNonNumber(x: Value, y: Value) { + Assert(SameType(x, y)); + + if (x instanceof UndefinedValue || x instanceof NullValue) { + return Value.true; + } + + if (x instanceof BigIntValue) { + return BigIntValue.equal(x, y as BigIntValue); + } + + if (x instanceof JSStringValue) { + if (x.stringValue() === (y as JSStringValue).stringValue()) { + return Value.true; + } + return Value.false; + } + + if (x instanceof BooleanValue) { + if (x === y) { + return Value.true; + } + return Value.false; + } + + return x === y ? Value.true : Value.false; +} + +/** https://tc39.es/ecma262/#sec-abstract-relational-comparison */ +export function* AbstractRelationalComparison(x: Value, y: Value, LeftFirst = true): ValueEvaluator { + let px; + let py; + // 1. If the LeftFirst flag is true, then + if (LeftFirst === true) { + // a. Let px be ? ToPrimitive(x, number). + px = Q(yield* ToPrimitive(x, 'number')); + // b. Let py be ? ToPrimitive(y, number). + py = Q(yield* 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, number). + py = Q(yield* ToPrimitive(y, 'number')); + // c. Let px be ? ToPrimitive(x, number). + px = Q(yield* ToPrimitive(x, 'number')); + } + // 3. If Type(px) is String and Type(py) is String, then + if (px instanceof JSStringValue && py instanceof JSStringValue) { + // 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 (px instanceof BigIntValue && py instanceof JSStringValue) { + // i. Let ny be StringToBigInt(py). + const ny = StringToBigInt(py); + // ii. If ny is undefined, return undefined. + if (ny === undefined) { + 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 (px instanceof JSStringValue && py instanceof BigIntValue) { + // i. Let ny be StringToBigInt(py). + const nx = StringToBigInt(px); + // ii. If ny is undefined, return undefined. + if (nx === undefined) { + 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(yield* ToNumeric(px)); + // d. Let ny be ? ToNumeric(py). + const ny = Q(yield* ToNumeric(py)); + // e. If Type(nx) is the same as Type(ny), return Type(nx)::lessThan(nx, ny). + if (SameType(nx, ny)) { + if (nx instanceof NumberValue) { + return NumberValue.lessThan(nx, ny as NumberValue); + } else { + Assert(nx instanceof BigIntValue); + return BigIntValue.lessThan(nx, ny as BigIntValue); + } + } + // f. Assert: Type(nx) is BigInt and Type(ny) is Number, or Type(nx) is Number and Type(ny) is BigInt. + Assert((nx instanceof BigIntValue && ny instanceof NumberValue) || (nx instanceof NumberValue && ny instanceof BigIntValue)); + // 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 instanceof NumberValue && R(nx) === -Infinity) || (ny instanceof NumberValue && R(ny) === +Infinity)) { + return Value.true; + } + // i. If nx is +∞ or ny is -∞, return false. + if ((nx instanceof NumberValue && R(nx) === +Infinity) || (ny instanceof NumberValue && R(ny) === -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 = R(nx); + const b = R(ny); + return a < b ? Value.true : Value.false; + } +} + +/** https://tc39.es/ecma262/#sec-islooselyequal */ +export function* IsLooselyEqual(x: Value, y: Value): ValueEvaluator { + // 1. If SameType(x, y) is true, then + if (SameType(x, y)) { + // a. Return the result of performing Strict Equality Comparison x === y. + return IsStrictlyEqual(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 (x instanceof NumberValue && y instanceof JSStringValue) { + return X(yield* IsLooselyEqual(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 (x instanceof JSStringValue && y instanceof NumberValue) { + return X(yield* IsLooselyEqual(X(ToNumber(x)), y)); + } + // 6. If Type(x) is BigInt and Type(y) is String, then + if (x instanceof BigIntValue && y instanceof JSStringValue) { + // a. Let n be StringToBigInt(y). + const n = StringToBigInt(y); + // b. If n is undefined, return false. + if (n === undefined) { + return Value.false; + } + // c. Return the result of the comparison x == n. + return X(yield* IsLooselyEqual(x, n)); + } + // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. + if (x instanceof JSStringValue && y instanceof BigIntValue) { + return X(yield* IsLooselyEqual(y, x)); + } + // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. + if (x instanceof BooleanValue) { + return X(yield* IsLooselyEqual(X(ToNumber(x)), y)); + } + // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). + if (y instanceof BooleanValue) { + return X(yield* IsLooselyEqual(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 ((x instanceof JSStringValue || x instanceof NumberValue || x instanceof BigIntValue || x instanceof SymbolValue) && y instanceof ObjectValue) { + return X(yield* IsLooselyEqual(x, Q(yield* 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 (x instanceof ObjectValue && (y instanceof JSStringValue || y instanceof NumberValue || y instanceof BigIntValue || y instanceof SymbolValue)) { + return X(yield* IsLooselyEqual(Q(yield* 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 ((x instanceof BigIntValue && y instanceof NumberValue) || (x instanceof NumberValue && y instanceof BigIntValue)) { + // 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 = R(x); + const b = R(y); + return a == b ? Value.true : Value.false; // eslint-disable-line eqeqeq + } + // 13. Return false. + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-isstrictlyequal */ +export function IsStrictlyEqual(x: Value, y: Value) { +// 1. If SameType(x, y) is false, return false. + if (!SameType(x, y)) { + return Value.false; + } + // 2. If x is a Number, then + if (x instanceof NumberValue) { + // a. Return Number::equal(x, y). + return NumberValue.equal(x, y as NumberValue); + } + // 3. Return SameValueNonNumber(x, y). + return SameValueNonNumber(x, y); +} diff --git a/src/abstract-ops/type-conversion.mts b/src/abstract-ops/type-conversion.mts new file mode 100644 index 0000000..f9235c5 --- /dev/null +++ b/src/abstract-ops/type-conversion.mts @@ -0,0 +1,564 @@ +import { + UndefinedValue, JSStringValue, SymbolValue, + ObjectValue, + Value, + NumberValue, + BigIntValue, + wellKnownSymbols, + NullValue, + BooleanValue, + PrimitiveValue, + type PropertyKeyValue, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + Q, X, + type ValueCompletion, +} from '../completion.mts'; +import { OutOfRange, type Mutable } from '../helpers.mts'; +import { MV_StringNumericLiteral } from '../runtime-semantics/all.mts'; +import type { BooleanObject } from '../intrinsics/Boolean.mts'; +import type { NumberObject } from '../intrinsics/Number.mts'; +import type { SymbolObject } from '../intrinsics/Symbol.mts'; +import type { BigIntObject } from '../intrinsics/BigInt.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { + Assert, + Call, + Get, + GetMethod, + IsCallable, + OrdinaryObjectCreate, + SameValue, + StringCreate, + Z, + F, R, +} from './all.mts'; + +/** https://tc39.es/ecma262/#sec-toprimitive */ +export function* ToPrimitive(input: Value, preferredType?: 'string' | 'number'): ValueEvaluator { + // 1. Assert: input is an ECMAScript language value. + Assert(input instanceof Value); + // 2. If Type(input) is Object, then + if (input instanceof ObjectValue) { + // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). + const exoticToPrim = Q(yield* GetMethod(input, wellKnownSymbols.toPrimitive)); + // b. If exoticToPrim is not undefined, then + if (exoticToPrim !== Value.undefined) { + let hint; + // i. If preferredType is not present, let hint be "default". + if (preferredType === undefined) { + hint = Value('default'); + } else if (preferredType === 'string') { // ii. Else if preferredType is string, let hint be "string". + hint = Value('string'); + } else { // iii. Else, + // 1. Assert: preferredType is number. + Assert(preferredType === 'number'); + // 2. Let hint be "number". + hint = Value('number'); + } + // iv. Let result be ? Call(exoticToPrim, input, « hint »). + const result = Q(yield* Call(exoticToPrim, input, [hint])); + // v. If Type(result) is not Object, return result. + if (!(result instanceof ObjectValue)) { + return result; + } + // vi. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'ObjectToPrimitive'); + } + // c. If preferredType is not present, let preferredType be number. + if (preferredType === undefined) { + preferredType = 'number'; + } + // d. Return ? OrdinaryToPrimitive(input, preferredType). + return Q(yield* OrdinaryToPrimitive(input, preferredType)); + } + // 3. Return input. + return input; +} + +/** https://tc39.es/ecma262/#sec-ordinarytoprimitive */ +export function* OrdinaryToPrimitive(O: ObjectValue, hint: 'string' | 'number'): ValueEvaluator { + // 1. Assert: Type(O) is Object. + Assert(O instanceof ObjectValue); + // 2. Assert: hint is either string or number. + Assert(hint === 'string' || hint === 'number'); + let methodNames; + // 3. If hint is string, then + if (hint === 'string') { + // a. Let methodNames be « "toString", "valueOf" ». + methodNames = [Value('toString'), Value('valueOf')]; + } else { // 4. Else, + // a. Let methodNames be « "valueOf", "toString" ». + methodNames = [Value('valueOf'), Value('toString')]; + } + // 5. For each element name of methodNames, do + for (const name of methodNames) { + // a. Let method be ? Get(O, name). + const method = Q(yield* Get(O, name)); + // b. If IsCallable(method) is true, then + if (IsCallable(method)) { + // i. Let result be ? Call(method, O). + const result = Q(yield* Call(method, O)); + // ii. If Type(result) is not Object, return result. + if (!(result instanceof ObjectValue)) { + return result; + } + } + } + // 6. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'ObjectToPrimitive'); +} + +/** https://tc39.es/ecma262/#sec-toboolean */ +export function ToBoolean(argument: Value): BooleanValue { + if (argument instanceof UndefinedValue) { + // Return false. + return Value.false; + } else if (argument instanceof NullValue) { + // Return false. + return Value.false; + } else if (argument instanceof BooleanValue) { + // Return argument. + return argument; + } else if (argument instanceof NumberValue) { + // If argument is +0𝔽, -0𝔽, or NaN, return false; otherwise return true. + if (R(argument) === 0 || argument.isNaN()) { + return Value.false; + } + } else if (argument instanceof JSStringValue) { + // If argument is the empty String, return false; otherwise return true. + if (argument.stringValue().length === 0) { + return Value.false; + } + } else if (argument instanceof BigIntValue) { + // If argument is 0ℤ, return false; otherwise return true. + if (R(argument) === 0n) { + return Value.false; + } + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-tonumeric */ +export function* ToNumeric(value: Value): ValueEvaluator { + // 1. Let primValue be ? ToPrimitive(value, number). + const primValue = Q(yield* ToPrimitive(value, 'number')); + // 2. If Type(primValue) is BigInt, return primValue. + if (primValue instanceof BigIntValue) { + return primValue; + } + // 3. Return ? ToNumber(primValue). + return Q(yield* ToNumber(primValue)); +} + +/** https://tc39.es/ecma262/#sec-tonumber */ +export function* ToNumber(argument: Value): ValueEvaluator { + if (argument instanceof UndefinedValue) { + // Return NaN. + return F(NaN); + } else if (argument instanceof NullValue) { + // Return +0𝔽. + return F(+0); + } else if (argument instanceof BooleanValue) { + // If argument is true, return 1𝔽. + if (argument === Value.true) { + return F(1); + } + // If argument is false, return +0𝔽. + return F(+0); + } else if (argument instanceof NumberValue) { + // Return argument (no conversion). + return argument; + } else if (argument instanceof JSStringValue) { + return MV_StringNumericLiteral(argument.stringValue()); + } else if (argument instanceof BigIntValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotMixBigInts'); + } else if (argument instanceof SymbolValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertSymbol', 'number'); + } else if (argument instanceof ObjectValue) { + // 1. Let primValue be ? ToPrimitive(argument, number). + const primValue = Q(yield* ToPrimitive(argument, 'number')); + // 2. Return ? ToNumber(primValue). + return Q(yield* ToNumber(primValue)); + } + throw new OutOfRange('ToNumber', { argument }); +} + +const mod = (n: number, m: number) => { + const r = n % m; + return Math.floor(r >= 0 ? r : r + m); +}; + +/** https://tc39.es/ecma262/#sec-tointegerorinfinity */ +export function* ToIntegerOrInfinity(argument: Value): PlainEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = Q(yield* ToNumber(argument)); + // 2. If number is NaN, +0𝔽, or -0𝔽, return 0. + if (number.isNaN() || R(number) === 0) { + return +0; + } + // 3. If number is +∞𝔽, return +∞. + // 4. If number is -∞𝔽, return -∞. + if (!number.isFinite()) { + return R(number); + } + // 4. Let integer be floor(abs(ℝ(number))). + let integer = Math.floor(Math.abs(R(number))); + // 5. If number < +0𝔽, set integer to -integer. + if (R(number) < 0 && integer !== 0) { + integer = -integer; + } + // 6. Return integer. + return integer; +} + +/** https://tc39.es/ecma262/#sec-toint32 */ +export function* ToInt32(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return F(+0); + } + // 3. Let int be truncate(ℝ(number)). + const int = Math.trunc(number); + // 4. Let int32bit be int modulo 2^32. + const int32bit = mod(int, 2 ** 32); + // 5. If int32bit ≥ 2^31, return 𝔽(int32bit - 2^32); otherwise return 𝔽(int32bit). + if (int32bit >= (2 ** 31)) { + return F(int32bit - (2 ** 32)); + } + return F(int32bit); +} + +/** https://tc39.es/ecma262/#sec-touint32 */ +export function* ToUint32(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return F(+0); + } + // 3. Let int be truncate(ℝ(number)). + const int = Math.trunc(number); + // 4. Let int32bit be int modulo 2^32. + const int32bit = mod(int, 2 ** 32); + // 5. Return 𝔽(int32bit). + return F(int32bit); +} + +/** https://tc39.es/ecma262/#sec-toint16 */ +export function* ToInt16(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return F(+0); + } + // 3. Let int be truncate(ℝ(number)). + const int = Math.trunc(number); + // 4. Let int16bit be int modulo 2^16. + const int16bit = mod(int, 2 ** 16); + // 5. If int16bit ≥ 2^31, return 𝔽(int16bit - 2^32); otherwise return 𝔽(int16bit). + if (int16bit >= (2 ** 15)) { + return F(int16bit - (2 ** 16)); + } + return F(int16bit); +} + +/** https://tc39.es/ecma262/#sec-touint16 */ +export function* ToUint16(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return F(+0); + } + // 3. Let int be truncate(ℝ(number)). + const int = Math.trunc(number); + // 4. Let int16bit be int modulo 2^16. + const int16bit = mod(int, 2 ** 16); + // 5. Return 𝔽(int16bit). + return F(int16bit); +} + +/** https://tc39.es/ecma262/#sec-toint8 */ +export function* ToInt8(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return F(+0); + } + // 3. Let int be truncate(ℝ(number)). + const int = Math.trunc(number); + // 4. Let int8bit be int modulo 2^8. + const int8bit = mod(int, 2 ** 8); + // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). + if (int8bit >= (2 ** 7)) { + return F(int8bit - (2 ** 8)); + } + return F(int8bit); +} + +/** https://tc39.es/ecma262/#sec-touint8 */ +export function* ToUint8(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if (Number.isNaN(number) || number === 0 || !Number.isFinite(number)) { + return F(+0); + } + // 3. Let int be truncate(ℝ(number)). + const int = Math.trunc(number); + // 4. Let int8bit be int modulo 2^8. + const int8bit = mod(int, 2 ** 8); + // 5. Return 𝔽(int8bit). + return F(int8bit); +} + +/** https://tc39.es/ecma262/#sec-touint8clamp */ +export function* ToUint8Clamp(argument: Value): ValueEvaluator { + // 1. Let number be ? ToNumber(argument). + const number = R(Q(yield* ToNumber(argument))); + // 2. If number is NaN, return +0𝔽. + if (Number.isNaN(number)) { + return F(+0); + } + // 3. If ℝ(number) ≤ 0, return +0𝔽. + if (number <= 0) { + return F(+0); + } + // 4. If ℝ(number) ≥ 255, return 255𝔽. + if (number >= 255) { + return F(255); + } + // 5. Let f be floor(ℝ(number)). + const f = Math.floor(number); + // 6. If f + 0.5 < ℝ(number), return 𝔽(f + 1). + if (f + 0.5 < number) { + return F(f + 1); + } + // 7. If ℝ(number) < f + 0.5, return 𝔽(f). + if (number < f + 0.5) { + return F(f); + } + // 8. If f is odd, return 𝔽(f + 1). + if (f % 2 === 1) { + return F(f + 1); + } + // 9. Return 𝔽(f). + return F(f); +} + +/** https://tc39.es/ecma262/#sec-tobigint */ +export function* ToBigInt(argument: Value): ValueEvaluator { + // 1. Let prim be ? ToPrimitive(argument, number). + const prim = Q(yield* ToPrimitive(argument, 'number')); + // 2. Return the value that prim corresponds to in Table 12 (#table-tobigint). + if (prim instanceof UndefinedValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToBigInt', prim); + } else if (prim instanceof NullValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToBigInt', prim); + } else if (prim instanceof BooleanValue) { + // Return 1ℤ if prim is true and 0ℤ if prim is false. + if (prim === Value.true) { + return Z(1n); + } + return Z(0n); + } else if (prim instanceof BigIntValue) { + // Return prim. + return prim; + } else if (prim instanceof NumberValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToBigInt', prim); + } else if (prim instanceof JSStringValue) { + // 1. Let n be StringToBigInt(prim). + const n = StringToBigInt(prim); + // 2. If n is NaN, throw a SyntaxError exception. + if (n === undefined) { + return surroundingAgent.Throw('SyntaxError', 'CannotConvertToBigInt', prim); + } + // 3. Return n. + return n; + } else if (prim instanceof SymbolValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertSymbol', 'bigint'); + } + throw new OutOfRange('ToBigInt', argument); +} + +/** https://tc39.es/ecma262/#sec-stringtobigint */ +export function StringToBigInt(argument: JSStringValue) { + try { + return Z(BigInt(argument.stringValue())); + } catch { + return undefined; + } +} + +/** https://tc39.es/ecma262/#sec-tobigint64 */ +export function* ToBigInt64(argument: Value): ValueEvaluator { + // 1. Let n be ? ToBigInt(argument). + const n = Q(yield* (ToBigInt(argument))); + // 2. Let int64bit be ℝ(n) modulo 2^64. + const int64bit = R(n) % (2n ** 64n); + // 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit). + if (int64bit >= 2n ** 63n) { + return Z(int64bit - (2n ** 64n)); + } + return Z(int64bit); +} + +/** https://tc39.es/ecma262/#sec-tobiguint64 */ +export function* ToBigUint64(argument: Value): ValueEvaluator { + // 1. Let n be ? ToBigInt(argument). + const n = Q(yield* (ToBigInt(argument))); + // 2. Let int64bit be ℝ(n) modulo 2^64. + const int64bit = R(n) % (2n ** 64n); + // 3. Return ℤ(int64bit). + return Z(int64bit); +} + +/** https://tc39.es/ecma262/#sec-tostring */ +export function* ToString(argument: Value): ValueEvaluator { + if (argument instanceof UndefinedValue) { + // Return "undefined". + return Value('undefined'); + } else if (argument instanceof NullValue) { + // Return "null". + return Value('null'); + } else if (argument instanceof BooleanValue) { + // If argument is true, return "true". + // If argument is false, return "false". + return Value(argument === Value.true ? 'true' : 'false'); + } else if (argument instanceof NumberValue) { + // Return ! Number::toString(argument). + return X(NumberValue.toString(argument, 10)); + } else if (argument instanceof JSStringValue) { + // Return argument. + return argument; + } else if (argument instanceof SymbolValue) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertSymbol', 'string'); + } else if (argument instanceof BigIntValue) { + // Return ! BigInt::toString(argument). + return X(BigIntValue.toString(argument, 10)); + } else if (argument instanceof ObjectValue) { + // 1. Let primValue be ? ToPrimitive(argument, string). + const primValue = Q(yield* ToPrimitive(argument, 'string')); + // 2. Return ? ToString(primValue). + return Q(yield* ToString(primValue)); + } + throw new OutOfRange('ToString', { argument }); +} + +/** https://tc39.es/ecma262/#sec-toobject */ +export function ToObject(argument: Value): ValueCompletion { + if (argument === Value.undefined) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'undefined'); + } else if (argument === Value.null) { + // Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'CannotConvertToObject', 'null'); + } else if (argument instanceof BooleanValue) { + // Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Boolean.prototype%'), ['BooleanData']) as Mutable; + obj.BooleanData = argument; + return obj; + } else if (argument instanceof NumberValue) { + // Return a new Number object whose [[NumberData]] internal slot is set to argument. + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Number.prototype%'), ['NumberData']) as Mutable; + obj.NumberData = argument; + return obj; + } else if (argument instanceof JSStringValue) { + // Return a new String object whose [[StringData]] internal slot is set to argument. + return StringCreate(argument, surroundingAgent.intrinsic('%String.prototype%')); + } else if (argument instanceof SymbolValue) { + // Return a new Symbol object whose [[SymbolData]] internal slot is set to argument. + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Symbol.prototype%'), ['SymbolData']) as Mutable; + obj.SymbolData = argument; + return obj; + } else if (argument instanceof BigIntValue) { + // Return a new BigInt object whose [[BigIntData]] internal slot is set to argument. + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%BigInt.prototype%'), ['BigIntData']) as Mutable; + obj.BigIntData = argument; + return obj; + } + Assert(argument instanceof ObjectValue); + return argument; +} + +/** https://tc39.es/ecma262/#sec-topropertykey */ +export function* ToPropertyKey(argument: Value): ValueEvaluator { + // 1. Let key be ? ToPrimitive(argument, string). + const key = Q(yield* ToPrimitive(argument, 'string')); + // 2. If Type(key) is Symbol, then + if (key instanceof SymbolValue) { + // a. Return key. + return key; + } + // 3. Return ! ToString(key). + return X(ToString(key)); +} + +/** https://tc39.es/ecma262/#sec-tolength */ +export function* ToLength(argument: Value): ValueEvaluator { + // 1. Let len be ? ToIntegerOrInfinity(argument). + const len = Q(yield* ToIntegerOrInfinity(argument)); + // 2. If len ≤ 0, return +0𝔽. + if (len <= 0) { + return F(+0); + } + // 3. Return 𝔽(min(len, 253 - 1)). + return F(Math.min(len, (2 ** 53) - 1)); +} + +/** https://tc39.es/ecma262/#sec-canonicalnumericindexstring */ +export function CanonicalNumericIndexString(argument: Value) { + // 1. Assert: Type(argument) is String. + Assert(argument instanceof JSStringValue); + // 2. If argument is "-0", return -0𝔽. + if (argument.stringValue() === '-0') { + return F(-0); + } + // 3. Let n be ! ToNumber(argument). + const n = X(ToNumber(argument)); + // 4. If SameValue(! ToString(n), argument) is false, return undefined. + if (SameValue(X(ToString(n)), argument) === Value.false) { + return Value.undefined; + } + // 4. Return n. + return n; +} + +/** https://tc39.es/ecma262/#sec-toindex */ +export function* ToIndex(value: Value) { + // 1. If value is undefined, then + if (value instanceof UndefinedValue) { + // a. Return 0. + return 0; + } else { + // a. Let integerIndex be 𝔽(? ToIntegerOrInfinity(value)). + const integerIndex = F(Q(yield* ToIntegerOrInfinity(value))); + // b. If integerIndex < +0𝔽, throw a RangeError exception. + if (R(integerIndex) < 0) { + return surroundingAgent.Throw('RangeError', 'NegativeIndex', 'Index'); + } + // c. Let index be ! ToLength(integerIndex). + const index = X(ToLength(integerIndex)); + // d. If ! SameValue(integerIndex, index) is false, throw a RangeError exception. + if (X(SameValue(integerIndex, index)) === Value.false) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', 'Index'); + } + // e. Return ℝ(index). + return R(index); + } +} diff --git a/src/abstract-ops/typedarray-objects.mts b/src/abstract-ops/typedarray-objects.mts new file mode 100644 index 0000000..bf5d505 --- /dev/null +++ b/src/abstract-ops/typedarray-objects.mts @@ -0,0 +1,396 @@ +import { + ObjectValue, Value, NumberValue, + JSStringValue, + type ObjectInternalMethods, + SymbolValue, + Descriptor, + UndefinedValue, + BooleanValue, +} from '../value.mts'; +import { + Q, X, type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { + type TypedArrayObject, TypedArrayElementSize, TypedArrayElementType, +} from '../intrinsics/TypedArray.mts'; +import { isDataViewObject, type DataViewObject } from '../intrinsics/DataView.mts'; +import { + Assert, + IsDetachedBuffer, + R, + ArrayBufferByteLength, + IsFixedLengthArrayBuffer, + type ArrayBufferObject, + MakeBasicObject, + isIntegerIndex, + ToString, + OrdinaryDelete, + CanonicalNumericIndexString, + F, + IsAccessorDescriptor, + OrdinaryDefineOwnProperty, + OrdinaryGet, + OrdinaryGetOwnProperty, + OrdinaryHasProperty, + OrdinarySet, + GetValueFromBuffer, + SetValueInBuffer, + ToBigInt, + ToNumber, + IsSharedArrayBuffer, + OrdinaryPreventExtensions, + SameValue, + IsIntegralNumber, + IsViewOutOfBounds, + MakeDataViewWithBufferWitnessRecord, +} from './all.mts'; + +const InternalMethods = { + /** https://tc39.es/ecma262/#sec-typedarray-preventextensions */ + * PreventExtensions() { + const O = this; + if (!IsTypedArrayFixedLength(O)) { + return Value.false; + } + return OrdinaryPreventExtensions(O); + }, + /** https://tc39.es/ecma262/#sec-typedarray-getownproperty */ + * GetOwnProperty(P) { + const O = this; + // 3. If Type(P) is String, then + if (P instanceof JSStringValue) { + // a. Let numericIndex be CanonicalNumericIndexString(P). + const numericIndex = CanonicalNumericIndexString(P); + // b. If numericIndex is not undefined, then + if (!(numericIndex instanceof UndefinedValue)) { + // i. Let value be TypedArrayGetElement(O, numericIndex). + const value = TypedArrayGetElement(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]]: true }. + return Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.true, + Configurable: Value.true, + }); + } + } + // 4. Return OrdinaryGetOwnProperty(O, P). + return OrdinaryGetOwnProperty(O, P); + }, + /** https://tc39.es/ecma262/#sec-typedarray-hasproperty */ + * HasProperty(P) { + const O = this; + // 3. If Type(P) is String, then + if (P instanceof JSStringValue) { + // a. Let numericIndex be CanonicalNumericIndexString(P). + const numericIndex = CanonicalNumericIndexString(P); + // b. If numericIndex is not undefined, then + if (!(numericIndex instanceof UndefinedValue)) { + return IsValidIntegerIndex(O, numericIndex); + } + } + // 4. Return ? OrdinaryHasProperty(O, P) + return Q(yield* OrdinaryHasProperty(O, P)); + }, + /** https://tc39.es/ecma262/#sec-typedarray-defineownproperty */ + * DefineOwnProperty(P, Desc) { + const O = this; + // 3. If Type(P) is String, then + if (P instanceof JSStringValue) { + // a. Let numericIndex be CanonicalNumericIndexString(P). + const numericIndex = CanonicalNumericIndexString(P); + // b. If numericIndex is not undefined, then + if (!(numericIndex instanceof UndefinedValue)) { + // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false. + if (IsValidIntegerIndex(O, numericIndex) === Value.false) { + return Value.false; + } + // iii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is true, return false. + if (Desc.Configurable === Value.false) { + 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; + } + // ii. If IsAccessorDescriptor(Desc) is true, return false. + if (IsAccessorDescriptor(Desc)) { + 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) { + return Q(yield* TypedArraySetElement(O, numericIndex, Desc.Value)); + } + // vii. Return true. + return Value.true; + } + } + // 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc). + return Q(yield* OrdinaryDefineOwnProperty(O, P, Desc)); + }, + /** https://tc39.es/ecma262/#sec-typedarray-get */ + * Get(P, Receiver) { + const O = this; + // 2. If Type(P) is String, then + if (P instanceof JSStringValue) { + // a. Let numericIndex be CanonicalNumericIndexString(P). + const numericIndex = CanonicalNumericIndexString(P); + // b. If numericIndex is not undefined, then + if (!(numericIndex instanceof UndefinedValue)) { + // i. Return ! IntegerIndexedElementGet(O, numericIndex). + return X(TypedArrayGetElement(O, numericIndex)); + } + } + // 3. Return ? OrdinaryGet(O, P, Receiver). + return Q(yield* OrdinaryGet(O, P, Receiver)); + }, + /** https://tc39.es/ecma262/#sec-typedarray-set */ + * Set(P, V, Receiver) { + const O = this; + // 2. If Type(P) is String, then + if (P instanceof JSStringValue) { + // a. Let numericIndex be CanonicalNumericIndexString(P). + const numericIndex = CanonicalNumericIndexString(P); + // b. If numericIndex is not undefined, then + if (!(numericIndex instanceof UndefinedValue)) { + if (SameValue(O, Receiver) === Value.true) { + // i. Perform ? IntegerIndexedElementSet(O, numericIndex, V). + Q(yield* TypedArraySetElement(O, numericIndex, V)); + // ii. Return true. + return Value.true; + } + if (IsValidIntegerIndex(O, numericIndex) === Value.false) { + return Value.true; + } + } + } + // 3. Return ? OrdinarySet(O, P, V, Receiver). + return Q(yield* OrdinarySet(O, P, V, Receiver)); + }, + /** https://tc39.es/ecma262/#sec-typedarray-delete */ + * Delete(P) { + const O = this; + // 3. If Type(P) is String, then + if (P instanceof JSStringValue) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + const numericIndex = CanonicalNumericIndexString(P); + // b. If numericIndex is not undefined, then + if (!(numericIndex instanceof UndefinedValue)) { + // ii. If IsValidIntegerIndex(O, numericIndex) is false, return true. + if (IsValidIntegerIndex(O, numericIndex) === Value.false) { + return Value.true; + } else { + // iii. Return false. + return Value.false; + } + } + } + // 4. Return ? OrdinaryDelete(O, P). + return Q(yield* OrdinaryDelete(O, P)); + }, + /** https://tc39.es/ecma262/#sec-typedarray-ownpropertykeys */ + * OwnPropertyKeys() { + const O = this; + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + // 1. Let keys be a new empty List. + const keys = []; + if (!IsTypedArrayOutOfBounds(taRecord)) { + const length = TypedArrayLength(taRecord); + // 4. For each integer i starting with 0 such that i < len, in ascending order, do + for (let i = 0; i < length; i += 1) { + // a. Add ! ToString(𝔽(i)) as the last element of keys. + keys.push(X(ToString(F(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 (P instanceof JSStringValue) { + 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 (P instanceof SymbolValue) { + // a. Add P as the last element of keys. + keys.push(P); + } + } + // 7. Return keys. + return keys; + }, +} satisfies Partial>; + +/** https://tc39.es/ecma262/#sec-typedarray-with-buffer-witness-records */ +export interface TypedArrayWithBufferWitnessRecord { + readonly Object: TypedArrayObject; + readonly CachedBufferByteLength: 'detached' | number; +} + +/** https://tc39.es/ecma262/#sec-maketypedarraywithbufferwitnessrecord */ +export function MakeTypedArrayWithBufferWitnessRecord(obj: TypedArrayObject, order: 'seq-cst' | 'unordered') { + const buffer = obj.ViewedArrayBuffer; + let byteLength: TypedArrayWithBufferWitnessRecord['CachedBufferByteLength']; + if (IsDetachedBuffer(buffer as ArrayBufferObject)) { + byteLength = 'detached'; + } else { + byteLength = ArrayBufferByteLength(buffer as ArrayBufferObject, order); + } + return { Object: obj, CachedBufferByteLength: byteLength }; +} + +/** https://tc39.es/ecma262/#sec-typedarraycreate */ +export function TypedArrayCreate(prototype: ObjectValue) { + const internalSlotsList = ['Prototype', 'Extensible', 'ViewedArrayBuffer', 'TypedArrayName', 'ContentType', 'ByteLength', 'ByteOffset', 'ArrayLength'] as const; + const A = MakeBasicObject(internalSlotsList); + A.PreventExtensions = InternalMethods.PreventExtensions; + A.GetOwnProperty = InternalMethods.GetOwnProperty; + A.HasProperty = InternalMethods.HasProperty; + A.DefineOwnProperty = InternalMethods.DefineOwnProperty; + A.Get = InternalMethods.Get; + A.Set = InternalMethods.Set; + A.Delete = InternalMethods.Delete; + A.OwnPropertyKeys = InternalMethods.OwnPropertyKeys; + A.Prototype = prototype; + return A; +} + +/** https://tc39.es/ecma262/#sec-typedarraybytelength */ +export function TypedArrayByteLength(taRecord: TypedArrayWithBufferWitnessRecord): number { + Assert(!IsTypedArrayOutOfBounds(taRecord)); + const O = taRecord.Object; + if (O.ByteLength !== 'auto') { + return O.ByteLength; + } + const length = TypedArrayLength(taRecord); + const elementSize = TypedArrayElementSize(O); + return length * elementSize; +} + +/** https://tc39.es/ecma262/#sec-typedarraylength */ +export function TypedArrayLength(taRecord: TypedArrayWithBufferWitnessRecord): number { + Assert(IsTypedArrayOutOfBounds(taRecord) === false); + const O = taRecord.Object; + if (O.ArrayLength !== 'auto') { + return O.ArrayLength; + } + Assert(!IsFixedLengthArrayBuffer(O.ViewedArrayBuffer as ArrayBufferObject)); + const byteOffset = O.ByteOffset; + const elementSize = TypedArrayElementSize(O); + const bufferLength = taRecord.CachedBufferByteLength; + Assert(bufferLength !== 'detached'); + return Math.floor((bufferLength - byteOffset) / elementSize); +} + +/** https://tc39.es/ecma262/#sec-istypedarrayoutofbounds */ +export function IsTypedArrayOutOfBounds(taRecord: TypedArrayWithBufferWitnessRecord) { + const O = taRecord.Object; + const bufferByteLength = taRecord.CachedBufferByteLength; + if (IsDetachedBuffer(O.ViewedArrayBuffer as ArrayBufferObject)) { + Assert(bufferByteLength === 'detached'); + return true; + } + Assert(typeof bufferByteLength === 'number' && bufferByteLength >= 0); + const byteOffsetStart = O.ByteOffset; + let byteOffsetEnd; + if (O.ArrayLength === 'auto') { + byteOffsetEnd = bufferByteLength; + } else { + const elementSize = TypedArrayElementSize(O); + const arrayByteLength = O.ArrayLength * elementSize; + byteOffsetEnd = byteOffsetStart + arrayByteLength; + } + if (byteOffsetStart > bufferByteLength || byteOffsetEnd > bufferByteLength) { + return true; + } + return false; +} + +/** https://tc39.es/ecma262/#sec-istypedarrayfixedlength */ +export function IsTypedArrayFixedLength(O: TypedArrayObject) { + if (O.ArrayLength === 'auto') { + return false; + } + const buffer = O.ViewedArrayBuffer as ArrayBufferObject; + if (!IsFixedLengthArrayBuffer(buffer) && !IsSharedArrayBuffer(buffer)) { + return false; + } + return true; +} + +/** https://tc39.es/ecma262/#sec-isvalidintegerindex */ +export function IsValidIntegerIndex(O: TypedArrayObject, index: NumberValue) { + if (IsDetachedBuffer(O.ViewedArrayBuffer as ArrayBufferObject)) { + return Value.false; + } + if (IsIntegralNumber(index) === Value.false) { + return Value.false; + } + const index_ = R(index); + if (Object.is(index_, -0) || index_ < 0) { + return Value.false; + } + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return Value.false; + } + const length = TypedArrayLength(taRecord); + if (index_ >= length) { + return Value.false; + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-typedarraygetelement */ +export function TypedArrayGetElement(O: TypedArrayObject, index: NumberValue) { + if (IsValidIntegerIndex(O, index) === Value.false) { + return Value.undefined; + } + const offset = O.ByteOffset; + const elementSize = TypedArrayElementSize(O); + const byteIndexInBuffer = (R(index) * elementSize) + offset; + const elementType = TypedArrayElementType(O); + return GetValueFromBuffer(O.ViewedArrayBuffer as ArrayBufferObject, byteIndexInBuffer, elementType, true, 'unordered'); +} + +/** https://tc39.es/ecma262/#sec-integerindexedelementset */ +export function* TypedArraySetElement(O: TypedArrayObject, index: NumberValue, value: Value): ValueEvaluator { + // 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(yield* ToBigInt(value)); + } else { + numValue = Q(yield* ToNumber(value)); + } + if (IsValidIntegerIndex(O, index) === Value.true) { + const offset = O.ByteOffset; + const elementSize = TypedArrayElementSize(O); + const byteIndexInBuffer = (R(index) * elementSize) + offset; + const elementType = TypedArrayElementType(O); + Q(yield* SetValueInBuffer(O.ViewedArrayBuffer as ArrayBufferObject, byteIndexInBuffer, elementType, numValue, true, 'unordered')); + return Value.true; + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-isarraybufferviewoutofbounds */ +export function IsArrayBufferViewOutOfBounds(O: DataViewObject | TypedArrayObject) { + if (isDataViewObject(O)) { + const viewRecord = MakeDataViewWithBufferWitnessRecord(O, 'seq-cst'); + return IsViewOutOfBounds(viewRecord); + } + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + return IsTypedArrayOutOfBounds(taRecord); +} diff --git a/src/abstract-ops/weak-operations.mts b/src/abstract-ops/weak-operations.mts new file mode 100644 index 0000000..8b1de71 --- /dev/null +++ b/src/abstract-ops/weak-operations.mts @@ -0,0 +1,20 @@ +import { AddToKeptObjects } from '../execution-context/WeakReference.mts'; +import { + Value, + type WeakRefObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-weakrefderef */ +export function WeakRefDeref(weakRef: WeakRefObject) { + // 1. Let target be weakRef.[[WeakRefTarget]]. + const target = weakRef.WeakRefTarget; + // 2. If target is not empty, then + if (target !== undefined) { + // a. Perform ! AddToKeptObjects(target). + AddToKeptObjects(target); + // b. Return target. + return target; + } + // 3. Return undefined. + return Value.undefined; +} diff --git a/src/api.mts b/src/api.mts new file mode 100644 index 0000000..db5482e --- /dev/null +++ b/src/api.mts @@ -0,0 +1,477 @@ +import { ObjectValue, Value, type PropertyKeyValue } from './value.mts'; +import { + surroundingAgent, + ScriptEvaluation, + type Markable, +} from './host-defined/engine.mts'; +import { HostEnqueueFinalizationRegistryCleanupJob } from './execution-context/WeakReference.mts'; +import { AgentSignifier } from './execution-context/Agent.mts'; +import { ExecutionContext } from './execution-context/ExecutionContext.mts'; +import { + X, + ThrowCompletion, + AbruptCompletion, + type PlainCompletion, + type ValueCompletion, + NormalCompletion, + Q, +} from './completion.mts'; +import { + ParseScript, + ParseModule, + ParseJSONModule, + ScriptRecord, + type ParseScriptHostDefined, +} from './parse.mts'; +import { + AbstractModuleRecord, ModuleRecord, SourceTextModuleRecord, type ModuleRecordHostDefined, type ModuleRecordHostDefinedPublic, +} from './modules.mts'; +import { isWeakRef, type WeakRefObject } from './intrinsics/WeakRef.mts'; +import { isFinalizationRegistryObject, type FinalizationRegistryObject } from './intrinsics/FinalizationRegistry.mts'; +import { isWeakMapObject, type WeakMapObject } from './intrinsics/WeakMap.mts'; +import { isWeakSetObject, type WeakSetObject } from './intrinsics/WeakSet.mts'; +import type { PromiseObject } from './intrinsics/Promise.mts'; +import type { ParseNode } from './parser/ParseNode.mts'; +import { + ClearKeptObjects, + CreateIntrinsics, + SetDefaultGlobalBindings, + OrdinaryObjectCreate, + Assert, +} from '#self'; +import { + Realm, + EnsureCompletion, GetModuleNamespace, GlobalEnvironmentRecord, type Intrinsics, + type ValueEvaluator, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-weakref-execution */ +export function gc() { + // 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: WeakMapObject['WeakMapData'][number][] = []; + + const markCb = (O: unknown) => { + if (typeof O !== 'object' || O === null) { + return; + } + + if (marked.has(O)) { + return; + } + marked.add(O); + + if (isWeakRef(O)) { + weakrefs.add(O); + markCb(O.properties); + markCb(O.Prototype); + } else if (isFinalizationRegistryObject(O)) { + fgs.add(O); + markCb(O.properties); + markCb(O.Prototype); + O.Cells.forEach((cell) => { + markCb(cell.HeldValue); + }); + } else if (isWeakMapObject(O)) { + weakmaps.add(O); + markCb(O.properties); + markCb(O.Prototype); + O.WeakMapData.forEach((r) => { + ephemeronQueue.push(r); + }); + } else if (isWeakSetObject(O)) { + weaksets.add(O); + markCb(O.properties); + markCb(O.Prototype); + } else if ('mark' in O) { + (O as Markable).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 interface ManagedRealmHostDefined { + promiseRejectionTracker?(promise: PromiseObject, operation: 'reject' | 'handle'): void; + getImportMetaProperties?(module: ModuleRecordHostDefinedPublic): readonly { readonly Key: PropertyKeyValue, readonly Value: Value }[]; + finalizeImportMeta?(meta: ObjectValue, module: ModuleRecordHostDefinedPublic): PlainCompletion; + resolverCache?: Map; + + randomSeed?(): string; + attachingInspector?: unknown; + attachingInspectorReportError?(realm: Realm, error: Value): void; + /** + * See https://tc39.es/ecma262/#sec-HostLoadImportedModule + * In case of + * + * and + * new ShadowRealm().importValue('./foo.mjs', 'default') + * a Realm instead of a ModuleRecord or ScriptRecord is passed as the referrer. + */ + specifier?: string | undefined; + /** The name displayed in the inspector. */ + name?: string | undefined; +} +export class ManagedRealm extends Realm { + override TemplateMap: { Site: ParseNode.TemplateLiteral; Array: ObjectValue; }[]; + + override AgentSignifier: unknown; + + override Intrinsics: Intrinsics; + + override randomState: BigUint64Array | undefined; + + override GlobalObject: ObjectValue; + + override GlobalEnv: GlobalEnvironmentRecord; + + override HostDefined: ManagedRealmHostDefined; + + topContext: ExecutionContext; + + active = false; + + /** https://tc39.es/ecma262/#sec-initializehostdefinedrealm */ + constructor(HostDefined: ManagedRealmHostDefined = {}, customizations?: (record: Realm) => [global: ObjectValue | undefined, thisValue: ObjectValue | undefined]) { + super(); + this.Intrinsics = CreateIntrinsics(this); + this.AgentSignifier = AgentSignifier(); + this.TemplateMap = []; + let [global, thisValue] = customizations?.(this) || []; + if (!global) { + global = OrdinaryObjectCreate(this.Intrinsics['%Object.prototype%']); + } else { + Assert(global instanceof ObjectValue); + } + if (!thisValue) { + thisValue = global; + } else { + Assert(thisValue instanceof ObjectValue); + } + this.GlobalObject = global; + this.GlobalEnv = new GlobalEnvironmentRecord(global, thisValue); + SetDefaultGlobalBindings(this); + const newContext = new ExecutionContext(); + newContext.Function = Value.null; + newContext.Realm = this; + newContext.ScriptOrModule = Value.null; + this.HostDefined = HostDefined; + this.topContext = newContext; + + surroundingAgent.hostDefinedOptions.onRealmCreated?.(this); + } + + scope(inspectorPreview?: boolean): Disposable | null; + + scope(cb: () => T, inspectorPreview?: boolean): T + + scope(arg0?: (() => T) | boolean, arg2?: boolean): T | Disposable | null { + if (typeof arg0 !== 'function') { + const inspectorPreview = arg0; + if (this.active) { + return null; + } + this.active = true; + surroundingAgent.executionContextStack.push(this.topContext); + using _ = inspectorPreview ? surroundingAgent.debugger_scopePreview() : null; + return { + [Symbol.dispose]: () => { + surroundingAgent.executionContextStack.pop(this.topContext); + this.active = false; + }, + }; + } else { + const callback = arg0; + if (this.active) { + return arg0(); + } + this.active = true; + surroundingAgent.executionContextStack.push(this.topContext); + const result = arg2 ? surroundingAgent.debugger_scopePreview(callback) : callback(); + surroundingAgent.executionContextStack.pop(this.topContext); + this.active = false; + return result; + } + } + + compileScript(sourceText: string, hostDefined?: ParseScriptHostDefined): PlainCompletion { + return this.scope(() => { + const s = ParseScript(sourceText, this, hostDefined); + if (Array.isArray(s)) { + return ThrowCompletion(s[0]); + } + return NormalCompletion(s); + }); + } + + compileModule(sourceText: string, hostDefined?: ModuleRecordHostDefined) { + return this.scope(() => { + const s = ParseModule(sourceText, this, { + SourceTextModuleRecord: ManagedSourceTextModuleRecord, + ...hostDefined, + }); + if (Array.isArray(s)) { + return ThrowCompletion(s[0]); + } + return NormalCompletion(s); + }); + } + + /** + * Call surroundingAgent.resumeEvaluate() to continue evaluation. + * + * This function will synchronously return a completion if this is a nested evaluation and debugger cannot be triggered. + */ + evaluate(sourceText: ScriptRecord | ModuleRecord | ValueEvaluator, callback: (completion: NormalCompletion | ThrowCompletion) => void) { + if (!sourceText) { + throw new TypeError('sourceText is null or undefined'); + } + let result: ValueCompletion | undefined; + + if (sourceText instanceof ModuleRecord) { + const old = this.active; + this.active = true; + surroundingAgent.executionContextStack.push(this.topContext); + + const loadModuleCompletion = sourceText.LoadRequestedModules(); + const link = ((): PlainCompletion => { + if (loadModuleCompletion.PromiseState === 'rejected') { + Q(ThrowCompletion(loadModuleCompletion.PromiseResult!)); + } else if (loadModuleCompletion.PromiseState === 'pending') { + throw new Error('Internal error: .LoadRequestedModules() returned a pending promise'); + } + Q(sourceText.Link()); + })(); + if (link instanceof ThrowCompletion) { + callback(link); + return link; + } + surroundingAgent.evaluate(sourceText.Evaluate(), (completion) => { + if (completion instanceof NormalCompletion && completion.Value.PromiseState === 'fulfilled') { + result = GetModuleNamespace(sourceText, 'evaluation'); + } else { + result = completion; + } + this.active = old; + surroundingAgent.executionContextStack.pop(this.topContext); + callback(EnsureCompletion(result)); + }); + return result; + } else if (sourceText instanceof ScriptRecord) { + const old = this.active; + this.active = true; + surroundingAgent.executionContextStack.push(this.topContext); + + surroundingAgent.evaluate(ScriptEvaluation(sourceText), (completion) => { + this.active = old; + surroundingAgent.executionContextStack.pop(this.topContext); + result = completion; + callback(completion); + }); + return result; + } else { + // this path only called by the inspector + Assert(!!surroundingAgent.hostDefinedOptions.onDebugger); + let emptyExecutionStack = false; + if (!surroundingAgent.runningExecutionContext) { + emptyExecutionStack = true; + this.active = true; + surroundingAgent.executionContextStack.push(this.topContext); + } + surroundingAgent.evaluate(sourceText, (completion) => { + result = completion; + if (emptyExecutionStack) { + this.active = false; + surroundingAgent.executionContextStack.pop(this.topContext); + } + callback(completion); + }); + return result; + } + } + + evaluateScript(sourceText: string | ScriptRecord, { specifier, doNotTrackScriptId }: { specifier?: string, doNotTrackScriptId?: boolean } = {}): ValueCompletion { + if (sourceText === undefined || sourceText === null) { + throw new TypeError('sourceText must be a string or a ScriptRecord'); + } + if (typeof sourceText === 'string') { + sourceText = Q(this.compileScript(sourceText, { specifier, doNotTrackScriptId })); + } + + let completion; + completion = this.evaluate(sourceText, (c) => { + completion = c; + }); + if (!completion) { + surroundingAgent.resumeEvaluate({ + noBreakpoint: true, + }); + } + if (!completion) { + throw new Assert.Error('Expect evaluation completes synchronously'); + } + if (!(completion instanceof AbruptCompletion)) { + runJobQueue(); + } + + return completion; + } + + evaluateModule(sourceText: string, specifier: string): PlainCompletion + + evaluateModule(sourceText: T, specifier: string): PlainCompletion + + evaluateModule(sourceText: string | ModuleRecord, specifier: string): PlainCompletion { + if (sourceText === undefined || sourceText === null) { + throw new TypeError('sourceText must be a string or a ModuleRecord'); + } + if (typeof sourceText === 'string') { + sourceText = Q(this.compileModule(sourceText, { specifier })); + } + + let completion; + completion = this.evaluate(sourceText, (c) => { + completion = c; + if (!(completion instanceof AbruptCompletion)) { + runJobQueue(); + } + }); + if (!completion) { + surroundingAgent.resumeEvaluate({ + noBreakpoint: true, + }); + } + + return sourceText; + } + + /** + * @deprecated use compileModule + */ + createSourceTextModule(specifier: string, sourceText: string): PlainCompletion { + if (typeof specifier !== 'string') { + throw new TypeError('specifier must be a string'); + } + if (typeof sourceText !== 'string') { + throw new TypeError('sourceText must be a string'); + } + const module = this.scope(() => ParseModule(sourceText, this, { + specifier, + SourceTextModuleRecord: ManagedSourceTextModuleRecord, + })); + if (Array.isArray(module)) { + return ThrowCompletion(module[0]); + } + return module; + } + + createJSONModule(specifier: string, sourceText: string) { + if (typeof specifier !== 'string') { + throw new TypeError('specifier must be a string'); + } + if (typeof sourceText !== 'string') { + throw new TypeError('sourceText must be a string'); + } + const module = this.scope(() => ParseJSONModule(Value(sourceText), this, { + specifier, + })); + return module; + } +} + +class ManagedSourceTextModuleRecord extends SourceTextModuleRecord { + override* Evaluate() { + const r = yield* super.Evaluate(); + runJobQueue(); + return r; + } +} diff --git a/src/completion.mts b/src/completion.mts new file mode 100644 index 0000000..053de79 --- /dev/null +++ b/src/completion.mts @@ -0,0 +1,482 @@ +import { type GCMarker, surroundingAgent } from './host-defined/engine.mts'; +import { + JSStringValue, Value, type Arguments, +} from './value.mts'; +import { + callable, + kAsyncContext, + OutOfRange, + resume, +} from './helpers.mts'; +import type { Evaluator, ValueEvaluator } from './evaluator.mts'; +import { + Assert, + CreateBuiltinFunction, + PerformPromiseThen, + PromiseCapabilityRecord, + PromiseResolve, + type IteratorRecord, +} from '#self'; +import { skipDebugger } from '#self'; + +let createNormalCompletion: (init: NormalCompletionInit) => NormalCompletionImpl; +let createBreakCompletion: (init: BreakCompletionInit) => BreakCompletion; +let createContinueCompletion: (init: ContinueCompletionInit) => ContinueCompletion; +let createReturnCompletion: (init: ReturnCompletionInit) => ReturnCompletion; +let createThrowCompletion: (init: ThrowCompletionInit) => ThrowCompletion_; + +type NormalCompletionInit = Pick, 'Type' | 'Value' | 'Target'>; + +type BreakCompletionInit = Pick; + +type ContinueCompletionInit = Pick; + +type ReturnCompletionInit = Pick; + +type ThrowCompletionInit = Pick; + +type AbruptCompletionInit = + | BreakCompletionInit + | ContinueCompletionInit + | ReturnCompletionInit + | ThrowCompletionInit; + +type CompletionInit = + | NormalCompletionInit + | AbruptCompletionInit; + +@callable((_target, _thisArg, [completionRecord]) => { + // 1. Assert: completionRecord is a Completion Record. + Assert(completionRecord instanceof Completion); + // 2. Return completionRecord as the Completion Record of this abstract operation. + return completionRecord; +}) +class CompletionImpl { + declare readonly Type: 'normal' | 'break' | 'continue' | 'return' | 'throw'; + + readonly Value!: T | Value; + + readonly Target!: JSStringValue | undefined; + + constructor(init: CompletionInit) { + if (new.target === CompletionImpl) { + switch (init.Type) { + case 'normal': + return createNormalCompletion(init); + case 'break': + return createBreakCompletion(init) as CompletionImpl; + case 'continue': + return createContinueCompletion(init) as CompletionImpl; + case 'return': + return createReturnCompletion(init) as CompletionImpl; + case 'throw': + return createThrowCompletion(init) as CompletionImpl; + default: + throw new OutOfRange('new Completion', init); + } + } + + const { Type, Value, Target } = init; + Assert(new.target.prototype.Type === Type); + this.Value = Value as T; + this.Target = Target; + } + + // NON-SPEC + mark(m: GCMarker) { + m(this.Value); + } + + static { + Object.defineProperty(this, 'name', { value: 'Completion' }); + } +} + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export type Completion = + | NormalCompletion + | AbruptCompletion; + +/** + * A NON-SPEC shorthand to notate "returns either a normal completion containing an ECMAScript language value or a throw completion". + */ +// export type ValueEvaluator = T | NormalCompletion | ThrowCompletion; +export type ValueCompletion = T | NormalCompletion | ThrowCompletion; +export { type ValueEvaluator } from './evaluator.mts'; +/** + * A NON-SPEC shorthand to notate "returns either a normal completion containing ... or a throw completion". + * + * If the T is an ECMAScript language value, use ExpressionCompletion. + */ +export type PlainCompletion = T | NormalCompletion | ThrowCompletion; +export type YieldCompletion = NormalCompletion | ThrowCompletion | ReturnCompletion; + +/** https://tc39.es/ecma262/#sec-completion-ao */ +export const Completion = CompletionImpl as { + /** https://tc39.es/ecma262/#sec-completion-ao */ + >(completionRecord: T): T; + + /** https://tc39.es/ecma262/#sec-completion-record-specification-type */ + new (completion: { Type: 'normal', Value: T, Target: undefined }): NormalCompletion; + new(completion: { Type: 'break', Value: void, Target: JSStringValue | undefined }): BreakCompletion; + new(completion: { Type: 'continue', Value: void, Target: JSStringValue | undefined }): ContinueCompletion; + new(completion: { Type: 'return', Value: Value, Target: undefined }): ReturnCompletion; + new(completion: { Type: 'throw', Value: Value, Target: undefined }): ThrowCompletion; + readonly prototype: CompletionImpl; +}; + +@callable((_target, _thisArg, [value]) => { // eslint-disable-line arrow-body-style -- Preserve algorithm steps comments + // 1. Return Completion { [[Type]]: normal, [[Value]]: value, [[Target]]: empty }. + return new Completion({ Type: 'normal', Value: value, Target: undefined }); +}) +class NormalCompletionImpl extends CompletionImpl { + declare readonly Type: 'normal'; + + declare readonly Value: T; + + declare readonly Target: undefined; + + private constructor(init: NormalCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(init); + } + + static { + Object.defineProperty(this, 'name', { value: 'NormalCompletion' }); + Object.defineProperty(this.prototype, 'Type', { value: 'normal' }); + createNormalCompletion = (init) => new NormalCompletionImpl(init); + } +} + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export type NormalCompletion = NormalCompletionImpl; + +/** https://tc39.es/ecma262/#sec-normalcompletion */ +export const NormalCompletion = NormalCompletionImpl as typeof NormalCompletionImpl & { + /** https://tc39.es/ecma262/#sec-normalcompletion */ + (value: T): NormalCompletion; +}; + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export type AbruptCompletion = + | ThrowCompletion + | ReturnCompletion + | BreakCompletion + | ContinueCompletion; + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export const AbruptCompletion = (() => { + abstract class AbruptCompletion extends CompletionImpl { + declare readonly Type: 'break' | 'continue' | 'return' | 'throw'; + + declare readonly Value: T | Value; + + declare readonly Target: JSStringValue | undefined; + + constructor(init: AbruptCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(init); + } + + static { + Object.defineProperty(this, 'name', { value: 'AbruptCompletion' }); + } + } + + return AbruptCompletion; +})(); + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export class BreakCompletion extends AbruptCompletion { + declare readonly Type: 'break'; + + declare readonly Value: void; + + private constructor(init: BreakCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(init); + } + + static { + Object.defineProperty(this, 'name', { value: 'BreakCompletion' }); + Object.defineProperty(this.prototype, 'Type', { value: 'break' }); + createBreakCompletion = (init) => new BreakCompletion(init); + } +} + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export class ContinueCompletion extends AbruptCompletion { + declare readonly Type: 'continue'; + + declare readonly Value: void; + + declare readonly Target: JSStringValue | undefined; + + private constructor(init: ContinueCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(init); + } + + static { + Object.defineProperty(this, 'name', { value: 'ContinueCompletion' }); + Object.defineProperty(this.prototype, 'Type', { value: 'continue' }); + createContinueCompletion = (init) => new ContinueCompletion(init); + } +} + +@callable((_target, _thisArg, [value]) => { + Assert(value instanceof Value); + // 1. Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: value as Value, Target: undefined }); +}) +class ReturnCompletion_ extends AbruptCompletion { + declare readonly Type: 'return'; + + declare readonly Value: Value; + + declare readonly Target: undefined; + + private constructor(init: ReturnCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(init); + } + + static { + Object.defineProperty(this, 'name', { value: 'ReturnCompletion' }); + Object.defineProperty(this.prototype, 'Type', { value: 'return' }); + createReturnCompletion = (init) => new ReturnCompletion(init); + } +} + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export type ReturnCompletion = ReturnCompletion_; + +/** https://tc39.es/ecma262/#sec-throwcompletion */ +export const ReturnCompletion = ReturnCompletion_ as typeof ReturnCompletion_ & { + /** https://tc39.es/ecma262/#sec-throwcompletion */ + (value: Value): ThrowCompletion; +}; + +const debugging = false; +@callable((_target, _thisArg, [value]) => { + Assert(value instanceof Value); + // 1. Return Completion { [[Type]]: throw, [[Value]]: value, [[Target]]: empty }. + return new Completion({ Type: 'throw', Value: value as Value, Target: undefined }); +}) +class ThrowCompletion_ extends AbruptCompletion { + declare readonly Type: 'throw'; + + declare readonly Value: Value; + + declare readonly Target: undefined; + + readonly stack = debugging ? new Error() : undefined; + + private constructor(init: Pick) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(init); + if (debugging) { + Error.stackTraceLimit = Infinity; + } + } + + static { + Object.defineProperty(this, 'name', { value: 'ThrowCompletion' }); + Object.defineProperty(this.prototype, 'Type', { value: 'throw' }); + createThrowCompletion = (init) => new ThrowCompletion_(init); + } +} + +/** https://tc39.es/ecma262/#sec-completion-record-specification-type */ +export type ThrowCompletion = ThrowCompletion_; + +/** https://tc39.es/ecma262/#sec-throwcompletion */ +export const ThrowCompletion = ThrowCompletion_ as typeof ThrowCompletion_ & { + /** https://tc39.es/ecma262/#sec-throwcompletion */ + (value: Value): ThrowCompletion; +}; + +/** https://tc39.es/ecma262/#sec-updateempty */ +export type UpdateEmpty, U> = + T extends NormalCompletion ? NormalCompletion : + T extends BreakCompletion ? BreakCompletion : + T extends ContinueCompletion ? ContinueCompletion : + T extends AbruptCompletion ? T : + T extends ReturnCompletion ? T : + never; + +/** https://tc39.es/ecma262/#sec-updateempty */ +export function UpdateEmpty, const T>(completionRecord: C, value: T): UpdateEmpty; +export function UpdateEmpty, const T>(completionRecord: C, value: T) { + // 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 CompletionImpl({ Type: completionRecord.Type, Value: value, Target: completionRecord.Target } as unknown as CompletionInit); // NOTE: unsound cast +} + +/** https://tc39.es/ecma262/#sec-returnifabrupt */ +export type Q = + T extends NormalCompletion ? V : + T extends AbruptCompletion ? never : + T; + +/** + * https://tc39.es/ecma262/#sec-returnifabrupt + * https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ? OperationName() + */ +export function Q(_completion: T): Q { + /* node:coverage ignore next */ + throw new TypeError('Q requires build'); +} + +function Q_runtime(completion: T): Q { + /* node:coverage ignore next 3 */ + if (typeof completion === 'object' && completion && 'next' in completion) { + throw new TypeError('Forgot to yield* on the completion.'); + } + const c = EnsureCompletion(completion); + if (c.Type === 'normal') { + return c.Value as Q; + } + throw c; +} + +/** https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ! OperationName() */ +export function X(_completion: T | Evaluator): Q { + /* node:coverage ignore next */ + throw new TypeError('X() requires build'); +} + +export function unwrapCompletion(completion: T | Evaluator): Q { + /* node:coverage ignore next 3 */ + if (typeof completion === 'object' && completion && 'next' in completion) { + completion = skipDebugger(completion); + } + const c = EnsureCompletion(completion); + if (c instanceof NormalCompletion) { + return c.Value as Q; + } + /* node:coverage ignore next */ + throw new Assert.Error('Unexpected AbruptCompletion.', { cause: c }); +} + +/** https://tc39.es/ecma262/#sec-ifabruptcloseiterator */ +export function IfAbruptCloseIterator(_value: T, _iteratorRecord: IteratorRecord): Q { + /* node:coverage ignore next */ + throw new TypeError('IfAbruptCloseIterator() requires build'); +} + +/** https://tc39.es/ecma262/#sec-ifabruptcloseasynciterator */ +export function IfAbruptCloseAsyncIterator(_value: T, _iteratorRecord: IteratorRecord): Q { + /* node:coverage ignore next */ + throw new TypeError('IfAbruptCloseAsyncIterator() requires build'); +} + +/** https://tc39.es/ecma262/#sec-ifabruptrejectpromise */ +export function IfAbruptRejectPromise(_value: T, _capability: PromiseCapabilityRecord): Q { + /* node:coverage ignore next */ + throw new TypeError('IfAbruptRejectPromise requires build'); +} + +/** + * This is a util for code that cannot use Q() or X() marco to emulate this behaviour. + * + * @example + * import { evalQ } from '...' + * evalQ((Q) => { + * let val = Q(operation); + * }); + */ +export function evalQ(callback: (q: typeof Q, x: typeof X) => Promise): Promise | ThrowCompletion> +export function evalQ(callback: (q: typeof Q, x: typeof X) => T): NormalCompletion | ThrowCompletion +export function evalQ(callback: (q: typeof Q, x: typeof X) => T | Promise): Promise | ThrowCompletion> | NormalCompletion | ThrowCompletion { + try { + const result = callback(Q_runtime, unwrapCompletion); + if (result instanceof Promise) { + return result.then(EnsureCompletion, (error) => { + if (error instanceof ThrowCompletion) { + return error; + } + throw error; + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return EnsureCompletion(result) as any; + } catch (error) { + if (error instanceof ThrowCompletion) { + return error; + } + // a real error + throw error; + } +} + +export type EnsureCompletion = EnsureCompletionWorker; + +// Distribute over `T`s that are `Completion`s, but don't distribute over `T`s that aren't `Completion`s +type EnsureCompletionWorker = T extends Completion ? T : NormalCompletion>>; + +/** https://tc39.es/ecma262/#sec-implicit-normal-completion */ +export function EnsureCompletion(val: Value): NormalCompletion; +export function EnsureCompletion(val: T): EnsureCompletion; +export function EnsureCompletion(val: T) { + if (val instanceof Completion) { + return val; + } + return NormalCompletion(val); +} + +export function ValueOfNormalCompletion(value: NormalCompletion | T) { + return value instanceof NormalCompletion ? value.Value : value; +} + +export function* Await(value: Value): ValueEvaluator { + // 1. Let asyncContext be the running execution context. + const asyncContext = surroundingAgent.runningExecutionContext; + // 2. Let promise be ? PromiseResolve(%Promise%, value). + const promise = Q(yield* PromiseResolve(surroundingAgent.intrinsic('%Promise%'), value)); + // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: + const fulfilledClosure = function* fulfilledClosure([v = Value.undefined]: Arguments) { + // a. Let prevContext be the running execution context. + const prevContext = surroundingAgent.runningExecutionContext; + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + surroundingAgent.executionContextStack.push(asyncContext); + // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. + yield* resume(asyncContext, { type: 'await-resume', value: NormalCompletion(v) }); + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + Assert(surroundingAgent.runningExecutionContext === prevContext); + // f. Return undefined. + return Value.undefined; + }; + // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). + const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 1, Value(''), []); + // @ts-expect-error TODO(ts): CreateBuiltinFunction should return a specalized type FunctionObjectValue that has a kAsyncContext on it. + onFulfilled[kAsyncContext] = asyncContext; + // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: + const rejectedClosure = function* rejectedClosure([reason = Value.undefined]: Arguments) { + // a. Let prevContext be the running execution context. + const prevContext = surroundingAgent.runningExecutionContext; + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + surroundingAgent.executionContextStack.push(asyncContext); + // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. + yield* resume(asyncContext, { type: 'await-resume', value: ThrowCompletion(reason) }); + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + Assert(surroundingAgent.runningExecutionContext === prevContext); + // f. Return undefined. + return Value.undefined; + }; + // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + const onRejected = CreateBuiltinFunction(rejectedClosure, 1, Value(''), []); + // @ts-expect-error TODO(ts): CreateBuiltinFunction should return a specalized type FunctionObjectValue that has a kAsyncContext on it. + onRejected[kAsyncContext] = asyncContext; + // 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected). + X(PerformPromiseThen(promise, onFulfilled, onRejected)); + // 8. Remove asyncContext 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(asyncContext); + // 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available. + const completion = yield { type: 'await' }; + Assert(completion.type === 'await-resume'); + // 10. Return. + return completion.value; + // 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext. +} diff --git a/src/ecma402/not-implemented.mts b/src/ecma402/not-implemented.mts new file mode 100644 index 0000000..6aeb004 --- /dev/null +++ b/src/ecma402/not-implemented.mts @@ -0,0 +1,4 @@ +/** https://tc39.es/ecma402/#sec-canonicalizeuvalue */ +export function CanonicalizeUValue(_ukey: string, uvalue: string): string { + return uvalue; +} diff --git a/src/evaluator.mts b/src/evaluator.mts new file mode 100644 index 0000000..ceac432 --- /dev/null +++ b/src/evaluator.mts @@ -0,0 +1,353 @@ +import type { + NormalCompletion, PlainCompletion, ThrowCompletion, YieldCompletion, +} from './completion.mts'; +import { surroundingAgent } from './host-defined/engine.mts'; +import { OutOfRange } from './helpers.mts'; +import type { ParseNode } from './parser/ParseNode.mts'; +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.mts'; +import { avoid_using_children } from './parser/utils.mts'; +import { + type AbruptCompletion, Assert, type ReferenceRecord, type ReturnCompletion, Value, + type ValueCompletion, +} from '#self'; + +export type Evaluator = Generator; +export type PlainEvaluator = Evaluator>; +export type ValueEvaluator = Evaluator>; +export type ExpressionEvaluator = Evaluator>; +export type StatementEvaluator = Evaluator | AbruptCompletion>; +export type ReferenceEvaluator = Evaluator>; +export type YieldEvaluator = Evaluator; +export type AsyncBuiltinSteps = () => Evaluator | ThrowCompletion | ReturnCompletion>; +export type ExpressionThatEvaluatedToReferenceRecord = ParseNode.IdentifierReference; + +export function Evaluate(node: ExpressionThatEvaluatedToReferenceRecord): ReferenceEvaluator +export function Evaluate(node: ParseNode.Module | ParseNode.ScriptBody): ValueEvaluator +export function Evaluate(node: ParseNode.Expression): ExpressionEvaluator +export function Evaluate(node: ParseNode): StatementEvaluator +export function* Evaluate(node: ParseNode): Evaluator { + surroundingAgent.runningExecutionContext.callSite.setLocation(node); + + if (surroundingAgent.hostDefinedOptions.onNodeEvaluation) { + surroundingAgent.hostDefinedOptions.onNodeEvaluation(node, surroundingAgent.currentRealmRecord); + } + if (surroundingAgent.hostDefinedOptions.onDebugger) { + const resumption = yield { type: 'potential-debugger' }; + Assert(resumption.type === 'debugger-resume'); + } + + 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 yield* 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 yield* 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 yield* 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 Evaluate_FunctionExpression(node); + case 'ClassExpression': + return yield* Evaluate_ClassExpression(node); + case 'GeneratorExpression': + return Evaluate_GeneratorExpression(node); + case 'AsyncFunctionExpression': + return Evaluate_AsyncFunctionExpression(node); + case 'AsyncGeneratorExpression': + return 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': { + surroundingAgent.runningExecutionContext.callSite.setCallLocation(node); + const r = yield* Evaluate_CallExpression(node); + const resumption = yield { type: 'potential-debugger' }; + Assert(resumption.type === 'debugger-resume'); + surroundingAgent.runningExecutionContext.callSite.setCallLocation(null); + return r; + } + 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(); + 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 Evaluate_ArrowFunction(node); + case 'AsyncArrowFunction': + return Evaluate_AsyncArrowFunction(node); + case 'ConditionalExpression': + return yield* Evaluate_ConditionalExpression(node); + case 'RegularExpressionLiteral': + return yield* Evaluate_RegularExpressionLiteral(node); + case 'AsyncBody': + case 'GeneratorBody': + case 'AsyncGeneratorBody': + return yield* Evaluate_AnyFunctionBody(node); + case 'ExpressionBody': + return yield* Evaluate_ExpressionBody(node); + default: + throw new OutOfRange('Evaluate', node); + } +} + +export type EvaluatorYieldType = + | { type: 'debugger' } + | { type: 'potential-debugger' } + | { type: 'await' } + | { type: 'yield', value: Value } + | { type: 'async-generator-yield' } + +export type EvaluatorNextType = { + type: 'debugger-resume', + value: ValueCompletion | undefined +} | { + type: 'await-resume', + value: ValueCompletion +} | { + type: 'generator-resume', + value: ValueCompletion | ReturnCompletion +} | { + type: 'async-generator-resume', + value: ValueCompletion | ReturnCompletion +} + +export interface BreakpointLocation { + scriptId: string; + lineNumber: number; + columnNumber?: number; +} + +export function getBreakpointCandidates(from: BreakpointLocation, to?: BreakpointLocation, _restrictToFunction = false): BreakpointLocation[] { + const scriptId = from.scriptId; + const script = surroundingAgent.parsedSources.get(scriptId); + if (!script || (to && scriptId !== to.scriptId)) { + return []; + } + const node = script.ECMAScriptCode; + if (!('type' in node)) { + return []; + } + const nodes = [...yieldAllNodesIntersectWithRange(node, from, to)]; + return nodes.map((node): BreakpointLocation => ({ scriptId, lineNumber: node.location.start.line - 1, columnNumber: node.location.start.column - 1 })); +} + +function* yieldAllNodesIntersectWithRange(node: ParseNode, from: BreakpointLocation, to: BreakpointLocation | undefined): Generator { + const fromLine = from.lineNumber + 1; + const fromColumn = from.columnNumber !== undefined ? from.columnNumber + 1 : undefined; + const toLine = to ? to.lineNumber + 1 : fromLine; + const toColumn = to?.columnNumber !== undefined ? to.columnNumber + 1 : undefined; + if (node.location.end.line < fromLine) { + return; + } + if (fromColumn && node.location.end.line === fromLine && node.location.end.column < fromColumn) { + return; + } + if (toLine) { + if (node.location.start.line > toLine) { + return; + } + if (toColumn && node.location.start.line === toLine && node.location.start.column > toColumn) { + return; + } + } + // only yield the current node iff strictly in the range + if ( + node.location.start.line >= fromLine + && (fromColumn ? node.location.start.column >= fromColumn : true) + && (toLine ? node.location.end.line <= toLine && (toColumn ? node.location.end.column <= toColumn : true) : true) + ) { + yield node; + } + for (const child of avoid_using_children(node)) { + yield* yieldAllNodesIntersectWithRange(child, from, to); + } +} diff --git a/src/execution-context/Agent.mts b/src/execution-context/Agent.mts new file mode 100644 index 0000000..49b409a --- /dev/null +++ b/src/execution-context/Agent.mts @@ -0,0 +1,345 @@ +import type { Protocol } from 'devtools-protocol'; +import { shouldStepOnNode } from '../host-defined/debugger-util.mts'; +import { +} from '../host-defined/engine.mts'; +import * as messages from '../messages.mts'; +import { isArray } from '../helpers.mts'; +import { + ObjectValue, SymbolValue, type Job, type Intrinsics, type ErrorType, Value, ThrowCompletion, Throw, GetActiveScriptOrModule, type ValueEvaluator, NormalCompletion, EnsureCompletion, skipDebugger, type ValueCompletion, type ScriptRecord, SourceTextModuleRecord, Realm, X, Construct, + ExecutionContextStack, + type AgentHostDefined, + DynamicParsedCodeRecord, + surroundingAgent, + type Feature, + type GCMarker, + type ResumeEvaluateOptions, + type ParseNode, + getBreakpointCandidates, +} from '#self'; + +let agentSignifier = 0; + +/** https://tc39.es/ecma262/#table-agent-record */ +export interface AgentRecord { + readonly LittleEndian: boolean; + CanBlock: boolean; + readonly Signifier: number; + readonly IsLockFree1: boolean; + readonly IsLockFree2: boolean; + readonly IsLockFree8: boolean; + // unsupported + CandidateExecution: never; + KeptAlive: Set; + ModuleAsyncEvaluationCount: number; +} + +/** https://tc39.es/ecma262/#sec-agents */ +export class Agent { + readonly AgentRecord: AgentRecord; + + executionContextStack = new ExecutionContextStack(); + + // NON-SPEC + readonly jobQueue: Job[] = []; + + scheduledForCleanup = new Set(); + + hostDefinedOptions: AgentHostDefined; + + constructor(options: AgentHostDefined = {}) { + const Signifier = agentSignifier; + agentSignifier += 1; + this.AgentRecord = { + LittleEndian: true, + CanBlock: true, + Signifier, + IsLockFree1: true, + IsLockFree2: true, + IsLockFree8: true, + CandidateExecution: undefined!, + KeptAlive: new Set(), + ModuleAsyncEvaluationCount: 0, + }; + + this.hostDefinedOptions = { + ...options, + features: options.features, + }; + } + + /** https://tc39.es/ecma262/#running-execution-context */ + get runningExecutionContext() { + return this.executionContextStack[this.executionContextStack.length - 1]; + } + + /** https://tc39.es/ecma262/#current-realm */ + get currentRealmRecord() { + return this.runningExecutionContext.Realm; + } + + /** https://tc39.es/ecma262/#active-function-object */ + get activeFunctionObject() { + return this.runningExecutionContext.Function; + } + + intrinsic(name: T): Intrinsics[T] { + return this.currentRealmRecord.Intrinsics[name]; + } + + // Generate a throw completion using message templates + /** @deprecated Use Throw */ + Throw(type: ErrorType | Value, template: K, ...templateArgs: Parameters<(typeof messages)[K]>): ThrowCompletion { + if (type instanceof Value) { + return ThrowCompletion(type); + } + return Throw(type, template, ...templateArgs); + } + + queueJob(queueName: string, job: () => void) { + const callerContext = this.runningExecutionContext; + const callerRealm = callerContext.Realm; + const callerScriptOrModule = GetActiveScriptOrModule(); + const pending: Job = { + queueName, + job, + callerRealm, + callerScriptOrModule, + }; + this.jobQueue.push(pending); + } + + // NON-SPEC: Check if a feature is enabled in this agent. + feature(name: Feature): boolean { + return !!this.hostDefinedOptions.features?.includes(name); + } + + // NON-SPEC + mark(m: GCMarker) { + this.AgentRecord.KeptAlive.forEach(m); + this.executionContextStack.forEach(m); + this.jobQueue.forEach((j) => { + m(j.callerRealm); + m(j.callerScriptOrModule); + }); + } + + // NON-SPEC + // #region Step-by-step evaluation + #pausedEvaluator?: ValueEvaluator; + + #onEvaluatorFin?: (completion: NormalCompletion | ThrowCompletion) => void; + + // NON-SPEC + /** This function will synchronously return a completion if this is a nested evaluation and debugger cannot be triggered. */ + evaluate(evaluator: ValueEvaluator, onFinished: (completion: NormalCompletion | ThrowCompletion) => void) { + if (this.#pausedEvaluator) { + const result = EnsureCompletion(skipDebugger(evaluator)); + // only the top evaluator can be evaluted step by step. + onFinished(result); + return result; + } + this.#pausedEvaluator = evaluator; + this.#onEvaluatorFin = onFinished as (completion: NormalCompletion | ThrowCompletion) => void; + return undefined; + } + + resumeEvaluate(options?: ResumeEvaluateOptions): IteratorResult { + const { noBreakpoint } = options || {}; + if (!this.#pausedEvaluator) { + throw new Error('No paused evaluator'); + } + let nextLocation; + if (options?.pauseAt === 'step-over') { + nextLocation = this.runningExecutionContext.callSite.nextNode; + } else if (options?.pauseAt === 'step-out') { + nextLocation = this.executionContextStack[this.executionContextStack.length - 2].callSite.lastCallNode; + } + let debuggerStatementCompletion = options?.debuggerStatementCompletion; + while (true) { + const state = this.#pausedEvaluator.next({ type: 'debugger-resume', value: debuggerStatementCompletion }); + debuggerStatementCompletion = undefined; + + if (!noBreakpoint && this.hostDefinedOptions.onDebugger && !this.debugger_isPreviewing && !state.done) { + if (state.value.type === 'debugger') { + this.hostDefinedOptions.onDebugger(); + return { done: false, value: undefined }; + } else if (state.value.type === 'potential-debugger') { + if (options?.pauseAt === 'step-in' && shouldStepOnNode()) { + this.hostDefinedOptions.onDebugger(); + return { done: false, value: undefined }; + } + const callSite = surroundingAgent.runningExecutionContext.callSite; + if (nextLocation && (callSite.lastNode === nextLocation || callSite.lastCallNode === nextLocation)) { + this.hostDefinedOptions.onDebugger(); + return { done: false, value: undefined }; + } + } + } + + if (state.done) { + this.#pausedEvaluator = undefined; + this.#onEvaluatorFin!(EnsureCompletion(state.value)); + this.#onEvaluatorFin = undefined; + return state; + } + } + } + + // #endregion + // NON-SPEC + // #region parsed scripts/modules + #script_id = 0; + + parsedSources = new Map(); + + addParsedSource(source: ScriptRecord | SourceTextModuleRecord) { + const id = `${this.#script_id}`; + if (source.HostDefined) { + source.HostDefined.scriptId = id; + } + this.hostDefinedOptions.onScriptParsed?.(source, id); + this.parsedSources.set(id, source); + this.#script_id += 1; + } + + #dynamicParsedSourceIds = new Map(); + + addDynamicParsedSource(realm: Realm, sourceText: string, ast?: unknown[] | ParseNode.Expression | ParseNode.Script): string | undefined { + if (this.debugger_isPreviewing) { + return undefined; + } + if (this.#dynamicParsedSourceIds.has(sourceText)) { + return this.#dynamicParsedSourceIds.get(sourceText); + } + const id = `${this.#script_id}`; + const source = new DynamicParsedCodeRecord(realm, !ast || isArray(ast) ? sourceText : ast); + source.HostDefined.scriptId = id; + this.hostDefinedOptions.onScriptParsed?.(source, id); + this.parsedSources.set(id, source); + this.#script_id += 1; + this.#dynamicParsedSourceIds.set(sourceText, id); + return id; + } + + // #endregion + + // #region breakpoint + breakpointsEnabled = false; + + pauseOnExceptions: undefined | 'caught' | 'uncaught' | 'all'; + + #breakpointId = 0; + + #breakpoints = new Map(); + + addBreakpointByUrl(breakpoint: Protocol.Debugger.SetBreakpointByUrlRequest): Protocol.Debugger.SetBreakpointByUrlResponse { + this.#breakpointId += 1; + let scriptId; + if (breakpoint.url) { + for (const [id, script] of this.parsedSources) { + if (script.HostDefined?.specifier === breakpoint.url) { + scriptId = id; + break; + } + } + } + if (!scriptId) { + return { breakpointId: this.#breakpointId.toString(), locations: [] }; + } + return { + breakpointId: this.#breakpointId.toString(), + locations: [getBreakpointCandidates({ scriptId, lineNumber: breakpoint.lineNumber, columnNumber: breakpoint.columnNumber })[0]], + }; + } + + removeBreakpoint(breakpointId: string) { + this.#breakpoints.delete(breakpointId); + } + // #endregion + + // #region side-effect free evaluator + #debugger_previewing = false; + + #debugger_objectsCreatedDuringPreview = new Set(); + + get debugger_isPreviewing() { + return this.#debugger_previewing; + } + + get debugger_cannotPreview() { + if (this.#debugger_previewing) { + return ThrowCompletion(X(Construct(this.currentRealmRecord.Intrinsics['%EvalError%'], [Value('Preview evaluator cannot evaluate side-effecting code')]))); + } + return undefined; + } + + debugger_tryTouchDuringPreview(object: ObjectValue) { + if (this.#debugger_previewing && !this.#debugger_objectsCreatedDuringPreview.has(object)) { + return this.debugger_cannotPreview; + } + return undefined; + } + + debugger_markObjectCreated(object: ObjectValue) { + if (!this.#debugger_previewing) { + return; + } + this.#debugger_objectsCreatedDuringPreview.add(object); + } + + debugger_scopePreview(): Disposable | null; + + debugger_scopePreview(cb: () => T): T; + + debugger_scopePreview(cb?: () => T): T | Disposable | null { + if (!cb) { + const old = this.#debugger_previewing; + this.#debugger_previewing = true; + return { + [Symbol.dispose]: () => { + this.#debugger_previewing = old; + this.#debugger_objectsCreatedDuringPreview.clear(); + }, + }; + } else { + const old = this.#debugger_previewing; + this.#debugger_previewing = true; + try { + const res = cb(); + return res; + } finally { + this.#debugger_previewing = old; + if (!old) { + this.#debugger_objectsCreatedDuringPreview.clear(); + } + } + } + } + // #endregion +} + +interface Breakpoint { + _: never; +} + +/** https://tc39.es/ecma262/#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; +} + +/** https://tc39.es/ecma262/#sec-agentcansuspend */ +export function AgentCanSuspend() { + const AR = surroundingAgent.AgentRecord; + return AR.CanBlock; +} + +// https://tc39.es/ecma262/#sec-IncrementModuleAsyncEvaluationCount +export function IncrementModuleAsyncEvaluationCount() { + const AR = surroundingAgent.AgentRecord; + const count = AR.ModuleAsyncEvaluationCount; + AR.ModuleAsyncEvaluationCount = count + 1; + return count; +} diff --git a/src/execution-context/Environment.mts b/src/execution-context/Environment.mts new file mode 100644 index 0000000..c29a6ce --- /dev/null +++ b/src/execution-context/Environment.mts @@ -0,0 +1,956 @@ +import { AbstractModuleRecord } from '../modules.mts'; +import { + Descriptor, + ReferenceRecord, + UndefinedValue, + ObjectValue, + Value, + wellKnownSymbols, + BooleanValue, + JSStringValue, + NullValue, +} from '../value.mts'; +import { surroundingAgent, type GCMarker } from '../host-defined/engine.mts'; +import { + NormalCompletion, Q, X, + type ValueEvaluator, +} from '../completion.mts'; +import { JSStringMap, skipDebugger } from '../helpers.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { + Assert, + DefinePropertyOrThrow, + Get, + HasOwnProperty, + HasProperty, + IsDataDescriptor, + IsExtensible, + IsPropertyKey, + Set, + ToBoolean, + isECMAScriptFunctionObject, + type ECMAScriptFunctionObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-environment-records */ +export abstract class EnvironmentRecord { + readonly OuterEnv: EnvironmentRecord | NullValue; + + constructor(outerEnv: EnvironmentRecord | NullValue) { + this.OuterEnv = outerEnv; + } + + abstract HasBinding(N: JSStringValue): ValueEvaluator; + + abstract CreateMutableBinding(N: JSStringValue, D: BooleanValue): PlainEvaluator; + + abstract CreateImmutableBinding(N: JSStringValue, S: BooleanValue): void; + + abstract InitializeBinding(N: JSStringValue, V: Value): PlainEvaluator; + + abstract SetMutableBinding(N: JSStringValue, V: Value, S: BooleanValue): PlainEvaluator; + + abstract GetBindingValue(N: JSStringValue, S: BooleanValue): ValueEvaluator; + + abstract DeleteBinding(N: JSStringValue): ValueEvaluator; + + abstract HasThisBinding(): BooleanValue; + + abstract HasSuperBinding(): BooleanValue; + + abstract WithBaseObject(): ObjectValue | UndefinedValue; + + // NON-SPEC + mark(m: GCMarker) { + m(this.OuterEnv); + } +} + +interface DeclarativeEnvironmentBinding { + readonly indirect: boolean; + initialized: boolean; + readonly mutable?: boolean; + readonly strict?: boolean; + readonly deletable?: boolean; + value?: Value | undefined; + + mark(m: GCMarker): void; +} + +interface ModuleEnvironmentBinding extends DeclarativeEnvironmentBinding { + readonly target: [AbstractModuleRecord, JSStringValue]; +} + +/** https://tc39.es/ecma262/#sec-declarative-environment-records */ +export class DeclarativeEnvironmentRecord extends EnvironmentRecord { + readonly bindings = new JSStringMap(); + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n */ + * HasBinding(N: JSStringValue) { + // 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; + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d */ + * CreateMutableBinding(N: JSStringValue, D: BooleanValue) { + // 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 deleted 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: GCMarker) { + m(this.value); + }, + }); + // 4. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s */ + CreateImmutableBinding(N: JSStringValue, S: BooleanValue) { + // 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); + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v */ + * InitializeBinding(N: JSStringValue, V: Value) { + // 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); + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s */ + * SetMutableBinding(N: JSStringValue, V: Value, S: BooleanValue): PlainEvaluator { + 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). + yield* envRec.CreateMutableBinding(N, Value.true); + // c. Perform envRec.InitializeBinding(N, V). + yield* 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); + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s */ + * GetBindingValue(N: JSStringValue, _S: BooleanValue): ValueEvaluator { + // 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 NormalCompletion(binding.value!); + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-deletebinding-n */ + * DeleteBinding(N: JSStringValue) { + // 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; + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-hasthisbinding */ + HasThisBinding(): BooleanValue { + // 1. Return false. + return Value.false; + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-hassuperbinding */ + HasSuperBinding(): BooleanValue { + // 1. Return false. + return Value.false; + } + + /** https://tc39.es/ecma262/#sec-declarative-environment-records-withbaseobject */ + WithBaseObject() { + // 1. Return undefined. + return Value.undefined; + } + + // NON-SPEC + override mark(m: GCMarker) { + // TODO(ts): this function does not call super.mark(). is it a mistake? + m(this.bindings); + } +} + +/** https://tc39.es/ecma262/#sec-function-environment-records */ +export class FunctionEnvironmentRecord extends DeclarativeEnvironmentRecord { + /** https://tc39.es/ecma262/#sec-newfunctionenvironment */ + constructor(F: ECMAScriptFunctionObject, newTarget: UndefinedValue | ObjectValue) { + // 1. Assert: F is an ECMAScript function. + Assert(isECMAScriptFunctionObject(F)); + // 2. Assert: Type(newTarget) is Undefined or Object. + Assert(newTarget instanceof UndefinedValue || newTarget instanceof ObjectValue); + // 3. Let env be a new function Environment Record containing no bindings. + super(F.Environment); + // 4. Set env.[[FunctionObject]] to F. + this.FunctionObject = F; + // 5. If F.[[ThisMode]] is lexical, set env.[[ThisBindingStatus]] to lexical. + + if (F.ThisMode === 'lexical') { + this.ThisBindingStatus = 'lexical'; + } else { // 6. Else, set env.[[ThisBindingStatus]] to uninitialized. + this.ThisBindingStatus = 'uninitialized'; + } + // 7. Set env.[[NewTarget]] to newTarget. + this.NewTarget = newTarget; + // 8. Set env.[[OuterEnv]] to F.[[Environment]]. + // 9. Return env. + } + + protected ThisValue!: Value; + + ThisBindingStatus: 'lexical' | 'uninitialized' | 'initialized'; + + readonly FunctionObject: ECMAScriptFunctionObject; + + readonly NewTarget: UndefinedValue | ObjectValue; + + /** https://tc39.es/ecma262/#sec-bindthisvalue */ + BindThisValue(V: Value) { + // 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; + } + + /** https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding */ + override 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; + } + } + + /** https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding */ + override HasSuperBinding() { + const envRec = this; + // 1. If envRec.[[ThisBindingStatus]] is lexical, return false. + if (envRec.ThisBindingStatus === 'lexical') { + return Value.false; + } + // 2. If envRec.[[FunctionObject]].[[HomeObject]] has the value undefined, return false; otherwise, return true. + if (envRec.FunctionObject.HomeObject === Value.undefined) { + return Value.false; + } else { + return Value.true; + } + } + + /** https://tc39.es/ecma262/#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; + } + + /** https://tc39.es/ecma262/#sec-getsuperbase */ + GetSuperBase() { + const envRec = this; + // 1. Let home be envRec.[[FunctionObject]].[[HomeObject]]. + const home = envRec.FunctionObject.HomeObject; + // 2. If home has the value undefined, return undefined. + if (home === Value.undefined) { + return Value.undefined; + } + // 3. Assert: Type(home) is Object. + Assert(home instanceof ObjectValue); + // 4. Return ! home.[[GetPrototypeOf]](). + return X(home.GetPrototypeOf()); + } + + override mark(m: GCMarker) { + super.mark(m); + m(this.ThisValue); + m(this.FunctionObject); + m(this.NewTarget); + } +} + +/** https://tc39.es/ecma262/#sec-module-environment-records */ +export class ModuleEnvironmentRecord extends DeclarativeEnvironmentRecord { + declare readonly bindings: JSStringMap; + + /** https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s */ + override* GetBindingValue(N: JSStringValue, S: BooleanValue): ValueEvaluator { + // 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) { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', N); + } + // d. Return ? targetEnv.GetBindingValue(N2, true). + return yield* 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 NormalCompletion(binding.value!); + } + + /** https://tc39.es/ecma262/#sec-module-environment-records-deletebinding-n */ + override DeleteBinding(): never { + Assert(false, 'This method is never invoked. See #sec-delete-operator-static-semantics-early-errors'); + } + + /** https://tc39.es/ecma262/#sec-module-environment-records-hasthisbinding */ + override HasThisBinding() { + // Return true. + return Value.true; + } + + /** https://tc39.es/ecma262/#sec-module-environment-records-getthisbinding */ + GetThisBinding() { + // Return undefined. + return Value.undefined; + } + + /** https://tc39.es/ecma262/#sec-createimportbinding */ + CreateImportBinding(N: JSStringValue, M: AbstractModuleRecord, N2: JSStringValue) { + // 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(skipDebugger(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: GCMarker) { + m(this.target[0]); + m(this.target[1]); + }, + }); + // 6. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } +} + +/** https://tc39.es/ecma262/#sec-object-environment-records */ +export class ObjectEnvironmentRecord extends EnvironmentRecord { + BindingObject: ObjectValue; + + IsWithEnvironment: BooleanValue; + + /** https://tc39.es/ecma262/#sec-newobjectenvironment */ + constructor(O: ObjectValue, W: BooleanValue, E: EnvironmentRecord | NullValue) { + super(E); + this.BindingObject = O; + this.IsWithEnvironment = W; + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n */ + * HasBinding(N: JSStringValue): ValueEvaluator { + // 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(yield* HasProperty(bindings, N)); + // 4. If foundBinding is false, return false. + if (foundBinding === Value.false) { + return Value.false; + } + // 5. If the IsWithEnvironment flag of envRec i s false, return true. + if (envRec.IsWithEnvironment === Value.false) { + return Value.true; + } + // 6. Let unscopables be ? Get(bindings, @@unscopables). + const unscopables = Q(yield* Get(bindings, wellKnownSymbols.unscopables)); + // 7. If Type(unscopables) is Object, then + if (unscopables instanceof ObjectValue) { + // a. Let blocked be ! ToBoolean(? Get(unscopables, N)). + const blocked = X(ToBoolean(Q(yield* Get(unscopables, N)))); + // b. If blocked is true, return false. + if (blocked === Value.true) { + return Value.false; + } + } + // 8. Return true. + return Value.true; + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d */ + * CreateMutableBinding(N: JSStringValue, D: BooleanValue): PlainEvaluator { + // 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 }). + Q(yield* DefinePropertyOrThrow(bindings, N, Descriptor({ + Value: Value.undefined, + Writable: Value.true, + Enumerable: Value.true, + Configurable: D, + }))); + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s */ + CreateImmutableBinding(_N: JSStringValue, _S: BooleanValue) { + Assert(false, 'CreateImmutableBinding called on an Object Environment Record'); + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v */ + * InitializeBinding(N: JSStringValue, V: Value): PlainEvaluator { + // 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). + Q(yield* envRec.SetMutableBinding(N, V, Value.false)); + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s */ + * SetMutableBinding(N: JSStringValue, V: Value, S: BooleanValue): PlainEvaluator { + // 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(yield* 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). + Q(yield* Set(bindings, N, V, S)); + return undefined; + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s */ + * GetBindingValue(N: JSStringValue, S: BooleanValue): ValueEvaluator { + // 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(yield* 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 NormalCompletion(Value.undefined); + } else { + return surroundingAgent.Throw('ReferenceError', 'NotDefined', N); + } + } + // 5. Return Get(bindings, N). + return yield* Get(bindings, N); + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n */ + * DeleteBinding(N: JSStringValue): ValueEvaluator { + // 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(yield* bindings.Delete(N)); + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-hasthisbinding */ + HasThisBinding() { + // 1. Return false. + return Value.false; + } + + /** https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding */ + HasSuperBinding() { + // 1. Return falase. + return Value.false; + } + + /** https://tc39.es/ecma262/#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 IsWithEnvironment flag of envRec is true, return the binding object for envRec. + if (envRec.IsWithEnvironment === Value.true) { + return envRec.BindingObject; + } + // 3. Otherwise, return undefined. + return Value.undefined; + } + + // NON-SPEC + override mark(m: GCMarker) { + // TODO(ts): this function does not call super.mark(). is it a mistake? + m(this.BindingObject); + } +} + +/** https://tc39.es/ecma262/#sec-global-environment-records */ +export class GlobalEnvironmentRecord extends EnvironmentRecord { + readonly ObjectRecord: ObjectEnvironmentRecord; + + readonly GlobalThisValue: ObjectValue; + + readonly DeclarativeRecord: DeclarativeEnvironmentRecord; + + /** https://tc39.es/ecma262/#sec-newglobalenvironment */ + constructor(G: ObjectValue, thisValue: ObjectValue) { + // 1. Let objRec be NewObjectEnvironment(G, false, null). + const objRec = new ObjectEnvironmentRecord(G, Value.false, Value.null); + // 2. Let dclRec be a new declarative Environment Record containing no bindings. + const dclRec = new DeclarativeEnvironmentRecord(Value.null); + // 3. Let env be a new global Environment Record. + super(Value.null); + // 4. Set env.[[ObjectRecord]] to objRec. + this.ObjectRecord = objRec; + // 5. Set env.[[GlobalThisValue]] to thisValue. + this.GlobalThisValue = thisValue; + // 6. Set env.[[DeclarativeRecord]] to dclRec. + this.DeclarativeRecord = dclRec; + // 8. Set env.[[OuterEnv]] to null. + // 9. Return env. + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n */ + * HasBinding(N: JSStringValue) { + // 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 ((yield* 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 yield* ObjRec.HasBinding(N); + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d */ + * CreateMutableBinding(N: JSStringValue, D: BooleanValue) { + // 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 ((yield* DclRec.HasBinding(N)) === Value.true) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', N); + } + // 4. Return DclRec.CreateMutableBinding(N, D). + return yield* DclRec.CreateMutableBinding(N, D); + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s */ + CreateImmutableBinding(N: JSStringValue, S: BooleanValue) { + // 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. + // TODO: remove skipDebugger + if (skipDebugger(DclRec.HasBinding(N)) === Value.true) { + return surroundingAgent.Throw('TypeError', 'AlreadyDeclared', N); + } + // Return DclRec.CreateImmutableBinding(N, S). + return DclRec.CreateImmutableBinding(N, S); + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-initializebinding-n-v */ + * InitializeBinding(N: JSStringValue, V: Value) { + // 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 + // TODO: remove skipDebugger + if (skipDebugger(DclRec.HasBinding(N)) === Value.true) { + // a. Return DclRec.InitializeBinding(N, V). + return yield* 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 yield* ObjRec.InitializeBinding(N, V); + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-setmutablebinding-n-v-s */ + * SetMutableBinding(N: JSStringValue, V: Value, S: BooleanValue): PlainEvaluator { + // 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 ((yield* DclRec.HasBinding(N)) === Value.true) { + // a. Return DclRec.SetMutableBinding(N, V, S). + return yield* DclRec.SetMutableBinding(N, V, S); + } + // 4. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 5. Return ? ObjRec.SetMutableBinding(N, V, S). + Q(yield* ObjRec.SetMutableBinding(N, V, S)); + return undefined; + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-getbindingvalue-n-s */ + * GetBindingValue(N: JSStringValue, S: BooleanValue): ValueEvaluator { + // 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 ((yield* DclRec.HasBinding(N)) === Value.true) { + // a. Return DclRec.GetBindingValue(N, S). + return yield* DclRec.GetBindingValue(N, S); + } + // 4. Let ObjRec be envRec.[[ObjectRecord]]. + const ObjRec = envRec.ObjectRecord; + // 5. Return ObjRec.GetBindingValue(N, S). + return yield* ObjRec.GetBindingValue(N, S); + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-deletebinding-n */ + * DeleteBinding(N: JSStringValue): PlainEvaluator { + // 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 ((yield* DclRec.HasBinding(N)) === Value.true) { + // a. Return DclRec.DeleteBinding(N). + return Q(yield* 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(yield* HasOwnProperty(globalObject, N)); + // 7. If existingProp is true, then + if (existingProp === Value.true) { + // a. Return ? ObjRec.DeleteBinding(N). + return Q(yield* ObjRec.DeleteBinding(N)); + } + // 8. Return true. + return Value.true; + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-hasthisbinding */ + HasThisBinding() { + // Return true. + return Value.true; + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-hassuperbinding */ + HasSuperBinding() { + // 1. Return false. + return Value.false; + } + + /** https://tc39.es/ecma262/#sec-global-environment-records-withbaseobject */ + WithBaseObject() { + // 1. Return undefined. + return Value.undefined; + } + + /** https://tc39.es/ecma262/#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; + } + + /** https://tc39.es/ecma262/#sec-haslexicaldeclaration */ + * HasLexicalDeclaration(N: JSStringValue) { + // 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 yield* DclRec.HasBinding(N); + } + + /** https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty */ + * HasRestrictedGlobalProperty(N: JSStringValue): ValueEvaluator { + // 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(yield* globalObject.GetOwnProperty(N)); + // 5. If existingProp is undefined, return false. + if (existingProp instanceof UndefinedValue) { + return Value.false; + } + // 6. If existingProp.[[Configurable]] is true, return false. + if (existingProp.Configurable === Value.true) { + return Value.false; + } + // Return true. + return Value.true; + } + + /** https://tc39.es/ecma262/#sec-candeclareglobalvar */ + * CanDeclareGlobalVar(N: JSStringValue): ValueEvaluator { + // 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(yield* HasOwnProperty(globalObject, N)); + // 5. If hasProperty is true, return true. + if (hasProperty === Value.true) { + return Value.true; + } + // 6. Return ? IsExtensible(globalObject). + return Q(yield* IsExtensible(globalObject)); + } + + /** https://tc39.es/ecma262/#sec-candeclareglobalfunction */ + * CanDeclareGlobalFunction(N: JSStringValue): ValueEvaluator { + // 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(yield* globalObject.GetOwnProperty(N)); + // 5. If existingProp is undefined, return ? IsExtensible(globalObject). + if (existingProp instanceof UndefinedValue) { + return Q(yield* 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; + } + + /** https://tc39.es/ecma262/#sec-createglobalvarbinding */ + * CreateGlobalVarBinding(N: JSStringValue, D: BooleanValue): PlainEvaluator { + // 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(yield* HasOwnProperty(globalObject, N)); + // 5. Let extensible be ? IsExtensible(globalObject). + const extensible = Q(yield* 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(yield* ObjRec.CreateMutableBinding(N, D)); + // b. Perform ? ObjRec.InitializeBinding(N, undefined). + Q(yield* ObjRec.InitializeBinding(N, Value.undefined)); + } + // return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + /** https://tc39.es/ecma262/#sec-createglobalfunctionbinding */ + * CreateGlobalFunctionBinding(N: JSStringValue, V: Value, D: BooleanValue): PlainEvaluator { + // 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(yield* globalObject.GetOwnProperty(N)); + // 5. If existingProp is undefined or existingProp.[[Configurable]] is true, then + let desc; + if (existingProp instanceof UndefinedValue || 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(yield* DefinePropertyOrThrow(globalObject, N, desc)); + // 8. Record that the binding for N in ObjRec has been initialized. + // 9. Perform ? Set(globalObject, N, V, false). + Q(yield* Set(globalObject, N, V, Value.false)); + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + + override mark(m: GCMarker) { + // TODO(ts): this function does not call super.mark(). is it a mistake? + m(this.ObjectRecord); + m(this.GlobalThisValue); + m(this.DeclarativeRecord); + } +} + +export type EnvironmentRecordWithThisBinding = FunctionEnvironmentRecord | GlobalEnvironmentRecord | ModuleEnvironmentRecord; + +/** https://tc39.es/ecma262/#sec-getidentifierreference */ +export function* GetIdentifierReference(env: EnvironmentRecord | NullValue, name: JSStringValue, strict: BooleanValue): PlainEvaluator { + // 1. If lex is the value null, then + if (env instanceof NullValue) { + // a. Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }. + return NormalCompletion(new ReferenceRecord({ + Base: 'unresolvable', + ReferencedName: name, + Strict: strict, + ThisValue: undefined, + })); + } + // 2. Let exists be ? envRec.HasBinding(name). + const exists = Q(yield* env.HasBinding(name)); + // 3. If exists is true, then + if (exists === Value.true) { + // a. Return the Reference Record { [[Base]]: env, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }. + return NormalCompletion(new ReferenceRecord({ + Base: env, + ReferencedName: name, + Strict: strict, + ThisValue: undefined, + })); + } else { + // a. Let outer be env.[[OuterEnv]]. + const outer = env.OuterEnv; + // b. Return ? GetIdentifierReference(outer, name, strict). + return yield* GetIdentifierReference(outer, name, strict); + } +} diff --git a/src/execution-context/ExecutionContext.mts b/src/execution-context/ExecutionContext.mts new file mode 100644 index 0000000..ab8199f --- /dev/null +++ b/src/execution-context/ExecutionContext.mts @@ -0,0 +1,134 @@ +import type { ExecutionContextHostDefined, GCMarker } from '../host-defined/engine.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { + type YieldEvaluator, NullValue, type FunctionObject, Value, type GeneratorObject, type AsyncGeneratorObject, AbstractModuleRecord, type ScriptRecord, EnvironmentRecord, PrivateEnvironmentRecord, CallSite, PromiseCapabilityRecord, Realm, + surroundingAgent, + Assert, + GetIdentifierReference, + JSStringValue, + UndefinedValue, + type EnvironmentRecordWithThisBinding, + ObjectValue, +} from '#self'; + + +/** https://tc39.es/ecma262/#sec-execution-contexts */ +export class ExecutionContext { + codeEvaluationState?: YieldEvaluator; + + Function: NullValue | FunctionObject = Value.null; + + Generator?: GeneratorObject | AsyncGeneratorObject; + + ScriptOrModule: AbstractModuleRecord | ScriptRecord | NullValue = Value.null; + + VariableEnvironment!: EnvironmentRecord; + + LexicalEnvironment!: EnvironmentRecord; + + PrivateEnvironment: PrivateEnvironmentRecord | NullValue = Value.null; + + HostDefined?: ExecutionContextHostDefined; + + // NON-SPEC + callSite = new CallSite(this); + + promiseCapability?: PromiseCapabilityRecord; + + poppedForTailCall = false; + + Realm!: Realm; + + 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.PrivateEnvironment = this.PrivateEnvironment; + e.HostDefined = this.HostDefined; + + e.callSite = this.callSite.clone(e); + e.promiseCapability = this.promiseCapability; + return e; + } + + // NON-SPEC + mark(m: GCMarker) { + m(this.Function); + m(this.Realm); + m(this.ScriptOrModule); + m(this.VariableEnvironment); + m(this.LexicalEnvironment); + m(this.PrivateEnvironment); + m(this.promiseCapability); + } +} + +/** https://tc39.es/ecma262/#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; +} + +/** https://tc39.es/ecma262/#sec-resolvebinding */ +export function ResolveBinding(name: JSStringValue, env?: EnvironmentRecord | UndefinedValue | NullValue, strict?: boolean) { + // 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); +} + +/** https://tc39.es/ecma262/#sec-getthisenvironment */ +export function GetThisEnvironment(): EnvironmentRecordWithThisBinding { + // 1. Let env be the running execution context's LexicalEnvironment. + let env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Repeat, + while (true) { + __ts_cast__(env); + // a. Let exists be env.HasThisBinding(). + const exists = env.HasThisBinding(); + // b. If exists is true, return envRec. + if (exists === Value.true) { + return env as EnvironmentRecordWithThisBinding; + } + // c. Let outer be env.[[OuterEnv]]. + const outer = env.OuterEnv; + // d. Assert: outer is not null. + Assert(!(outer instanceof NullValue)); + // e. Set env to outer. + env = outer; + } +} + +/** https://tc39.es/ecma262/#sec-resolvethisbinding */ +export function ResolveThisBinding() { + const envRec = GetThisEnvironment(); + return envRec.GetThisBinding(); +} + +/** https://tc39.es/ecma262/#sec-getnewtarget */ +export function GetNewTarget(): ObjectValue | UndefinedValue { + const envRec = GetThisEnvironment(); + Assert('NewTarget' in envRec); + return envRec.NewTarget; +} + +/** https://tc39.es/ecma262/#sec-getglobalobject */ +export function GetGlobalObject() { + const currentRealm = surroundingAgent.currentRealmRecord; + return currentRealm.GlobalObject; +} diff --git a/src/execution-context/Job.mts b/src/execution-context/Job.mts new file mode 100644 index 0000000..49a6caf --- /dev/null +++ b/src/execution-context/Job.mts @@ -0,0 +1,54 @@ +import type { kAsyncContext } from '../helpers.mts'; +import { + type Realm, type AbstractModuleRecord, type ScriptRecord, type NullValue, type ExecutionContext, type FunctionObject, + Assert, + Call, + IsCallable, + Q, + Value, + type Arguments, + type ValueEvaluator, + surroundingAgent, +} from '#self'; + +/** https://tc39.es/ecma262/#job */ +export interface Job { + readonly queueName: string; + readonly job: () => void; + readonly callerRealm: Realm; + readonly callerScriptOrModule: AbstractModuleRecord | ScriptRecord | NullValue; +} + +/** https://tc39.es/ecma262/#sec-jobcallback-records */ +export interface JobCallbackRecord { + Callback: FunctionObject & { [kAsyncContext]?: ExecutionContext; }; + HostDefined: undefined; +} + +/** https://tc39.es/ecma262/#sec-hostmakejobcallback */ +export function HostMakeJobCallback(callback: FunctionObject): JobCallbackRecord { + // 1. Assert: IsCallable(callback) is true. + Assert(IsCallable(callback)); + // 2. Return the JobCallback Record { [[Callback]]: callback, [[HostDefined]]: empty }. + return { Callback: callback, HostDefined: undefined }; +} + +/** https://tc39.es/ecma262/#sec-hostcalljobcallback */ +export function* HostCallJobCallback(jobCallback: JobCallbackRecord, V: Value, argumentsList: Arguments): ValueEvaluator { + // 1. Assert: IsCallable(jobCallback.[[Callback]]) is true. + Assert(IsCallable(jobCallback.Callback)); + // 1. Return ? Call(jobCallback.[[Callback]], V, argumentsList). + return Q(yield* Call(jobCallback.Callback, V, argumentsList)); +} + +// Atomics: HostEnqueueGenericJob + +/** https://tc39.es/ecma262/#sec-hostenqueuepromisejob */ +export function HostEnqueuePromiseJob(job: () => void, _realm: Realm | NullValue) { + if (surroundingAgent.debugger_isPreviewing) { + return; + } + surroundingAgent.queueJob('PromiseJobs', job); +} + +// Atomics: HostEnqueueTimeoutJob diff --git a/src/execution-context/PrivateEnvironment.mts b/src/execution-context/PrivateEnvironment.mts new file mode 100644 index 0000000..29022b1 --- /dev/null +++ b/src/execution-context/PrivateEnvironment.mts @@ -0,0 +1,41 @@ +import { + type PrivateName, type GCMarker, Assert, JSStringValue, NullValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-privateenvironment-records */ +export class PrivateEnvironmentRecord { + readonly OuterPrivateEnvironment: PrivateEnvironmentRecord | NullValue; + + readonly Names: PrivateName[] = []; + + /** https://tc39.es/ecma262/#sec-newprivateenvironment */ + constructor(outerEnv: PrivateEnvironmentRecord | NullValue) { + this.OuterPrivateEnvironment = outerEnv; + } + + mark(m: GCMarker) { + this.Names.forEach((name) => { + m(name); + }); + } +} + +/** https://tc39.es/ecma262/#sec-resolve-private-identifier */ +export function ResolvePrivateIdentifier(privEnv: PrivateEnvironmentRecord, identifier: JSStringValue) { + // 1. Let names be privEnv.[[Names]]. + const names = privEnv.Names; + // 2. If names contains a Private Name whose [[Description]] is identifier, then + const name = names.find((n) => n.Description.stringValue() === identifier.stringValue()); + if (name) { + // a. Let name be that Private Name. + // b. Return name. + return name; + } else { // 3. Else, + // a. Let outerPrivEnv be privEnv.[[OuterPrivateEnvironment]]. + const outerPrivEnv = privEnv.OuterPrivateEnvironment; + // b. Assert: outerPrivEnv is not null. + Assert(!(outerPrivEnv instanceof NullValue)); + // c. Return ResolvePrivateIdentifier(outerPrivEnv, identifier). + return ResolvePrivateIdentifier(outerPrivEnv, identifier); + } +} diff --git a/src/execution-context/Realm.mts b/src/execution-context/Realm.mts new file mode 100644 index 0000000..a078466 --- /dev/null +++ b/src/execution-context/Realm.mts @@ -0,0 +1,363 @@ +import { AddRestrictedFunctionProperties, type Intrinsics } from '../abstract-ops/realms.mts'; +import { bootstrapAggregateError } from '../intrinsics/AggregateError.mts'; +import { bootstrapAggregateErrorPrototype } from '../intrinsics/AggregateErrorPrototype.mts'; +import { bootstrapArray } from '../intrinsics/Array.mts'; +import { bootstrapArrayBuffer } from '../intrinsics/ArrayBuffer.mts'; +import { bootstrapArrayBufferPrototype } from '../intrinsics/ArrayBufferPrototype.mts'; +import { bootstrapArrayIteratorPrototype } from '../intrinsics/ArrayIteratorPrototype.mts'; +import { bootstrapArrayPrototype } from '../intrinsics/ArrayPrototype.mts'; +import { bootstrapAsyncFromSyncIteratorPrototype } from '../intrinsics/AsyncFromSyncIteratorPrototype.mts'; +import { bootstrapAsyncFunction } from '../intrinsics/AsyncFunction.mts'; +import { bootstrapAsyncFunctionPrototype } from '../intrinsics/AsyncFunctionPrototype.mts'; +import { bootstrapAsyncGeneratorFunction } from '../intrinsics/AsyncGeneratorFunction.mts'; +import { bootstrapAsyncGeneratorFunctionPrototype } from '../intrinsics/AsyncGeneratorFunctionPrototype.mts'; +import { bootstrapAsyncGeneratorFunctionPrototypePrototype } from '../intrinsics/AsyncGeneratorFunctionPrototypePrototype.mts'; +import { bootstrapAsyncIteratorPrototype } from '../intrinsics/AsyncIteratorPrototype.mts'; +import { bootstrapBigInt } from '../intrinsics/BigInt.mts'; +import { bootstrapBigIntPrototype } from '../intrinsics/BigIntPrototype.mts'; +import { bootstrapBoolean } from '../intrinsics/Boolean.mts'; +import { bootstrapBooleanPrototype } from '../intrinsics/BooleanPrototype.mts'; +import { bootstrapDataView } from '../intrinsics/DataView.mts'; +import { bootstrapDataViewPrototype } from '../intrinsics/DataViewPrototype.mts'; +import { bootstrapDate } from '../intrinsics/Date.mts'; +import { bootstrapDatePrototype } from '../intrinsics/DatePrototype.mts'; +import { bootstrapError } from '../intrinsics/Error.mts'; +import { bootstrapErrorPrototype } from '../intrinsics/ErrorPrototype.mts'; +import { bootstrapEval } from '../intrinsics/eval.mts'; +import { bootstrapFinalizationRegistry } from '../intrinsics/FinalizationRegistry.mts'; +import { bootstrapFinalizationRegistryPrototype } from '../intrinsics/FinalizationRegistryPrototype.mts'; +import { bootstrapForInIteratorPrototype } from '../intrinsics/ForInIteratorPrototype.mts'; +import { bootstrapFunction } from '../intrinsics/Function.mts'; +import { bootstrapFunctionPrototype } from '../intrinsics/FunctionPrototype.mts'; +import { bootstrapGeneratorFunction } from '../intrinsics/GeneratorFunction.mts'; +import { bootstrapGeneratorFunctionPrototype } from '../intrinsics/GeneratorFunctionPrototype.mts'; +import { bootstrapGeneratorFunctionPrototypePrototype } from '../intrinsics/GeneratorFunctionPrototypePrototype.mts'; +import { bootstrapIsFinite } from '../intrinsics/isFinite.mts'; +import { bootstrapIsNaN } from '../intrinsics/isNaN.mts'; +import { bootstrapIterator } from '../intrinsics/Iterator.mts'; +import { bootstrapIteratorHelperPrototype } from '../intrinsics/IteratorHelperPrototype.mts'; +import { bootstrapIteratorPrototype } from '../intrinsics/IteratorPrototype.mts'; +import { bootstrapJSON } from '../intrinsics/JSON.mts'; +import { bootstrapMap } from '../intrinsics/Map.mts'; +import { bootstrapMapIteratorPrototype } from '../intrinsics/MapIteratorPrototype.mts'; +import { bootstrapMapPrototype } from '../intrinsics/MapPrototype.mts'; +import { bootstrapMath } from '../intrinsics/Math.mts'; +import { bootstrapNativeError } from '../intrinsics/NativeError.mts'; +import { bootstrapNumber } from '../intrinsics/Number.mts'; +import { bootstrapNumberPrototype } from '../intrinsics/NumberPrototype.mts'; +import { bootstrapObject } from '../intrinsics/Object.mts'; +import { makeObjectPrototype, bootstrapObjectPrototype } from '../intrinsics/ObjectPrototype.mts'; +import { bootstrapParseFloat } from '../intrinsics/parseFloat.mts'; +import { bootstrapParseInt } from '../intrinsics/parseInt.mts'; +import { bootstrapPromise } from '../intrinsics/Promise.mts'; +import { bootstrapPromisePrototype } from '../intrinsics/PromisePrototype.mts'; +import { bootstrapProxy } from '../intrinsics/Proxy.mts'; +import { bootstrapReflect } from '../intrinsics/Reflect.mts'; +import { bootstrapRegExp } from '../intrinsics/RegExp.mts'; +import { bootstrapRegExpPrototype } from '../intrinsics/RegExpPrototype.mts'; +import { bootstrapRegExpStringIteratorPrototype } from '../intrinsics/RegExpStringIteratorPrototype.mts'; +import { bootstrapSet } from '../intrinsics/Set.mts'; +import { bootstrapSetIteratorPrototype } from '../intrinsics/SetIteratorPrototype.mts'; +import { bootstrapSetPrototype } from '../intrinsics/SetPrototype.mts'; +import { bootstrapShadowRealm } from '../intrinsics/ShadowRealm.mts'; +import { bootstrapShadowRealmPrototype } from '../intrinsics/ShadowRealmPrototype.mts'; +import { bootstrapString } from '../intrinsics/String.mts'; +import { bootstrapStringIteratorPrototype } from '../intrinsics/StringIteratorPrototype.mts'; +import { bootstrapStringPrototype } from '../intrinsics/StringPrototype.mts'; +import { bootstrapSymbol } from '../intrinsics/Symbol.mts'; +import { bootstrapSymbolPrototype } from '../intrinsics/SymbolPrototype.mts'; +import { bootstrapThrowTypeError } from '../intrinsics/ThrowTypeError.mts'; +import { bootstrapTypedArray } from '../intrinsics/TypedArray.mts'; +import { bootstrapUint8Array } from '../intrinsics/TypedArray_Uint8Array.mts'; +import { bootstrapTypedArrayConstructors } from '../intrinsics/TypedArrayConstructors.mts'; +import { bootstrapTypedArrayPrototype } from '../intrinsics/TypedArrayPrototype.mts'; +import { bootstrapTypedArrayPrototypes } from '../intrinsics/TypedArrayPrototypes.mts'; +import { bootstrapURIHandling } from '../intrinsics/URIHandling.mts'; +import { bootstrapWeakMap } from '../intrinsics/WeakMap.mts'; +import { bootstrapWeakMapPrototype } from '../intrinsics/WeakMapPrototype.mts'; +import { bootstrapWeakRef } from '../intrinsics/WeakRef.mts'; +import { bootstrapWeakRefPrototype } from '../intrinsics/WeakRefPrototype.mts'; +import { bootstrapWeakSet } from '../intrinsics/WeakSet.mts'; +import { bootstrapWeakSetPrototype } from '../intrinsics/WeakSetPrototype.mts'; +import { bootstrapWrapForValidIteratorPrototype } from '../intrinsics/WrapForValidIteratorPrototype.mts'; +import { bootstrapTemporal } from '../intrinsics/Temporal/Temporal.mts'; +import { + type ObjectValue, type GlobalEnvironmentRecord, type ParseNode, type LoadedModuleRequestRecord, type ManagedRealmHostDefined, type GCMarker, + ManagedRealm, + type Mutable, + DefinePropertyOrThrow, + Descriptor, + F as toNumberValue, + Value, + X, + surroundingAgent, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-code-realms */ +export abstract class Realm { + abstract readonly AgentSignifier: unknown; + + abstract readonly Intrinsics: Intrinsics; + + abstract readonly GlobalObject: ObjectValue; + + abstract readonly GlobalEnv: GlobalEnvironmentRecord; + + abstract readonly TemplateMap: { Site: ParseNode.TemplateLiteral; Array: ObjectValue; }[]; + + readonly LoadedModules: LoadedModuleRequestRecord[] = []; + + abstract readonly HostDefined: ManagedRealmHostDefined; + + // NON-SPEC + abstract randomState: undefined | BigUint64Array; + + mark(m: GCMarker) { + 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); + } + for (const v of this.LoadedModules) { + m(v.Module); + } + } +} + +/** https://tc39.es/ecma262/pr/3728/#sec-makerealm */ +export function MakeRealm(...args: ConstructorParameters) { + return new ManagedRealm(...args); +} + +/** https://tc39.es/ecma262/#sec-createintrinsics */ +export function CreateIntrinsics(realmRec: Realm) { + const intrinsics = Object.create(null); + (realmRec as Mutable).Intrinsics = intrinsics; + makeObjectPrototype(realmRec); + + 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); + bootstrapIterator(realmRec); + bootstrapIteratorHelperPrototype(realmRec); + bootstrapWrapForValidIteratorPrototype(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); + + bootstrapGeneratorFunctionPrototypePrototype(realmRec); + bootstrapGeneratorFunctionPrototype(realmRec); + bootstrapGeneratorFunction(realmRec); + + bootstrapAsyncFunctionPrototype(realmRec); + bootstrapAsyncFunction(realmRec); + + bootstrapAsyncGeneratorFunctionPrototypePrototype(realmRec); + bootstrapAsyncGeneratorFunctionPrototype(realmRec); + bootstrapAsyncGeneratorFunction(realmRec); + + bootstrapAsyncFromSyncIteratorPrototype(realmRec); + + bootstrapArrayBufferPrototype(realmRec); + bootstrapArrayBuffer(realmRec); + + bootstrapTypedArrayPrototype(realmRec); + bootstrapTypedArray(realmRec); + bootstrapTypedArrayPrototypes(realmRec); + bootstrapTypedArrayConstructors(realmRec); + bootstrapUint8Array(realmRec); + + bootstrapDataViewPrototype(realmRec); + bootstrapDataView(realmRec); + + bootstrapJSON(realmRec); + + bootstrapWeakMapPrototype(realmRec); + bootstrapWeakMap(realmRec); + bootstrapWeakSetPrototype(realmRec); + bootstrapWeakSet(realmRec); + + bootstrapWeakRefPrototype(realmRec); + bootstrapWeakRef(realmRec); + + bootstrapFinalizationRegistryPrototype(realmRec); + bootstrapFinalizationRegistry(realmRec); + + bootstrapShadowRealmPrototype(realmRec); + bootstrapShadowRealm(realmRec); + + if (surroundingAgent.feature('temporal')) { + bootstrapTemporal(realmRec); + } + + AddRestrictedFunctionProperties(intrinsics['%Function.prototype%'], realmRec); + + return intrinsics; +} + +/** https://tc39.es/ecma262/#sec-setdefaultglobalbindings */ +export function SetDefaultGlobalBindings(realmRec: Realm) { + const global = realmRec.GlobalObject; + + // Value Properties of the Global Object + for (const [name, value] of [ + ['Infinity', toNumberValue(Infinity)], + ['NaN', toNumberValue(NaN)], + ['undefined', Value.undefined], + ] as const) { + X(DefinePropertyOrThrow(global, Value(name), Descriptor({ + Value: value, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + } + + X(DefinePropertyOrThrow(global, Value('globalThis'), Descriptor({ + Value: realmRec.GlobalEnv.GlobalThisValue, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + for (const name of [ + // 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', + 'Iterator', + 'Map', + 'Number', + 'Object', + 'Promise', + 'Proxy', + 'RangeError', + 'ReferenceError', + 'RegExp', + 'Set', + 'ShadowRealm', + // 'SharedArrayBuffer', + 'String', + 'Symbol', + 'SyntaxError', + 'Temporal', + 'TypeError', + 'Uint8Array', + 'Uint8ClampedArray', + 'Uint16Array', + 'Uint32Array', + 'URIError', + 'WeakMap', + 'WeakRef', + 'WeakSet', + + // Other Properties of the Global Object + // 'Atomics', + 'JSON', + 'Math', + 'Reflect', + ] as const) { + const value = realmRec.Intrinsics[`%${name}%`]; + if (!value) { + continue; + } + X(DefinePropertyOrThrow(global, Value(name), Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } +} diff --git a/src/execution-context/WeakReference.mts b/src/execution-context/WeakReference.mts new file mode 100644 index 0000000..e6b5679 --- /dev/null +++ b/src/execution-context/WeakReference.mts @@ -0,0 +1,77 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + type FinalizationRegistryObject, type PlainCompletion, Q, skipDebugger, NormalCompletion, ObjectValue, SymbolValue, Assert, HostCallJobCallback, type JobCallbackRecord, UndefinedValue, Value, type ValueEvaluator, KeyForSymbol, +} from '#self'; + + +/** https://tc39.es/ecma262/#sec-host-cleanup-finalization-registry */ + +export function HostEnqueueFinalizationRegistryCleanupJob(fg: FinalizationRegistryObject): PlainCompletion { + if (surroundingAgent.hostDefinedOptions.cleanupFinalizationRegistry !== undefined) { + Q(surroundingAgent.hostDefinedOptions.cleanupFinalizationRegistry(fg)); + } else { + if (!surroundingAgent.scheduledForCleanup.has(fg)) { + surroundingAgent.scheduledForCleanup.add(fg); + surroundingAgent.queueJob('FinalizationCleanup', () => { + surroundingAgent.scheduledForCleanup.delete(fg); + // TODO: remove skipDebugger + skipDebugger(CleanupFinalizationRegistry(fg)); + }); + } + } + return NormalCompletion(undefined); +}/** https://tc39.es/ecma262/#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(); +}/** https://tc39.es/ecma262/#sec-addtokeptobjects */ + +export function AddToKeptObjects(object: ObjectValue | SymbolValue) { + // 1. Let agentRecord be the surrounding agent's Agent Record. + const agentRecord = surroundingAgent.AgentRecord; + // 2. Append object to agentRecord.[[KeptAlive]]. + agentRecord.KeptAlive.add(object); +}/** https://tc39.es/ecma262/#sec-cleanup-finalization-registry */ + +export function* CleanupFinalizationRegistry(finalizationRegistry: FinalizationRegistryObject, callback?: JobCallbackRecord): ValueEvaluator { + Q(surroundingAgent.debugger_tryTouchDuringPreview(finalizationRegistry)); + // 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 = 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 ? HostCallJobCallback(callback, undefined, « cell.[[HeldValue]] »). + Q(yield* HostCallJobCallback(callback, Value.undefined, [cell.HeldValue])); + } + // 4. Return NormalCompletion(undefined). + return NormalCompletion(Value.undefined); +}/** https://tc39.es/ecma262/#sec-canbeheldweakly */ + +export function CanBeHeldWeakly(v: Value): v is ObjectValue | SymbolValue { + // 1. If v is an Object, return true. + if (v instanceof ObjectValue) { + return true; + } + + // 2. If v is a Symbol and KeyForSymbol(v) is undefined, return true. + if (v instanceof SymbolValue && KeyForSymbol(v) === Value.undefined) { + return true; + } + + // 3. Return false. + return false; +} diff --git a/src/execution-context/all.mts b/src/execution-context/all.mts new file mode 100644 index 0000000..8eba6e1 --- /dev/null +++ b/src/execution-context/all.mts @@ -0,0 +1,7 @@ +export * from './Environment.mts'; +export * from './PrivateEnvironment.mts'; +export * from './Realm.mts'; +export * from './ExecutionContext.mts'; +export * from './Job.mts'; +export * from './Agent.mts'; +export * from './WeakReference.mts'; diff --git a/src/helpers.mts b/src/helpers.mts new file mode 100644 index 0000000..72b56ab --- /dev/null +++ b/src/helpers.mts @@ -0,0 +1,631 @@ +import type { Protocol } from 'devtools-protocol'; +import { + DynamicParsedCodeRecord, type GCMarker, surroundingAgent, +} from './host-defined/engine.mts'; +import { ExecutionContext } from './execution-context/ExecutionContext.mts'; +import { + Value, JSStringValue, ObjectValue, UndefinedValue, NullValue, type PropertyKeyValue, + SymbolValue, +} from './value.mts'; +import { Q } from './completion.mts'; +import type { ParseNode } from './parser/ParseNode.mts'; +import type { + Evaluator, EvaluatorNextType, ValueEvaluator, YieldEvaluator, +} from './evaluator.mts'; +import type { ErrorObject } from './intrinsics/Error.mts'; +import { + Call, + isFunctionObject, + isBuiltinFunctionObject, + isECMAScriptFunctionObject, +} from '#self'; + +export const kInternal = Symbol('kInternal'); + +export class JSStringMap implements Map { + #map = new Map(); + + clear() { + this.#map.clear(); + } + + delete(key: JSStringValue | string) { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + return this.#map.delete(key); + } + + forEach(callbackfn: (value: V, key: JSStringValue, map: Map) => void, thisArg?: JSStringMap) { + this.#map.forEach((value, key) => Reflect.apply(callbackfn, thisArg, [value, typeof key === 'string' ? Value(key) : key, this])); + } + + get(key: JSStringValue | string) { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + return this.#map.get(key); + } + + has(key: JSStringValue | string) { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + return this.#map.has(key); + } + + set(key: JSStringValue | string, value: V): this { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + this.#map.set(key, value); + return this; + } + + get size() { + return this.#map.size; + } + + * entries() { + for (const [key, value] of this.#map.entries()) { + yield [Value(key), value] as [JSStringValue, V]; + } + return undefined; + } + + * keys() { + for (const key of this.#map.keys()) { + yield Value(key); + } + return undefined; + } + + values() { + return this.#map.values(); + } + + declare [Symbol.iterator]: () => MapIterator<[JSStringValue, V]>; + + declare [Symbol.toStringTag]: string; + + static { + JSStringMap.prototype[Symbol.toStringTag] = 'JSStringMap'; + JSStringMap.prototype[Symbol.iterator] = JSStringMap.prototype.entries; + } + + mark(m: GCMarker) { + for (const [k, v] of this.#map.entries()) { + m(k); + m(v); + } + } +} + +export class PropertyKeyMap implements Map { + #map = new Map(); + + clear() { + this.#map.clear(); + } + + delete(key: PropertyKeyValue | string) { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + return this.#map.delete(key); + } + + forEach(callbackfn: (value: V, key: PropertyKeyValue, map: Map) => void, thisArg?: PropertyKeyMap) { + this.#map.forEach((value, key) => Reflect.apply(callbackfn, thisArg, [value, typeof key === 'string' ? Value(key) : key, this])); + } + + get(key: PropertyKeyValue | string) { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + return this.#map.get(key); + } + + has(key: PropertyKeyValue | string) { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + return this.#map.has(key); + } + + set(key: PropertyKeyValue | string, value: V): this { + if (key instanceof JSStringValue) { + key = key.stringValue(); + } + this.#map.set(key, value); + return this; + } + + get size() { + return this.#map.size; + } + + * entries() { + for (const [key, value] of this.#map.entries()) { + if (typeof key === 'string') { + yield [Value(key), value] as [JSStringValue, V]; + } else { + yield [key, value] as [SymbolValue, V]; + } + } + return undefined; + } + + * keys() { + for (const key of this.#map.keys()) { + if (typeof key === 'string') { + yield Value(key); + } else { + yield key; + } + } + return undefined; + } + + * values() { + for (const value of this.#map.values()) { + yield value; + } + return undefined; + } + + declare [Symbol.iterator]: () => MapIterator<[PropertyKeyValue, V]>; + + declare [Symbol.toStringTag]: string; + + static { + PropertyKeyMap.prototype[Symbol.toStringTag] = 'PropertyKeyMap'; + PropertyKeyMap.prototype[Symbol.iterator] = PropertyKeyMap.prototype.entries; + } + + mark(m: GCMarker) { + for (const [k, v] of this.#map.entries()) { + m(k); + m(v); + } + } +} + +export class JSStringSet { + #set = new Set(); + + constructor(value?: Iterable) { + if (value) { + for (const item of value) { + this.add(item); + } + } + } + + add(value: JSStringValue | string): this { + this.#set.add(typeof value === 'string' ? value : value.stringValue()); + return this; + } + + clear(): void { + this.#set.clear(); + } + + delete(value: JSStringValue | string): boolean { + return this.#set.delete(typeof value === 'string' ? value : value.stringValue()); + } + + forEach(callbackfn: (value: JSStringValue, value2: JSStringValue, set: Set) => void, thisArg?: JSStringSet): void { + for (const value of this.#set) { + Reflect.apply(callbackfn, thisArg, [Value(value), Value(value), this]); + } + } + + has(value: JSStringValue | NullValue | string): boolean { + if (value instanceof NullValue) { + return false; + } + return this.#set.has(typeof value === 'string' ? value : value.stringValue()); + } + + get size() { + return this.#set.size; + } + + * entries(): SetIterator<[JSStringValue, JSStringValue]> { + for (const value of this.#set) { + yield [Value(value), Value(value)]; + } + return undefined; + } + + declare keys: () => SetIterator; + + * values() { + for (const value of this.#set) { + yield Value(value); + } + return undefined; + } + + declare [Symbol.iterator]: () => SetIterator; + + declare [Symbol.toStringTag]: string; + + static { + JSStringSet.prototype[Symbol.toStringTag] = 'JSStringSet'; + JSStringSet.prototype[Symbol.iterator] = JSStringSet.prototype.values; + JSStringSet.prototype.keys = JSStringSet.prototype.values; + } + + mark(_m: GCMarker) { } +} + +export class OutOfRange extends RangeError { + /* node:coverage disable */ + declare cause: unknown; + + detail: unknown; + + constructor(fn: string, detail: unknown) { + super(`${fn}() argument out of range`, { cause: detail }); + this.detail = detail; + } +} +/* node:coverage enable */ + +export function skipDebugger(iterator: Evaluator, maxSteps = Infinity): T { + let steps = 0; + while (true) { + const { done, value } = iterator.next({ type: 'debugger-resume', value: undefined }); + if (done) { + return value; + } + /* node:coverage ignore next 4 */ + steps += 1; + if (steps > maxSteps) { + throw new RangeError('Max steps exceeded'); + } + } +} + +export function* resume(context: ExecutionContext, completion: EvaluatorNextType): YieldEvaluator { + let result; + while (true) { + result = context.codeEvaluationState!.next(completion); + if (result.done) { + return result.value; + } + const { value } = result; + if (value.type === 'debugger' || value.type === 'potential-debugger') { + completion = yield value; + } else if (value.type === 'await' || value.type === 'async-generator-yield') { + return Value.undefined; + } else if (value.type === 'yield') { + return value.value; + } else { + unreachable(value); + } + } +} + +export class CallSite { + context: ExecutionContext; + + lastNode: ParseNode | null = null; + + nextNode: ParseNode | null = null; + + lastCallNode: ParseNode.CallExpression | null = null; + + inheritedLastCallNode: ParseNode.CallExpression | null = null; + + constructCall = false; + + constructor(context: ExecutionContext) { + this.context = context; + } + + clone(context = this.context) { + const c = new CallSite(context); + c.lastNode = this.lastNode; + c.lastCallNode = this.lastCallNode; + c.inheritedLastCallNode = this.inheritedLastCallNode; + c.constructCall = this.constructCall; + return c; + } + + isTopLevel() { + return this.context.Function === Value.null; + } + + isConstructCall() { + return this.constructCall; + } + + isAsync() { + if (!(this.context.Function instanceof NullValue) && isECMAScriptFunctionObject(this.context.Function) && this.context.Function.ECMAScriptCode) { + const code = this.context.Function.ECMAScriptCode; + return code.type === 'AsyncBody' || code.type === 'AsyncGeneratorBody'; + } + return false; + } + + isNative() { + return isBuiltinFunctionObject(this.context.Function); + } + + getFunctionName(): string | null { + if (isFunctionObject(this.context.Function)) { + const name = this.context.Function.properties.get('name'); + if (name && name.Value && name.Value instanceof JSStringValue) { + return name.Value.stringValue(); + } + } + return null; + } + + getSpecifier() { + if (this.context.HostDefined?.scriptId && surroundingAgent.parsedSources.get(this.context.HostDefined.scriptId) instanceof DynamicParsedCodeRecord) { + return null; + } + if (!(this.context.ScriptOrModule instanceof NullValue)) { + return this.context.ScriptOrModule.HostDefined.specifier; + } + return null; + } + + getScriptId() { + const context = this.context.HostDefined?.scriptId; + if (context) { + return context; + } + if (!(this.context.ScriptOrModule instanceof NullValue)) { + return this.context.ScriptOrModule.HostDefined.scriptId; + } + return undefined; + } + + setLocation(node: ParseNode) { + this.lastNode = node; + } + + setNextLocation(node: ParseNode) { + this.nextNode = node; + } + + setCallLocation(node: ParseNode.CallExpression | null) { + this.lastCallNode = 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 visualFunctionName; + if (this.inheritedLastCallNode?.CallExpression.type === 'IdentifierReference') { + visualFunctionName = this.inheritedLastCallNode.CallExpression.name; + } + if (visualFunctionName === functionName) { + visualFunctionName = undefined; + } + + let string = isAsync ? 'async ' : ''; + + if (isConstructCall) { + string += 'new '; + } + + if (isMethodCall || isConstructCall) { + if (functionName) { + string += functionName; + } else { + string += ''; + } + if (visualFunctionName) { + string += ` (as ${visualFunctionName})`; + } + } else if (functionName) { + string += functionName; + if (visualFunctionName) { + string += ` (as ${visualFunctionName})`; + } + } else { + return `${string}${this.loc()}`; + } + + return `${string} (${this.loc()})`; + } + + toCallFrame(): Protocol.Runtime.CallFrame | undefined { + const source = this.getScriptId(); + if (source === undefined || source === null) { + return undefined; + } + return { + columnNumber: (this.columnNumber || 1) - 1, + lineNumber: (this.lineNumber || 1) - 1, + functionName: this.getFunctionName() || '', + scriptId: source, + url: this.getSpecifier() || '', + }; + } +} + +export class CallFrame { + columnNumber: number | undefined; + + lineNumber: number | undefined; + + functionName: string | undefined; + + scriptId: string | undefined; + + url: string | undefined; + + toCallFrame(): Protocol.Runtime.CallFrame | undefined { + if (!this.scriptId) { + return undefined; + } + return { + columnNumber: (this.columnNumber || 1) - 1, + lineNumber: (this.lineNumber || 1) - 1, + functionName: this.functionName || '', + scriptId: this.scriptId, + url: this.url || '', + }; + } +} + +export const kAsyncContext = Symbol('kAsyncContext'); + +function captureAsyncStack(stack: CallSite[]) { + 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[kAsyncContext]) { + const asyncContext = reaction.Handler.Callback[kAsyncContext]; + stack.push(asyncContext.callSite.clone()); + if ('PromiseState' in asyncContext.promiseCapability!.Promise) { + promise = asyncContext.promiseCapability!.Promise; + } else { + return; + } + } else if (!(reaction.Capability instanceof UndefinedValue)) { + if ('PromiseState' in reaction.Capability.Promise) { + promise = reaction.Capability.Promise; + } else { + return; + } + } + } +} + +export function getHostDefinedErrorStack(O: Value): (CallSite | CallFrame)[] | undefined { + if (O instanceof ObjectValue && 'HostDefinedErrorStack' in O && isArray((O as ErrorObject).HostDefinedErrorStack)) { + return (O as ErrorObject).HostDefinedErrorStack as (CallSite | CallFrame)[]; + } + return undefined; +} + +export function getCurrentStack(excludeGlobalStack = true) { + const stack: CallSite[] = []; + for (let i = surroundingAgent.executionContextStack.length - (excludeGlobalStack ? 2 : 1); i >= 0; i -= 1) { + const e = surroundingAgent.executionContextStack[i]; + if (e.VariableEnvironment === undefined && e.Function === Value.null) { + break; + } + const clone = e.callSite.clone(); + const parent = stack[stack.length - 1]; + if (parent && !parent.context.poppedForTailCall) { + parent.inheritedLastCallNode = clone.lastCallNode; + } + stack.push(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); + } + return stack; +} + +export function captureStack() { + const stack = getCurrentStack(); + + let nativeStack: string | undefined; + if (surroundingAgent.hostDefinedOptions.errorStackAttachNativeStack) { + const origStackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 12; + try { + nativeStack = new Error().stack; + } finally { + Error.stackTraceLimit = origStackTraceLimit; + } + } + + return { + stack, + nativeStack, + }; +} + +export function* callSiteToErrorString(O: ErrorObject, stack: readonly CallSite[], nativeStack?: string): ValueEvaluator { + const errorString = (Q(yield* Call(surroundingAgent.intrinsic('%Error.prototype.toString%'), O)) as JSStringValue).stringValue(); + const errorStack = callSiteToErrorStack(stack, nativeStack); + return Value(errorString + errorStack); +} + +export function callSiteToErrorStack(stack: readonly CallSite[], nativeStack: string | undefined) { + let errorString = ''; + stack.forEach((s) => { + errorString = `${errorString}\n at ${s.toString()}`; + }); + if (typeof nativeStack === 'string') { + errorString = `${errorString}\n \n${nativeStack.split('\n').slice(6).join('\n')}`; + } + return errorString; +} + +export function callable( + onCalled = (target: Class, _thisArg: unknown, args: unknown[]) => Reflect.construct(target as new (...args: unknown[]) => unknown, args), +) { + const handler: ProxyHandler = Object.freeze({ + __proto__: null, + apply: onCalled, + }); + return function decorator(classValue: Class, _classContext: ClassDecoratorContext unknown)>) { + return new Proxy(classValue, handler); + }; +} + +export type Mutable = { + -readonly [P in keyof T]: T[P]; +} + +export const isArray: (arg: unknown) => arg is readonly unknown[] = Array.isArray; +export function unreachable(_: never): never { + throw new Error('Unreachable'); +} +export function __ts_cast__(_value: unknown): asserts _value is T { } diff --git a/src/host-defined/debugger-eval.mts b/src/host-defined/debugger-eval.mts new file mode 100644 index 0000000..507e42a --- /dev/null +++ b/src/host-defined/debugger-eval.mts @@ -0,0 +1,128 @@ +import { + ContainsArguments, + DeclarativeEnvironmentRecord, + DynamicParsedCodeRecord, + EnsureCompletion, + EnvironmentRecord, + EvalDeclarationInstantiation, + Evaluate, + ExecutionContext, + FunctionEnvironmentRecord, GetThisEnvironment, IsStrict, ManagedRealm, NormalCompletion, Q, surroundingAgent, ThrowCompletion, Value, wrappedParse, type PlainCompletion, type ValueEvaluator, +} from '#self'; + +const cascadeStack = new WeakMap(); +// This is modified based on PerformEval, used internally for devtools console. +export function* performDevtoolsEval(source: string, evalRealm: ManagedRealm, strictCaller: boolean, doNotTrack: boolean): ValueEvaluator { + let inFunction = false; + let inMethod = false; + let inDerivedConstructor = false; + let inClassFieldInitializer = false; + let scriptContext; + if (!surroundingAgent.runningExecutionContext?.LexicalEnvironment) { + // top level devtools eval + const globalEnv = evalRealm.GlobalEnv; + scriptContext = new ExecutionContext(); + scriptContext.Function = Value.null; + scriptContext.Realm = evalRealm; + // scriptContext.ScriptOrModule = scriptRecord; + scriptContext.VariableEnvironment = globalEnv; + if (!cascadeStack.has(globalEnv)) { + cascadeStack.set(globalEnv, new DeclarativeEnvironmentRecord(globalEnv)); + } + scriptContext.LexicalEnvironment = cascadeStack.get(evalRealm.GlobalEnv)!; + scriptContext.PrivateEnvironment = Value.null; + // scriptContext.HostDefined = scriptRecord.HostDefined; + surroundingAgent.executionContextStack.push(scriptContext); + } + + const thisEnv = GetThisEnvironment(); + if (thisEnv instanceof FunctionEnvironmentRecord) { + const F = thisEnv.FunctionObject; + inFunction = true; + inMethod = thisEnv.HasSuperBinding() === Value.true; + if (F.ConstructorKind === 'derived') { + inDerivedConstructor = true; + } + const classFieldInitializerName = F.ClassFieldInitializerName; + if (classFieldInitializerName !== undefined) { + inClassFieldInitializer = true; + } + } + const script = wrappedParse({ source, allowAllPrivateNames: true }, (parser) => parser.scope.with({ + strict: strictCaller, + newTarget: inFunction, + superProperty: inMethod, + superCall: inDerivedConstructor, + private: true, + }, () => parser.parseScript())); + if (Array.isArray(script)) { + if (scriptContext) { + surroundingAgent.executionContextStack.pop(scriptContext); + } + return ThrowCompletion(script[0]); + } + if (!script.ScriptBody) { + if (scriptContext) { + surroundingAgent.executionContextStack.pop(scriptContext); + } + return Value.undefined; + } + + const body = script.ScriptBody; + if (inClassFieldInitializer && ContainsArguments(body)) { + return surroundingAgent.Throw('SyntaxError', 'UnexpectedToken'); + } + + const scriptId = doNotTrack ? undefined : surroundingAgent.addDynamicParsedSource(surroundingAgent.currentRealmRecord, source, script); + if (!doNotTrack) { + (surroundingAgent.parsedSources.get(scriptId!) as DynamicParsedCodeRecord).HostDefined.isInspectorEval = true; + if (scriptContext) { + scriptContext.HostDefined ??= {}; + scriptContext.HostDefined.scriptId = scriptId; + } + } + + let strictEval; + if (strictCaller === true) { + strictEval = true; + } else { + strictEval = IsStrict(script); + } + const runningContext = surroundingAgent.runningExecutionContext; + let parentLexicalEnvironment; + if (cascadeStack.has(runningContext.LexicalEnvironment)) { + parentLexicalEnvironment = cascadeStack.get(runningContext.LexicalEnvironment)!; + } else { + parentLexicalEnvironment = runningContext.LexicalEnvironment; + } + const lexEnv = new DeclarativeEnvironmentRecord(parentLexicalEnvironment); + cascadeStack.set(runningContext.LexicalEnvironment, lexEnv); + let varEnv; + const privateEnv = runningContext.PrivateEnvironment; + varEnv = runningContext.VariableEnvironment; + if (strictEval === true) { + varEnv = lexEnv; + } + const evalContext = new ExecutionContext(); + evalContext.HostDefined ??= {}; + evalContext.HostDefined.scriptId = scriptId; + evalContext.Function = Value.null; + evalContext.Realm = evalRealm; + evalContext.ScriptOrModule = runningContext.ScriptOrModule; + evalContext.VariableEnvironment = varEnv; + evalContext.LexicalEnvironment = lexEnv; + evalContext.PrivateEnvironment = privateEnv; + surroundingAgent.executionContextStack.push(evalContext); + let result: PlainCompletion = EnsureCompletion(yield* EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)); + if (result.Type === 'normal') { + result = EnsureCompletion(yield* Evaluate(body)); + } + if (result.Type === 'normal' && result.Value === undefined) { + result = NormalCompletion(Value.undefined); + } + surroundingAgent.executionContextStack.pop(evalContext); + if (scriptContext) { + surroundingAgent.executionContextStack.pop(scriptContext); + } + return Q(result)!; +} diff --git a/src/host-defined/debugger-util.mts b/src/host-defined/debugger-util.mts new file mode 100644 index 0000000..a9e9f04 --- /dev/null +++ b/src/host-defined/debugger-util.mts @@ -0,0 +1,15 @@ +import { surroundingAgent, type ParseNode } from '#self'; + +const ShouldSkipStepIn: readonly ParseNode['type'][] = [ + 'NumericLiteral', 'NullLiteral', 'StringLiteral', 'BooleanLiteral', 'RegularExpressionLiteral', + 'CallExpression', + 'Block', +]; + +export function shouldStepOnNode() { + const type = surroundingAgent.runningExecutionContext.callSite.lastNode?.type; + if (type && !type.endsWith('Statement') && !type.endsWith('Declaration') && !ShouldSkipStepIn.includes(type)) { + return true; + } + return false; +} diff --git a/src/host-defined/engine.mts b/src/host-defined/engine.mts new file mode 100644 index 0000000..89cdc18 --- /dev/null +++ b/src/host-defined/engine.mts @@ -0,0 +1,273 @@ +import { Value } from '../value.mts'; +import { + EnsureCompletion, + NormalCompletion, + ThrowCompletion, + Q, X, + type PlainCompletion, +} from '../completion.mts'; +import { GlobalDeclarationInstantiation } from '../runtime-semantics/all.mts'; +import { + Evaluate, type PlainEvaluator, type ValueEvaluator, +} from '../evaluator.mts'; +import { kInternal } from '../helpers.mts'; +import { + AbstractModuleRecord, CyclicModuleRecord, ObjectValue, runJobQueue, type ValueCompletion, type ModuleRecordHostDefined, type ParseScriptHostDefined, type ScriptRecord, + ManagedRealm, + SourceTextModuleRecord, + type ModuleRequestRecord, + Realm, +} from '../index.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PromiseObject } from '../intrinsics/Promise.mts'; +import type { FinalizationRegistryObject } from '../intrinsics/FinalizationRegistry.mts'; +import type { ShadowRealmObject } from '../intrinsics/ShadowRealm.mts'; +import { ExecutionContext } from '../execution-context/ExecutionContext.mts'; +import type { Agent } from '../execution-context/Agent.mts'; +import { + Assert, + FinishLoadingImportedModule, + type FunctionObject, + GraphLoadingState, + PromiseCapabilityRecord, +} from '#self'; + +export interface Engine262Feature { + name: string; + flag: string; + url: string; +} + +// unflag a feature when it reaches stage 3. +export const FEATURES = ([ + // stage 3, but too big + { + name: 'Decorators', + flag: 'decorators', + url: 'https://github.com/tc39/proposal-decorators', + }, + { + name: 'Skip bugfix for field initializers in decorator', + flag: 'decorators.no-bugfix.1', + url: '', + }, + { + name: 'Temporal (wip)', + flag: 'temporal', + url: 'https://github.com/tc39/proposal-temporal', + }, + // stage 2.7 + { + name: 'Iterator#join', + flag: 'iterator.join', + url: 'https://github.com/tc39/proposal-iterator-join', + }, + // stage 2 + { + name: 'FinalizationRegistry#cleanupSome', + flag: 'cleanup-some', + url: 'https://github.com/tc39/proposal-cleanup-some', + }, + { + name: 'RegExp Buffer Boundaries', + flag: 'regexp-buffer-boundaries', + url: 'https://github.com/tc39/proposal-regexp-buffer-boundaries', + }, +]) as const satisfies Engine262Feature[]; +Object.freeze(FEATURES); +FEATURES.forEach(Object.freeze); +export type Feature = typeof FEATURES[number]['flag']; + +export class ExecutionContextStack extends Array { + // This ensures that only the length taking overload is supported. + // This is necessary to support `ArraySpeciesCreate`, which invokes + // the constructor with argument `length`: + constructor(length = 0) { + super(+length); + } + + // @ts-expect-error + override pop(ctx: ExecutionContext) { + if (!ctx.poppedForTailCall) { + const popped = super.pop(); + Assert(popped === ctx); + } + } +} + +export interface HostHooks { + HostInitializeShadowRealm?(realmRec: Realm, innerContext: ExecutionContext, O: ShadowRealmObject): PlainEvaluator | PlainCompletion; + HostEnsureCanCompileStrings?(calleeRealm: Realm, parameterStrings: readonly string[], bodyString: string, direct: boolean): PlainEvaluator | PlainCompletion; +} +export interface AgentHostDefined { + hostHooks?: HostHooks; + hasSourceTextAvailable?(f: FunctionObject): void; + ensureCanCompileStrings?(callerRealm: Realm, calleeRealm: Realm): PlainCompletion; + cleanupFinalizationRegistry?(FinalizationRegistry: FinalizationRegistryObject): PlainCompletion; + features?: readonly string[]; + supportedImportAttributes?: readonly string[]; + loadImportedModule?(referrer: AbstractModuleRecord | ScriptRecord | Realm, specifier: string, attributes: Map, hostDefined: ModuleRecordHostDefined | undefined, finish: (res: PlainCompletion) => void): void; + onDebugger?(): void; + onRealmCreated?(realm: ManagedRealm): void; + onScriptParsed?(script: ScriptRecord | SourceTextModuleRecord | DynamicParsedCodeRecord, scriptId: string): void; + onNodeEvaluation?(node: ParseNode, realm: Realm): void; + + errorStackAttachNativeStack?: boolean; +} + +export interface ResumeEvaluateOptions { + noBreakpoint?: boolean; + pauseAt?: 'step-over' | 'step-in' | 'step-out'; + debuggerStatementCompletion?: ValueCompletion; +} + +// NON-SPEC, only used in the inspector +export class DynamicParsedCodeRecord { + constructor(public Realm: Realm, sourceText: string | ParseNode.Script | ParseNode.Expression) { + this.ECMAScriptCode = typeof sourceText === 'string' ? { sourceText } : sourceText; + } + + public HostDefined = { + scriptId: undefined as string | undefined, + specifier: undefined, + isInspectorEval: false, + }; + + public ECMAScriptCode: { sourceText: string } | ParseNode.Script | ParseNode.Expression; +} + +export let surroundingAgent: Agent; +export function setSurroundingAgent(a: Agent) { + surroundingAgent = a; +} + +export interface ExecutionContextHostDefined { + readonly [kInternal]?: ParseScriptHostDefined[typeof kInternal]; + scriptId?: string; +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation */ +export function* ScriptEvaluation(scriptRecord: ScriptRecord): ValueEvaluator { + 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.PrivateEnvironment = Value.null; + if (scriptRecord.HostDefined[kInternal]) { + scriptContext.HostDefined = { + [kInternal]: scriptRecord.HostDefined[kInternal], + }; + } + // Suspend runningExecutionContext + surroundingAgent.executionContextStack.push(scriptContext); + const scriptBody = scriptRecord.ECMAScriptCode; + let result: NormalCompletion | ThrowCompletion = EnsureCompletion(yield* GlobalDeclarationInstantiation(scriptBody, globalEnv)); + + if (result.Type === 'normal') { + result = EnsureCompletion(yield* (Evaluate(scriptBody))) as NormalCompletion; + + if (result.Type === 'normal' && !result.Value) { + result = NormalCompletion(Value.undefined); + } + } + + // Suspend scriptCtx + surroundingAgent.executionContextStack.pop(scriptContext); + // Resume(surroundingAgent.runningExecutionContext); + + return result as ValueCompletion; +} + +export function* HostEnsureCanCompileStrings(calleeRealm: Realm, parameterStrings: readonly string[], bodyString: string, direct: boolean): PlainEvaluator { + const completion = surroundingAgent.hostDefinedOptions.hostHooks?.HostEnsureCanCompileStrings?.(calleeRealm, parameterStrings, bodyString, direct); + if (!completion) { + return NormalCompletion(undefined); + } + if ('next' in completion) { + Q(yield* completion); + } else { + Q(completion); + } +} + +export function HostPromiseRejectionTracker(promise: PromiseObject, operation: 'reject' | 'handle') { + if (surroundingAgent.debugger_isPreviewing) { + return; + } + const realm = surroundingAgent.currentRealmRecord; + if (realm && realm.HostDefined.promiseRejectionTracker) { + X(realm.HostDefined.promiseRejectionTracker(promise, operation)); + } +} + +export function HostHasSourceTextAvailable(func: FunctionObject) { + if (surroundingAgent.hostDefinedOptions.hasSourceTextAvailable) { + return X(surroundingAgent.hostDefinedOptions.hasSourceTextAvailable(func)); + } + return Value.true; +} + +export function HostGetSupportedImportAttributes(): readonly string[] { + if (surroundingAgent.hostDefinedOptions.supportedImportAttributes) { + return surroundingAgent.hostDefinedOptions.supportedImportAttributes; + } + return []; +} + +// #sec-HostLoadImportedModule +export function HostLoadImportedModule(referrer: CyclicModuleRecord | ScriptRecord | Realm, moduleRequest: ModuleRequestRecord, hostDefined: ModuleRecordHostDefined | undefined, payload: GraphLoadingState | PromiseCapabilityRecord) { + if (surroundingAgent.hostDefinedOptions.loadImportedModule) { + const executionContext = surroundingAgent.runningExecutionContext; + let result: PlainCompletion | undefined; + let sync = true; + const attributes = new Map(moduleRequest.Attributes.map(({ Key, Value }) => [Key.stringValue(), Value.stringValue()])); + surroundingAgent.hostDefinedOptions.loadImportedModule(referrer, moduleRequest.Specifier.stringValue(), attributes, hostDefined, (res) => { + result = res; + if (!sync) { + // If this callback has been called asynchronously, restore the correct execution context and enqueue a job. + surroundingAgent.executionContextStack.push(executionContext); + surroundingAgent.queueJob('FinishLoadingImportedModule', () => { + result = EnsureCompletion(result); + Assert(!!result && (result.Type === 'normal' || result.Type === 'throw')); + FinishLoadingImportedModule(referrer, moduleRequest, result, payload); + }); + surroundingAgent.executionContextStack.pop(executionContext); + runJobQueue(); + } + }); + sync = false; + if (result !== undefined) { + result = EnsureCompletion(result); + Assert(result.Type === 'normal' || result.Type === 'throw'); + FinishLoadingImportedModule(referrer, moduleRequest, result, payload); + } + } else { + FinishLoadingImportedModule(referrer, moduleRequest, surroundingAgent.Throw('Error', 'CouldNotResolveModule', moduleRequest.Specifier), payload); + } +} + +/** https://tc39.es/ecma262/#sec-hostgetimportmetaproperties */ +export function HostGetImportMetaProperties(moduleRecord: AbstractModuleRecord) { + const realm = surroundingAgent.currentRealmRecord; + if (realm.HostDefined.getImportMetaProperties) { + return X(realm.HostDefined.getImportMetaProperties(moduleRecord.HostDefined.public)); + } + return []; +} + +/** https://tc39.es/ecma262/#sec-hostfinalizeimportmeta */ +export function HostFinalizeImportMeta(importMeta: ObjectValue, moduleRecord: AbstractModuleRecord) { + const realm = surroundingAgent.currentRealmRecord; + if (realm.HostDefined.finalizeImportMeta) { + return X(realm.HostDefined.finalizeImportMeta(importMeta, moduleRecord.HostDefined.public)); + } + return Value.undefined; +} + +export type GCMarker = (value: unknown) => void; +export interface Markable { + mark(marker: GCMarker): void; +} diff --git a/src/host-defined/error-messages.mts b/src/host-defined/error-messages.mts new file mode 100644 index 0000000..f12235f --- /dev/null +++ b/src/host-defined/error-messages.mts @@ -0,0 +1,281 @@ +import { unreachable } from '../helpers.mts'; +import * as messages from '../messages.mts'; +import { R } from '../abstract-ops/all.mjs'; +import { isBooleanObject } from '../intrinsics/Boolean.mts'; +import { isNumberObject } from '../intrinsics/Number.mts'; +import { isBigIntObject } from '../intrinsics/BigInt.mts'; +import { isStringObject } from '../intrinsics/String.mts'; +import { isSymbolObject } from '../intrinsics/Symbol.mts'; +import { + BigIntValue, + BooleanValue, + Construct, CreateArrayFromList, EscapeRegExpPattern, isArrayBufferObject, isArrayExoticObject, isDateObject, isErrorObject, isFunctionObject, isModuleNamespaceObject, isPromiseObject, isRegExpObject, isTypedArrayObject, JSStringValue, NullValue, NumberValue, ObjectValue, PrivateName, surroundingAgent, SymbolValue, ThrowCompletion, UndefinedValue, Value, X, + type Intrinsics, +} from '#self'; + +export type ErrorType = 'AggregateError' | 'TypeError' | 'Error' | 'SyntaxError' | 'RangeError' | 'ReferenceError' | 'URIError'; + +/** @deprecated Use ThrowCompletion */ +export function Throw(value: Value): ThrowCompletion; +/** @deprecated Use concrete methods like Throw.TypeError */ +export function Throw(errorType: ErrorType, template: string, ...messages: unknown[]): ThrowCompletion; +/** @deprecated Use concrete methods like Throw.TypeError */ +export function Throw(value: Value | ErrorType, template?: string, ...templateArgs: unknown[]): ThrowCompletion { + if (value instanceof Value) { + return ThrowCompletion(value); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = ((messages as any)[template!] as (...args: unknown[]) => string)(...templateArgs); + const cons = surroundingAgent.intrinsic(`%${value}%`); + let error; + if (value === 'AggregateError') { + error = X(Construct(cons, [ + X(CreateArrayFromList([])), + Value(message), + ])); + } else { + error = X(Construct(cons, [Value(message)])); + } + return ThrowCompletion(error); +} + +function ThrowFactory(intrinsicName: keyof Intrinsics & `%${string}Error%`): Throw { + return (message: string, ...args: Formattable[]) => { + message = message.replace(/\$(\d+)/g, (_, n) => { + const index = Number(n) - 1; + if (index < 0 || index >= args.length) { + throw new RangeError('Insufficient arguments for format string'); + } + const arg = args[index]; + return format(arg); + }); + if (intrinsicName === '%AggregateError%') { + const E = X(Construct(surroundingAgent.intrinsic(intrinsicName), [X(CreateArrayFromList([])), Value(message)])); + return ThrowCompletion(E); + } else { + const E = X(Construct(surroundingAgent.intrinsic(intrinsicName), [Value(message)])); + return ThrowCompletion(E); + } + }; +} +Throw.EvalError = ThrowFactory('%EvalError%'); +Throw.RangeError = ThrowFactory('%RangeError%'); +Throw.ReferenceError = ThrowFactory('%ReferenceError%'); +Throw.SyntaxError = ThrowFactory('%SyntaxError%'); +Throw.TypeError = ThrowFactory('%TypeError%'); +Throw.URIError = ThrowFactory('%URIError%'); +Throw.Error = ThrowFactory('%Error%'); +Throw.AggregateError = ThrowFactory('%AggregateError%'); + +export type Formattable = string | number | bigint | Value | PrivateName | Formattable[]; +export function format(arg: Formattable): string { + switch (true) { + case typeof arg !== 'object': + return String(arg); + case arg instanceof PrivateName: + return `#${arg.Description instanceof UndefinedValue ? '' : arg.Description.stringValue()}`; + case arg instanceof JSStringValue: + return JSON.stringify(arg.stringValue()); + case arg instanceof NumberValue: { + const n = R(arg); + if (n === 0 && Object.is(n, -0)) { + return '-0'; + } + return n.toString(); + } + case arg instanceof BigIntValue: + return `${String(R(arg))}n`; + case arg instanceof SymbolValue: + return `Symbol(${arg.Description instanceof UndefinedValue ? '' : arg.Description.stringValue()})`; + case arg instanceof NullValue: + return 'null'; + case arg instanceof UndefinedValue: + return 'undefined'; + case arg instanceof BooleanValue: + return String(arg.booleanValue()); + case arg instanceof ObjectValue: { + if (isPromiseObject(arg)) { + return '[object Promise]'; + } + if (isModuleNamespaceObject(arg)) { + return '[object Module]'; + } + if (isFunctionObject(arg)) { + const name = arg.properties.get('name'); + if (name && name.Value instanceof JSStringValue && name.Value.stringValue() !== '') { + return `[Function ${name.Value.stringValue()}]`; + } + return '[Function]'; + } + if (isErrorObject(arg)) { + return '[object Error]'; + } + if (isRegExpObject(arg)) { + const P = EscapeRegExpPattern(arg.OriginalSource, arg.OriginalFlags).stringValue(); + const F = arg.OriginalFlags.stringValue(); + return `/${P}/${F}`; + } + if (isDateObject(arg)) { + const d = new Date(R(arg.DateValue)); + if (Number.isNaN(d.getTime())) { + return '[Date Invalid]'; + } + return `[Date ${d.toISOString()}]`; + } + if (isBooleanObject(arg)) { + return `[Boolean ${format(arg.BooleanData)}]`; + } + if (isNumberObject(arg)) { + return `[Number ${format(arg.NumberData)}]`; + } + if (isBigIntObject(arg)) { + return `[BigInt ${format(arg.BigIntData)}]`; + } + if (isStringObject(arg)) { + return `[String ${format(arg.StringData)}]`; + } + if (isSymbolObject(arg)) { + return `[Symbol ${format(arg.SymbolData)}]`; + } + if (isArrayExoticObject(arg)) { + return '[object Array]'; + } + if (isTypedArrayObject(arg)) { + return `[object ${arg.TypedArrayName}]`; + } + if (isArrayBufferObject(arg)) { + return '[object ArrayBuffer]'; + } + return '[object Object]'; + } + case Array.isArray(arg): + return `[${arg.map(format).join(', ')}]`; + default: + return unreachable(arg); + } +} + +export interface Throw { + // auto-generate start + (m: +'"day" is required' + | '"month-code" or "month" is required' + | '"year" is required' + | 'Array length must be uint32.' + | 'Array length too big.' + | 'BigInt has no unsigned right shift, use >> instead' + | 'Calendars are not equal' + | 'Cannot add a date to an instant' + | 'Cannot call addInitializer after decoration is finished' + | 'Cannot define private element to a non-extensible object' + | 'Cannot divide by zero' + | 'Cannot resize ArrayBuffer to bigger than maxByteLength' + | 'DateTime outside of range' + | 'Decorators can only be used to decorate classes' + | 'Decorators cannot appear on both sides of the export keyword' + | 'Exponent of bigint must be positive' + | 'ISODate is out of range' + | 'Invalid date' + | 'Invalid duration' + | 'Invalid leap month' + | 'Invalid month' + | 'Invalid receiver' + | 'Invalid time' + | 'Multiple possible epoch nanoseconds' + | 'No matching offset found for the given date and time' + | 'No possible epoch nanoseconds' + | 'Offset is out of bound' + | 'Options parameter is required' + | 'PlainDateTime outside of range' + | 'PlainMonthDay out of range' + | 'PlainYearMonth calendars do not match' + | 'PlainYearMonth out of range' + | 'RegExp flags "v" and "u" cannot be used together' + | 'Resulting ISODate is out of range' + | 'Resulting date-time is out of range' + | 'Temporal.Duration cannot be converted to primitive value. If you are comparing two Temporal.Duration objects with > or <, use Temporal.Duration.compare() instead.' + | 'Temporal.Duration constructor cannot be called without new' + | 'Temporal.Instant cannot be called without new' + | 'Temporal.Instant cannot be converted to primitive value If you are comparing two Temporal.Duration objects with > or <, use Temporal.Instant.compare() instead.' + | 'Temporal.PlainDate cannot be converted to primitive value. If you are comparing two Temporal.PlainDate objects with > or <, use Temporal.PlainDate.compare() instead.' + | 'Temporal.PlainDate constructor cannot be called without new' + | 'Temporal.PlainDateTime cannot be called without new' + | 'Temporal.PlainDateTime cannot be converted to primitive value. If you are comparing two Temporal.PlainDateTime objects with > or <, use Temporal.PlainDateTime.compare() instead.' + | 'Temporal.PlainMonthDay cannot be called without new' + | 'Temporal.PlainMonthDay cannot be converted to primitive value. If you are comparing two Temporal.PlainMonthDay objects with > or <, use Temporal.PlainMonthDay.compare() instead.' + | 'Temporal.PlainTime cannot be called without new' + | 'Temporal.PlainTime cannot be converted to primitive value. If you are comparing two Temporal.PlainTime objects with > or <, use Temporal.PlainTime.compare() instead.' + | 'Temporal.PlainYearMonth cannot be called without new' + | 'Temporal.PlainYearMonth cannot be converted to primitive value. If you are comparing two Temporal.PlainYearMonth objects with > or <, use Temporal.PlainYearMonth.compare() instead.' + | 'Temporal.ZonedDateTime cannot be called without new' + | 'Temporal.ZonedDateTime cannot be converted to primitive value. If you are comparing two Temporal.ZonedDateTime objects with > or <, use Temporal.ZonedDateTime.compare() instead.' + | 'Time zones are not equal' + | 'TypedArray out of bounds' + | 'calendar is not a string' + | 'directionParam is required' + | 'largestUnit must be larger than smallestUnit' + | 'relativeTo is required for calendar units' + | 'relativeTo option is required when comparing durations with calendar units' + | 'roundTo is required' + | 'roundingIncrement must be 1 when rounding a date unit to a larger unit' + | 'smallestUnit and largestUnit cannot both be omitted' + | 'smallestUnit cannot be hour' + | 'smallestUnit cannot be hour or minute' + | 'timeZone is not a string' + | 'totalOf is required' + ): ThrowCompletion; + (m: +'"roundingIncrement" ($1) is out of range' + | '$1 cannot be used as a WeakMap key' + | '$1 does not look like a TemporalTimeLike object' + | '$1 is not a TemporalTimeLike object' + | '$1 is not a constructor' + | '$1 is not a function' + | '$1 is not a partial Temporal object' + | '$1 is not a string' + | '$1 is not a supported calendar' + | '$1 is not a valid epoch nanoseconds' + | '$1 is not an integral number' + | '$1 is not an object' + | '$1 is not the [[ArrayBufferDetachKey]] of the given ArrayBuffer' + | 'Accessor decorator must return an object or undefined, but $1 was returned' + | 'Cannot mix BigInt and other types in $1 operation' + | 'Class decorator must return a function or undefined, but $1 was returned' + | 'Field decorator must return a function or undefined, but $1 was returned' + | 'Invalid TemporalUnit value $1' + | 'Invalid time string $1' + | 'Invalid time zone identifier: $1' + | 'Method decorator must return a function or undefined, but $1 was returned' + | 'Private field $1 is not a getter' + | 'Private field $1 is not a setter' + | 'Private method $1 cannot be set' + | 'The get property of the return value of an accessor decorator must be a function or undefined, but $1 was returned' + | 'The init property of the return value of an accessor decorator must be a function or undefined, but $1 was returned' + | 'The set property of the return value of an accessor decorator must be a function or undefined, but $1 was returned' + | 'addInitializer must be called with a function, but $1 was passed' + | 'calendar must be a string, but $1' + | 'invalid time zone identifier: $1' + | 'temporalCalendarLike must be a string or a Temporal object, but got $1' + , $1: Formattable): ThrowCompletion; + (m: +'"$1" is required on object $2' + | '$1 does not exist on $2' + | '$1 is a required on object $2' + | '$1 is not a $2' + | 'Private element $1 is already defined on $2' + , $1: Formattable, $2: Formattable): ThrowCompletion; + (m: +'"$1" on object $2 is not valid ($3)' + | '$1-$2-$3 is not a valid date' + , $1: Formattable, $2: Formattable, $3: Formattable): ThrowCompletion; + // auto-generate end + (m: S, ...args: ParsePrintFormat): ThrowCompletion; +} + +// thanks https://github.com/type-challenges/type-challenges/blob/main/questions/00147-hard-c-printf-parser/README.md +type ParametersMap = { + '1': Formattable; + '2': Formattable; + '3': Formattable; +} +type ParsePrintFormat = S extends `${string}$${infer T}${infer End}` ? T extends keyof ParametersMap ? [ParametersMap[T], ...ParsePrintFormat] : ParsePrintFormat : [] diff --git a/src/host-defined/inspect.mts b/src/host-defined/inspect.mts new file mode 100644 index 0000000..0b5031f --- /dev/null +++ b/src/host-defined/inspect.mts @@ -0,0 +1,250 @@ +import { + JSStringValue, ObjectValue, Value, wellKnownSymbols, BooleanValue, NumberValue, BigIntValue, SymbolValue, UndefinedValue, +} from '../value.mts'; +import { Completion, X } from '../completion.mts'; +import { isRegExpObject } from '../intrinsics/RegExp.mts'; +import type { DateObject } from '../intrinsics/Date.mts'; +import type { BooleanObject } from '../intrinsics/Boolean.mts'; +import type { NumberObject } from '../intrinsics/Number.mts'; +import type { BigIntObject } from '../intrinsics/BigInt.mts'; +import type { StringObject } from '../intrinsics/String.mts'; +import type { SymbolObject } from '../intrinsics/Symbol.mts'; +import { isTypedArrayObject } from '../intrinsics/TypedArray.mts'; +import { isShadowRealmObject } from '../intrinsics/ShadowRealm.mts'; +import { surroundingAgent } from './engine.mts'; +import { + Call, IsArray, Get, LengthOfArrayLike, + EscapeRegExpPattern, R, type BuiltinFunctionObject, + + Realm, + type Descriptor, type ValueCompletion, type PromiseObject, CyclicModuleRecord, +} from '#self'; + +const bareKeyRe = /^[a-zA-Z_][a-zA-Z_0-9]*$/; + +function getObjectTag(value: ObjectValue, wrap = false): string { + let s = ''; + try { + s = (X(Get(value, wellKnownSymbols.toStringTag)) as JSStringValue).stringValue(); + } catch { } + try { + const c = X(Get(value, Value('constructor'))); + s = (X(Get(c as ObjectValue, Value('name'))) as JSStringValue).stringValue(); + } catch { } + if (s) { + if (wrap) { + return `[${s}] `; + } + return s; + } + return ''; +} + +const compactObject = (realm: Realm, value: ObjectValue) => { + try { + const toString = X(Get(value, Value('toString'))) as BuiltinFunctionObject; + const objectToString = realm.Intrinsics['%Object.prototype.toString%']; + if (toString.nativeFunction === objectToString.nativeFunction) { + return (X(Call(toString, value)) as JSStringValue).stringValue(); + } else { + const tag = getObjectTag(value, false) || 'Unknown'; + const ctor = X(Get(value, Value('constructor'))); + if (ctor instanceof ObjectValue) { + const ctorName = (X(Get(ctor, Value('name'))) as JSStringValue).stringValue(); + if (ctorName !== '') { + return `#<${ctorName}>`; + } + return `[object ${tag}]`; + } + return `[object ${tag}]`; + } + } catch (e) { + return '[object Unknown]'; + } +}; + +interface InspectContext { + realm: Realm; + indent: number; + inspected: Value[]; + compact: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Inspector = (value: any, context: InspectContext, inner: (v: Value) => string) => string; + +const INSPECTORS = { + Null: () => 'null', + Undefined: () => 'undefined', + Boolean: (v: BooleanValue) => v.booleanValue().toString(), + Number: (v: NumberValue) => { + const n = R(v); + if (n === 0 && Object.is(n, -0)) { + return '-0'; + } + return n.toString(); + }, + BigInt: (v: BigIntValue) => `${R(v)}n`, + String: (v: JSStringValue) => { + const s = JSON.stringify(v.stringValue()).slice(1, -1); + return `'${s}'`; + }, + Symbol: (v: SymbolValue) => `Symbol(${v.Description instanceof UndefinedValue ? '' : v.Description.stringValue()})`, + Object: (v: ObjectValue, ctx, i) => { + if (ctx.inspected.includes(v)) { + return '[Circular]'; + } + if ('PromiseState' in v) { + ctx.indent += 1; + const result = v.PromiseState === 'pending' ? 'undefined' : i((v as PromiseObject).PromiseResult!); + ctx.indent -= 1; + return `Promise { + [[PromiseState]]: ${v.PromiseState} + [[PromiseResult]]: ${result} +}`; + } + if ( + 'Module' in v + && v.Module instanceof CyclicModuleRecord + && v.Module.Status === 'linked' + && v.Module.DeferredNamespace === v + ) { + // Do not read the namespace to avoid triggering the module evaluation. + return 'Deferred Module { ... }'; + } + + if ('Call' in v) { + const name = v.properties.get('name'); + if (name && (name.Value! as JSStringValue).stringValue() !== '') { + return `[Function: ${(name.Value as JSStringValue).stringValue()}]`; + } + return '[Function]'; + } + + if ('ErrorData' in v) { + let e = X(Get(v, Value('stack'))); + if (!(e as JSStringValue).stringValue) { + const toString = X(Get(v, Value('toString'))); + e = X(Call(toString, v)); + } + return (e as JSStringValue).stringValue(); + } + + if (isRegExpObject(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(R((v as DateObject).DateValue)); + if (Number.isNaN(d.getTime())) { + return '[Date Invalid]'; + } + return `[Date ${d.toISOString()}]`; + } + + if ('BooleanData' in v) { + return `[Boolean ${i((v as BooleanObject).BooleanData)}]`; + } + if ('NumberData' in v) { + return `[Number ${i((v as NumberObject).NumberData)}]`; + } + if ('BigIntData' in v) { + return `[BigInt ${i((v as BigIntObject).BigIntData)}]`; + } + if ('StringData' in v) { + return `[String ${i((v as StringObject).StringData)}]`; + } + if ('SymbolData' in v) { + return `[Symbol ${i((v as SymbolObject).SymbolData)}]`; + } + if (isShadowRealmObject(v)) { + return '[ShadowRealm]'; + } + + ctx.indent += 1; + ctx.inspected.push(v); + + try { + const isArray = IsArray(v) === Value.true; + const isTypedArray = isTypedArrayObject(v); + if (isArray || isTypedArray) { + const length = X(LengthOfArrayLike(v)); + let holes = 0; + const flushHoles = () => { + if (holes > 0) { + out.push(`<${holes} empty items>`); + holes = 0; + } + }; + const out = []; + for (let j = 0; j < length; j += 1) { + const elem = X(v.GetOwnProperty(Value(j.toString()))); + if (elem instanceof UndefinedValue) { + holes += 1; + } else { + flushHoles(); + if (elem.Value) { + out.push(i(elem.Value)); + } else { + out.push(''); + } + } + } + flushHoles(); + 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)) as Descriptor; + if (C.Enumerable === Value.true) { + cache.push([ + key instanceof JSStringValue && 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.realm, v); + } finally { + ctx.indent -= 1; + ctx.inspected.pop(); + } + }, +} satisfies Partial>; + +// TODO: add an option to inspect so it can return string with color. +export function inspect(value: Value | ValueCompletion): string { + const context: InspectContext = { + realm: surroundingAgent.currentRealmRecord, + indent: 0, + inspected: [], + compact: false, + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const inner = (v: Value) => (INSPECTORS[v.type] as any)(v, context, inner); + if (value instanceof Completion) { + value = value.Value; + } + return inner(value); +} diff --git a/src/host-defined/test262-intrinsics.mts b/src/host-defined/test262-intrinsics.mts new file mode 100644 index 0000000..8d755d8 --- /dev/null +++ b/src/host-defined/test262-intrinsics.mts @@ -0,0 +1,164 @@ +// defined intrinsics that used in test262 +import { isArray } from '../helpers.mts'; +import { + CreateBuiltinFunction, DetachArrayBuffer, EnsureCompletion, inspect, isArrayBufferObject, isBuiltinFunctionObject, JSStringValue, ManagedRealm, NormalCompletion, OrdinaryObjectCreate, Q, skipDebugger, surroundingAgent, ToString, Value, type Arguments, type ValueCompletion, gc, + ParseScript, + ThrowCompletion, + ScriptEvaluation, + CreateNonEnumerableDataPropertyOrThrow, + type ValueEvaluator, + X, + type NativeSteps, + Call, + Assert, + HasProperty, + Set, +} from '#self'; + +/** https://github.com/tc39/test262/blob/main/INTERPRETING.md */ +export function createTest262Intrinsics(realm: ManagedRealm, printCompatMode: boolean) { + return realm.scope(() => { + let test262PrintHandle: ((str: string, value: Value) => void) | undefined; + const setPrintHandle = (f: typeof test262PrintHandle | undefined) => { + test262PrintHandle = f; + }; + const print = CreateBuiltinFunction((args: Arguments): ValueCompletion => { + if (surroundingAgent.debugger_isPreviewing) { + return NormalCompletion(Value.undefined); + } + /* node:coverage ignore next */ + if (test262PrintHandle) { + if (args[0] instanceof JSStringValue) { + test262PrintHandle(args[0].stringValue(), args[1] || Value.undefined); + return Value.undefined; + } + } else { + if (printCompatMode) { + const str: string[] = []; + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]!; + const s = EnsureCompletion(skipDebugger(ToString(arg))); + if (s.Type === 'throw') { + return s; + } + str.push(s.Value.stringValue()); + } + // eslint-disable-next-line no-console + console.log(...str); + return Value.undefined; + } else { + const formatted = args.map((a, i) => { + if (i === 0 && a instanceof JSStringValue) { + return a.stringValue(); + } + return inspect(a); + }).join(' '); + console.log(formatted); // eslint-disable-line no-console + } + } + return Value.undefined; + }, 0, Value('print'), []); + CreateNonEnumerableDataPropertyOrThrow(realm.GlobalObject, Value('print'), print); + + const $262 = OrdinaryObjectCreate.from({ + // TODO: AbstractModuleSource + createRealm: function* createRealm(): ValueEvaluator { + Q(surroundingAgent.debugger_cannotPreview); + const realm = new ManagedRealm(); + const { $262 } = createTest262Intrinsics(realm, printCompatMode); + return $262; + }, + detachArrayBuffer: function* detachArrayBuffer(arrayBuffer = Value.undefined) { + if (!isArrayBufferObject(arrayBuffer)) { + return surroundingAgent.Throw('TypeError', 'Raw', 'Argument must be an ArrayBuffer'); + } + Q(DetachArrayBuffer(arrayBuffer)); + return Value.undefined; + }, + evalScript: function* evalScript(sourceText) { + if (!(sourceText instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'Raw', 'Argument must be a string'); + } + const s = ParseScript(sourceText.stringValue(), surroundingAgent.currentRealmRecord); + if (isArray(s)) { + return ThrowCompletion(s[0]); + } + const status = yield* ScriptEvaluation(s); + return status; + }, + gc, + global: realm.GlobalObject, + // TODO: agent only if we have multi-threading. + + // engine262 only + spec: function* spec(value) { + if (isBuiltinFunctionObject(value) && value.nativeFunction.section) { + return Value(value.nativeFunction.section); + } + return Value.undefined; + }, + debugger: function* hostDebugger(value = Value.undefined, callValue = Value.false): ValueEvaluator { + if (surroundingAgent.debugger_isPreviewing) { + return Value.undefined; + } + // eslint-disable-next-line no-debugger + debugger; + if (callValue !== Value.false) { + Q(skipDebugger(Call(value, Value.undefined, []))); + } + return Value.undefined; + }, + }); + // engine262 only + CreateNonEnumerableDataPropertyOrThrow(realm.GlobalObject, Value('$262'), $262); + CreateNonEnumerableDataPropertyOrThrow(realm.GlobalObject, Value('$'), $262); + + return { + setPrintHandle, + $262, + }; + }); +} + +export function boostTest262Harness(realm: ManagedRealm) { + // test262/harness/regExpUtils.js + const key = Value('buildString'); + realm.scope(() => { + if (X(HasProperty(realm.GlobalObject, key)) === Value.true) { + X(Set(realm.GlobalObject, key, CreateBuiltinFunction(boostHarness.buildString, 1, key, []), Value.true)); + } + }); +} + +const boostHarness = { + * buildString(argumentsList): ValueEvaluator { + const json = Q(yield* Call(surroundingAgent.intrinsic('%JSON.stringify%'), Value.null, [argumentsList[0] || Value.undefined])); + Assert(json instanceof JSStringValue); + const jsonString = json.stringValue(); + + const { loneCodePoints, ranges } = JSON.parse(jsonString); + + // #region test262/harness/regExpUtils.js + const CHUNK_SIZE = 10000; + let result = String.fromCodePoint.apply(null, loneCodePoints); + for (let i = 0; i < ranges.length; i += 1) { + const range = ranges[i]; + const start = range[0]; + const end = range[1]; + const codePoints: number[] = []; + for (let length = 0, codePoint = start; codePoint <= end; codePoint += 1) { + codePoints[length] = codePoint; + length += 1; + if (length === CHUNK_SIZE) { + result += String.fromCodePoint.apply(null, codePoints); + length = 0; + codePoints.length = 0; + } + } + result += String.fromCodePoint.apply(null, codePoints); + } + // #endregion + + return Value(result); + }, +} satisfies Record; diff --git a/src/index.mts b/src/index.mts new file mode 100644 index 0000000..ea9d9b2 --- /dev/null +++ b/src/index.mts @@ -0,0 +1,48 @@ +export * from './abstract-ops/all.mts'; +export * from './execution-context/all.mts'; +export * from './static-semantics/all.mts'; +export * from './runtime-semantics/all.mts'; +export * from './value.mts'; +export * from './host-defined/engine.mts'; +export * from './completion.mts'; +export * from './parse.mts'; +export * from './modules.mts'; +export * from './host-defined/inspect.mts'; +export { type ErrorType, type Formattable, Throw } from './host-defined/error-messages.mts'; +export * from './evaluator.mts'; + +export { captureStack } from './helpers.mts'; +export { + gc, runJobQueue, type ManagedRealmHostDefined, ManagedRealm, +} from './api.mts'; +export type { ParseNode } from './parser/ParseNode.mts'; +export { createTest262Intrinsics, boostTest262Harness } from './host-defined/test262-intrinsics.mts'; +export { performDevtoolsEval } from './host-defined/debugger-eval.mts'; +export { + getHostDefinedErrorStack, skipDebugger, getCurrentStack, JSStringMap, JSStringSet, CallSite, CallFrame, type Mutable, PropertyKeyMap, kInternal, +} from './helpers.mts'; + +export { isMapObject, type MapObject } from './intrinsics/Map.mts'; +export { isSetObject, type SetObject } from './intrinsics/Set.mts'; +export { isRegExpObject, type RegExpObject } from './intrinsics/RegExp.mts'; +export { isWeakMapObject, type WeakMapObject } from './intrinsics/WeakMap.mts'; +export { isWeakSetObject, type WeakSetObject } from './intrinsics/WeakSet.mts'; +export { isDataViewObject, type DataViewObject } from './intrinsics/DataView.mts'; +export { isDateObject, type DateObject } from './intrinsics/Date.mts'; +export { DateProto_toISOString } from './intrinsics/DatePrototype.mts'; +export { isPromiseObject, type PromiseObject } from './intrinsics/Promise.mts'; +export { isTypedArrayObject, type TypedArrayObject } from './intrinsics/TypedArray.mts'; +export { isProxyExoticObject, type ProxyObject } from './intrinsics/Proxy.mts'; +export { isWeakRef, type WeakRefObject } from './intrinsics/WeakRef.mts'; +export { isFinalizationRegistryObject, type FinalizationRegistryObject } from './intrinsics/FinalizationRegistry.mts'; +export { isErrorObject } from './intrinsics/Error.mts'; +export { isShadowRealmObject, type ShadowRealmObject } from './intrinsics/ShadowRealm.mts'; + +export { isTemporalDurationObject, type TemporalDurationObject } from './intrinsics/Temporal/Duration.mts'; +export { isTemporalInstantObject, type TemporalInstantObject } from './intrinsics/Temporal/Instant.mts'; +export { isTemporalPlainDateObject, type TemporalPlainDateObject } from './intrinsics/Temporal/PlainDate.mts'; +export { isTemporalPlainDateTimeObject, type TemporalPlainDateTimeObject } from './intrinsics/Temporal/PlainDateTime.mts'; +export { isTemporalPlainMonthDayObject, type TemporalPlainMonthDayObject } from './intrinsics/Temporal/PlainMonthDay.mts'; +export { isTemporalPlainTimeObject, type TemporalPlainTimeObject } from './intrinsics/Temporal/PlainTime.mts'; +export { isTemporalPlainYearMonthObject, type TemporalPlainYearMonthObject } from './intrinsics/Temporal/PlainYearMonth.mts'; +export { isTemporalZonedDateTimeObject, type TemporalZonedDateTimeObject } from './intrinsics/Temporal/ZonedDateTime.mts'; diff --git a/src/intrinsics/AggregateError.mts b/src/intrinsics/AggregateError.mts new file mode 100644 index 0000000..42d8384 --- /dev/null +++ b/src/intrinsics/AggregateError.mts @@ -0,0 +1,68 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, Descriptor, type Arguments, type FunctionCallContext, + UndefinedValue, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { captureStack, callSiteToErrorString } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import type { ErrorObject } from './Error.mts'; +import { + ToString, + IteratorToList, + OrdinaryCreateFromConstructor, + DefinePropertyOrThrow, + InstallErrorCause, + CreateArrayFromList, + type FunctionObject, + CreateNonEnumerableDataPropertyOrThrow, + GetIterator, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-aggregate-error-constructor */ +function* AggregateErrorConstructor([errors = Value.undefined, message = Value.undefined, options = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. + let newTarget; + if (NewTarget instanceof UndefinedValue) { + newTarget = surroundingAgent.activeFunctionObject as FunctionObject; + } else { + newTarget = NewTarget; + } + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »). + const O = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%AggregateError.prototype%', [ + 'ErrorData', + 'HostDefinedErrorStack', + ])) as ErrorObject; + // 3. If message is not undefined, then + if (message !== Value.undefined) { + // a. Let msg be ? ToString(message). + const msg = Q(yield* ToString(message)); + // b. Perform ! CreateMethodProperty(O, "message", msg). + X(CreateNonEnumerableDataPropertyOrThrow(O, Value('message'), msg)); + } + Q(yield* InstallErrorCause(O, options)); + // 4. Let errorsList be ? IterableToList(errors). + const errorsList = Q(yield* IteratorToList(Q(yield* GetIterator(errors, 'sync')))); + // 5. Perform ! DefinePropertyOrThrow(O, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: ! CreateArrayFromList(errorsList) }). + X(DefinePropertyOrThrow(O, Value('errors'), Descriptor({ + Configurable: Value.true, + Enumerable: Value.false, + Writable: Value.true, + Value: CreateArrayFromList(errorsList), + }))); + + // NON-SPEC + const S = captureStack(); + O.HostDefinedErrorStack = S.stack; + O.ErrorData = X(callSiteToErrorString(O, S.stack, S.nativeStack)); + + // 7. Return O. + return O; +} + +export function bootstrapAggregateError(realmRec: Realm) { + const c = bootstrapConstructor(realmRec, AggregateErrorConstructor, 'AggregateError', 2, realmRec.Intrinsics['%AggregateError.prototype%'], []); + c.Prototype = realmRec.Intrinsics['%Error%']; + realmRec.Intrinsics['%AggregateError%'] = c; +} diff --git a/src/intrinsics/AggregateErrorPrototype.mts b/src/intrinsics/AggregateErrorPrototype.mts new file mode 100644 index 0000000..65222f4 --- /dev/null +++ b/src/intrinsics/AggregateErrorPrototype.mts @@ -0,0 +1,12 @@ +import { Value } from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { Realm } from '#self'; + +export function bootstrapAggregateErrorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['name', Value('AggregateError')], + ['message', Value('')], + ], realmRec.Intrinsics['%Error.prototype%'], 'AggregateError'); + + realmRec.Intrinsics['%AggregateError.prototype%'] = proto; +} diff --git a/src/intrinsics/Array.mts b/src/intrinsics/Array.mts new file mode 100644 index 0000000..06d1ddf --- /dev/null +++ b/src/intrinsics/Array.mts @@ -0,0 +1,326 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + Await, + IfAbruptCloseIterator, + Q, + ThrowCompletion, X, + type ValueCompletion, + type ValueEvaluator, +} from '../completion.mts'; +import { + NumberValue, + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { __ts_cast__, OutOfRange } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + ArrayCreate, + Assert, + Call, + Construct, + CreateDataProperty, + CreateDataPropertyOrThrow, + Get, + GetMethod, + GetPrototypeFromConstructor, + IsArray, + IsCallable, + IsConstructor, + IteratorClose, + Set, + LengthOfArrayLike, + ToObject, + ToString, + ToUint32, + F, R, + type FunctionObject, + IteratorStepValue, + GetIteratorFromMethod, + type IteratorRecord, + CreateAsyncFromSyncIterator, + AsyncIteratorClose, + IteratorComplete, +} from '#self'; +import { + Realm, + IfAbruptCloseAsyncIterator, IteratorValue, Throw, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-array-constructor */ +function* ArrayConstructor(argumentsList: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + const numberOfArgs = argumentsList.length; + if (numberOfArgs === 0) { + /** https://tc39.es/ecma262/#sec-array-constructor-array */ + Assert(numberOfArgs === 0); + if (NewTarget instanceof UndefinedValue) { + NewTarget = surroundingAgent.activeFunctionObject as FunctionObject; + } + const proto = X(GetPrototypeFromConstructor(NewTarget, '%Array.prototype%')); + return ArrayCreate(0, proto); + } else if (numberOfArgs === 1) { + /** https://tc39.es/ecma262/#sec-array-len */ + const [len] = argumentsList; + Assert(numberOfArgs === 1); + if (NewTarget instanceof UndefinedValue) { + NewTarget = surroundingAgent.activeFunctionObject as FunctionObject; + } + const proto = X(GetPrototypeFromConstructor(NewTarget, '%Array.prototype%')); + const array = X(ArrayCreate(0, proto)); + let intLen; + if (!(len instanceof NumberValue)) { + const defineStatus = X(CreateDataProperty(array, Value('0'), len!)); + Assert(defineStatus === Value.true); + intLen = F(1); + } else { + intLen = X(ToUint32(len)); + if (R(intLen) !== R(len)) { + return surroundingAgent.Throw('RangeError', 'InvalidArrayLength', len); + } + } + yield* Set(array, Value('length'), intLen, Value.true); + return array; + } else if (numberOfArgs >= 2) { + /** https://tc39.es/ecma262/#sec-array-items */ + const items = argumentsList; + Assert(numberOfArgs >= 2); + if (NewTarget instanceof UndefinedValue) { + NewTarget = surroundingAgent.activeFunctionObject as FunctionObject; + } + const proto = Q(yield* GetPrototypeFromConstructor(NewTarget, '%Array.prototype%')); + const array = X(ArrayCreate(0, proto)); + let k = 0; + while (k < numberOfArgs) { + const Pk = X(ToString(F(k))); + const itemK = items[k]!; + const defineStatus = X(CreateDataProperty(array, Pk, itemK)); + Assert(defineStatus === Value.true); + k += 1; + } + Assert(R(X(Get(array, Value('length'))) as NumberValue) === numberOfArgs); + return array; + } + + throw new OutOfRange('ArrayConstructor', numberOfArgs); +} + +/** https://tc39.es/ecma262/#sec-array.from */ +function* Array_from([items = Value.undefined, mapper = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const C = thisValue; + let mapping; + let A; + if (mapper === Value.undefined) { + mapping = false; + } else { + if (!IsCallable(mapper)) { + return Throw.TypeError('$1 is not a function', mapper); + } + mapping = true; + } + const usingIterator = Q(yield* GetMethod(items, wellKnownSymbols.iterator)); + if (!(usingIterator instanceof UndefinedValue)) { + if (IsConstructor(C)) { + A = Q(yield* Construct(C)); + } else { + A = X(ArrayCreate(0)); + } + const iteratorRecord = Q(yield* GetIteratorFromMethod(items, 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(yield* IteratorClose(iteratorRecord, error)); + } + const Pk = X(ToString(F(k))); + const next = Q(yield* IteratorStepValue(iteratorRecord)); + if (next === 'done') { + Q(yield* Set(A, Value('length'), F(k), Value.true)); + return A; + } + let mappedValue; + if (mapping) { + mappedValue = yield* Call(mapper, thisArg, [next, F(k)]); + IfAbruptCloseIterator(mappedValue, iteratorRecord); + __ts_cast__(mappedValue); + } else { + mappedValue = next; + } + const defineStatus = yield* CreateDataPropertyOrThrow(A, Pk, mappedValue); + IfAbruptCloseIterator(defineStatus, iteratorRecord); + k += 1; + } + } + const arrayLike = X(ToObject(items)); + const len = Q(yield* LengthOfArrayLike(arrayLike)); + if (IsConstructor(C)) { + A = Q(yield* Construct(C, [F(len)])); + } else { + A = Q(ArrayCreate(len)); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = Q(yield* Get(arrayLike, Pk)); + let mappedValue; + if (mapping === true) { + mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)])); + } else { + mappedValue = kValue; + } + Q(yield* CreateDataPropertyOrThrow(A, Pk, mappedValue)); + k += 1; + } + Q(yield* Set(A, Value('length'), F(len), Value.true)); + return A; +} + +/** https://tc39.es/ecma262/#sec-array.fromasync */ +function* Array_fromAsync([items = Value.undefined, mapper = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const C = thisValue; + let mapping = false; + if (mapper !== Value.undefined) { + if (!IsCallable(mapper)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', mapper); + } + mapping = true; + } + let iteratorRecord: IteratorRecord | undefined; + const usingAsyncIterator = Q(yield* GetMethod(items, wellKnownSymbols.asyncIterator)); + let usingSyncIterator: UndefinedValue | FunctionObject = Value.undefined; + if (usingAsyncIterator instanceof UndefinedValue) { + usingSyncIterator = Q(yield* GetMethod(items, wellKnownSymbols.iterator)); + if (!(usingSyncIterator instanceof UndefinedValue)) { + iteratorRecord = CreateAsyncFromSyncIterator(Q(yield* GetIteratorFromMethod(items, usingSyncIterator))); + } + } else { + iteratorRecord = Q(yield* GetIteratorFromMethod(items, usingAsyncIterator)); + } + + if (iteratorRecord) { + const MAX_SAFE_INTEGER = (2 ** 53) - 1; + let A: ObjectValue; + if (IsConstructor(C)) { + A = Q(yield* Construct(C)); + } else { + A = X(ArrayCreate(0)); + } + + let k = 0; + while (true) { + if (k > MAX_SAFE_INTEGER) { + const error = surroundingAgent.Throw('TypeError', 'OutOfRange', k); + return Q(yield* AsyncIteratorClose(iteratorRecord, error)); + } + + const Pk = X(ToString(F(k))); + let nextResult: Value = Q(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator)); + nextResult = Q(yield* Await(nextResult)); + if (!(nextResult instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', nextResult); + } + const done = Q(yield* IteratorComplete(nextResult)); + if (done === Value.true) { + Q(yield* Set(A, Value('length'), F(k), Value.true)); + return A; + } + + const nextValue: ValueCompletion = Q(yield* IteratorValue(nextResult)); + let mappedValue; + if (mapping) { + mappedValue = (yield* Call(mapper, thisArg, [nextValue, F(k)])); + IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord); + __ts_cast__(mappedValue); + mappedValue = yield* Await(mappedValue); + IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord); + __ts_cast__(mappedValue); + } else { + mappedValue = nextValue; + } + + const defineStatus = yield* CreateDataPropertyOrThrow(A, Pk, mappedValue); + IfAbruptCloseAsyncIterator(defineStatus, iteratorRecord); + k += 1; + } + } else { + const arrayLike = X(ToObject(items)); + const len = Q(yield* LengthOfArrayLike(arrayLike)); + + let A: ObjectValue; + if (IsConstructor(C)) { + A = Q(yield* Construct(C, [F(len)])); + } else { + A = Q(ArrayCreate(len)); + } + + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let kValue = Q(yield* Get(arrayLike, Pk)); + kValue = Q(yield* Await(kValue)); + let mappedValue: Value; + if (mapping) { + mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)])); + mappedValue = Q(yield* Await(mappedValue)); + } else { + mappedValue = kValue; + } + Q(yield* CreateDataPropertyOrThrow(A, Pk, mappedValue)); + k += 1; + } + + Q(yield* Set(A, Value('length'), F(len), Value.true)); + return A; + } +} + +/** https://tc39.es/ecma262/#sec-array.isarray */ +function Array_isArray([arg = Value.undefined]: Arguments): ValueCompletion { + return Q(IsArray(arg)); +} + +/** https://tc39.es/ecma262/#sec-array.of */ +function* Array_of(items: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const len = items.length; + // Let items be the List of arguments passed to this function. + const C = thisValue; + let A; + if (IsConstructor(C)) { + A = Q(yield* Construct(C, [F(len)])); + } else { + A = Q(ArrayCreate(len)); + } + let k = 0; + while (k < len) { + const kValue = items[k]!; + const Pk = X(ToString(F(k))); + Q(yield* CreateDataPropertyOrThrow(A, Pk, kValue)); + k += 1; + } + Q(yield* Set(A, Value('length'), F(len), Value.true)); + return A; +} + +/** https://tc39.es/ecma262/#sec-get-array-@@species */ +function Array_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) { + return thisValue; +} + +export function bootstrapArray(realmRec: Realm) { + const proto = realmRec.Intrinsics['%Array.prototype%']; + + const cons = bootstrapConstructor(realmRec, ArrayConstructor, 'Array', 1, proto, [ + ['from', Array_from, 1], + ['fromAsync', Array_fromAsync, 1, undefined, true], + ['isArray', Array_isArray, 1], + ['of', Array_of, 0], + [wellKnownSymbols.species, [Array_speciesGetter]], + ]); + + realmRec.Intrinsics['%Array%'] = cons; +} diff --git a/src/intrinsics/ArrayBuffer.mts b/src/intrinsics/ArrayBuffer.mts new file mode 100644 index 0000000..ccd4146 --- /dev/null +++ b/src/intrinsics/ArrayBuffer.mts @@ -0,0 +1,49 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, UndefinedValue, Value, wellKnownSymbols, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + ToIndex, AllocateArrayBuffer, type FunctionObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-arraybuffer-length */ +function* ArrayBufferConstructor(this: FunctionObject, [length = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let byteLength be ? ToIndex(length). + const byteLength = Q(yield* ToIndex(length)); + // 3. Return ? AllocateArrayBuffer(NewTarget, byteLength). + return Q(yield* AllocateArrayBuffer(NewTarget, byteLength)); +} + +/** https://tc39.es/ecma262/#sec-arraybuffer.isview */ +function ArrayBuffer_isView([arg = Value.undefined]: Arguments) { + // 1. If Type(arg) is not Object, return false. + if (!(arg instanceof ObjectValue)) { + 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; +} + +/** https://tc39.es/ecma262/#sec-get-arraybuffer-@@species */ +function ArrayBuffer_species(_: Arguments, { thisValue }: FunctionCallContext) { + return thisValue; +} + +export function bootstrapArrayBuffer(realmRec: Realm) { + 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/src/intrinsics/ArrayBufferPrototype.mts b/src/intrinsics/ArrayBufferPrototype.mts new file mode 100644 index 0000000..1d222ac --- /dev/null +++ b/src/intrinsics/ArrayBufferPrototype.mts @@ -0,0 +1,120 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + DataBlock, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + RequireInternalSlot, IsDetachedBuffer, IsSharedArrayBuffer, + SpeciesConstructor, Construct, ToIntegerOrInfinity, SameValue, CopyDataBlockBytes, + F, + type ArrayBufferObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength */ +function ArrayBufferProto_byteLength(_args: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let O be this value. + const O = thisValue as ArrayBufferObject; + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + Q(RequireInternalSlot(O, 'ArrayBufferData')); + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + if (IsSharedArrayBuffer(O)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferShared'); + } + // 4. If IsDetachedBuffer(O) is true, return +0𝔽. + if (IsDetachedBuffer(O)) { + return F(+0); + } + // 5. Let length be O.[[ArrayBufferByteLength]]. + const length = O.ArrayBufferByteLength; + // 6. Return length. + return F(length); +} + +/** https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice */ +function* ArrayBufferProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let O be the this value. + const O = thisValue as ArrayBufferObject; + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + Q(RequireInternalSlot(O, 'ArrayBufferData')); + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + if (IsSharedArrayBuffer(O)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferShared'); + } + // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if (IsDetachedBuffer(O)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Let len be O.[[ArrayBufferByteLength]]. + const len = O.ArrayBufferByteLength; + // 6. Let relativeStart be ? ToIntegerOrInfinity(start). + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + 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 ? ToIntegerOrInfinity(end). + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + 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(yield* SpeciesConstructor(O, surroundingAgent.intrinsic('%ArrayBuffer%'))); + // 12. Let new be ? Construct(ctor, « newLen »). + const newO = Q(yield* Construct(ctor, [F(newLen)])) as ArrayBufferObject; + // 13. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). + Q(RequireInternalSlot(newO, 'ArrayBufferData')); + // 14. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + if (IsSharedArrayBuffer(newO)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferShared'); + } + // 15. If IsDetachedBuffer(new) is true, throw a TypeError exception. + if (IsDetachedBuffer(newO)) { + return surroundingAgent.Throw('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 < 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)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 20. Let fromBuf be O.[[ArrayBufferData]]. + const fromBuf = O.ArrayBufferData as DataBlock; + // 21. Let toBuf be new.[[ArrayBufferData]]. + const toBuf = newO.ArrayBufferData as DataBlock; + // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). + CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen); + // 23. Return new. + return newO; +} + +export function bootstrapArrayBufferPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['byteLength', [ArrayBufferProto_byteLength]], + ['slice', ArrayBufferProto_slice, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'ArrayBuffer'); + + realmRec.Intrinsics['%ArrayBuffer.prototype%'] = proto; +} diff --git a/src/intrinsics/ArrayIteratorPrototype.mts b/src/intrinsics/ArrayIteratorPrototype.mts new file mode 100644 index 0000000..1ee2a4c --- /dev/null +++ b/src/intrinsics/ArrayIteratorPrototype.mts @@ -0,0 +1,23 @@ +import { Q, type ValueEvaluator } from '../completion.mts'; +import { + Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + GeneratorResume, +} from '#self'; +import type { Realm } from '#self'; + +/** https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next */ +function* ArrayIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Return ? GeneratorResume(this value, empty, "%ArrayIteratorPrototype%"). + return Q(yield* GeneratorResume(thisValue, undefined, Value('%ArrayIteratorPrototype%'))); +} + +export function bootstrapArrayIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', ArrayIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'Array Iterator'); + + realmRec.Intrinsics['%ArrayIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/ArrayPrototype.mts b/src/intrinsics/ArrayPrototype.mts new file mode 100644 index 0000000..105ecf3 --- /dev/null +++ b/src/intrinsics/ArrayPrototype.mts @@ -0,0 +1,755 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BooleanValue, + Descriptor, + JSStringValue, + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { assignProps } from './bootstrap.mts'; +import { ArrayProto_sortBody, bootstrapArrayPrototypeShared, SortIndexedProperties } from './ArrayPrototypeShared.mts'; +import { + ArrayCreate, + ArraySpeciesCreate, + Assert, + Call, + CreateArrayIterator, + CreateDataProperty, + CreateDataPropertyOrThrow, + DeletePropertyOrThrow, + Get, + HasProperty, + IsArray, + IsCallable, + IsConcatSpreadable, + Set, + CompareArrayElements, + LengthOfArrayLike, + OrdinaryObjectCreate, + ToBoolean, + ToIntegerOrInfinity, + ToObject, + ToString, + F, + type FunctionObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-array.prototype.concat */ +function* ArrayProto_concat(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const A = Q(yield* ArraySpeciesCreate(O, 0)); + let n = 0; + const items = [O, ...args]; + while (items.length > 0) { + const E = items.shift()!; + const spreadable = Q(yield* IsConcatSpreadable(E)); + __ts_cast__(E); + if (spreadable === Value.true) { + let k = 0; + const len = Q(yield* LengthOfArrayLike(E)); + if (n + len > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + while (k < len) { + const P = X(ToString(F(k))); + const exists = Q(yield* HasProperty(E, P)); + if (exists === Value.true) { + const subElement = Q(yield* Get(E, P)); + const nStr = X(ToString(F(n))); + Q(yield* CreateDataPropertyOrThrow(A, nStr, subElement)); + } + n += 1; + k += 1; + } + } else { + if (n >= (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + const nStr = X(ToString(F(n))); + Q(yield* CreateDataPropertyOrThrow(A, nStr, E)); + n += 1; + } + } + Q(yield* Set(A, Value('length'), F(n), Value.true)); + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.copywithin */ +function* ArrayProto_copyWithin([target = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const relativeTarget = Q(yield* ToIntegerOrInfinity(target)); + let to; + if (relativeTarget < 0) { + to = Math.max(len + relativeTarget, 0); + } else { + to = Math.min(relativeTarget, len); + } + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let from; + if (relativeStart < 0) { + from = Math.max(len + relativeStart, 0); + } else { + from = Math.min(relativeStart, len); + } + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + let count = Math.min(final - from, len - 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: JSStringValue = X(ToString(F(from))); + const toKey: JSStringValue = X(ToString(F(to))); + const fromPresent = Q(yield* HasProperty(O, fromKey)); + if (fromPresent === Value.true) { + const fromVal = Q(yield* Get(O, fromKey)); + Q(yield* Set(O, toKey, fromVal, Value.true)); + } else { + Q(yield* DeletePropertyOrThrow(O, toKey)); + } + from += direction; + to += direction; + count -= 1; + } + return O; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.entries */ +function ArrayProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = Q(ToObject(thisValue)); + return CreateArrayIterator(O, 'key+value'); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.fill */ +function* ArrayProto_fill([value = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let k; + if (relativeStart < 0) { + k = Math.max(len + relativeStart, 0); + } else { + k = Math.min(relativeStart, len); + } + let relativeEnd; + if (end instanceof UndefinedValue) { + relativeEnd = len; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + let final; + if (relativeEnd < 0) { + final = Math.max(len + relativeEnd, 0); + } else { + final = Math.min(relativeEnd, len); + } + while (k < final) { + const Pk: JSStringValue = X(ToString(F(k))); + Q(yield* Set(O, Pk, value, Value.true)); + k += 1; + } + return O; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.filter */ +function* ArrayProto_filter([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + const A = Q(yield* ArraySpeciesCreate(O, 0)); + let k = 0; + let to = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kPresent = Q(yield* HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + const selected = ToBoolean(Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O]))); + if (selected === Value.true) { + Q(yield* CreateDataPropertyOrThrow(A, X(ToString(F(to))), kValue)); + to += 1; + } + } + k += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-flattenintoarray */ +function* FlattenIntoArray(target: ObjectValue, source: ObjectValue, sourceLen: number, start: number, depth: number, mapperFunction?: FunctionObject, thisArg?: Value): PlainEvaluator { + Assert(target instanceof ObjectValue); + Assert(source instanceof ObjectValue); + 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(F(sourceIndex))); + const exists = Q(yield* HasProperty(source, P)); + if (exists === Value.true) { + let element = Q(yield* Get(source, P)); + if (mapperFunction) { + Assert(!!thisArg); + element = Q(yield* Call(mapperFunction, thisArg, [element, F(sourceIndex), source])); + } + let shouldFlatten: BooleanValue = Value.false; + if (depth > 0) { + shouldFlatten = Q(IsArray(element)); + } + if (shouldFlatten === Value.true) { + const elementLen = Q(yield* LengthOfArrayLike(element as ObjectValue)); + targetIndex = Q(yield* FlattenIntoArray(target, element as ObjectValue, elementLen, targetIndex, depth - 1)); + } else { + if (targetIndex >= (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'OutOfRange', targetIndex); + } + Q(yield* CreateDataPropertyOrThrow(target, X(ToString(F(targetIndex))), element)); + targetIndex += 1; + } + } + sourceIndex += 1; + } + return targetIndex; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.flat */ +function* ArrayProto_flat([depth = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const sourceLen = Q(yield* LengthOfArrayLike(O)); + let depthNum = 1; + if (depth !== Value.undefined) { + depthNum = Q(yield* ToIntegerOrInfinity(depth)); + } + const A = Q(yield* ArraySpeciesCreate(O, 0)); + Q(yield* FlattenIntoArray(A, O, sourceLen, 0, depthNum)); + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.flatmap */ +function* ArrayProto_flatMap([mapperFunction = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const sourceLen = Q(yield* LengthOfArrayLike(O)); + if (!IsCallable(mapperFunction)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', mapperFunction); + } + const A = Q(yield* ArraySpeciesCreate(O, 0)); + Q(yield* FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg)); + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.keys */ +function ArrayProto_keys(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = Q(ToObject(thisValue)); + return CreateArrayIterator(O, 'key'); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.map */ +function* ArrayProto_map([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + const A = Q(yield* ArraySpeciesCreate(O, len)); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kPresent = Q(yield* HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + const mappedValue = Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O])); + Q(yield* CreateDataPropertyOrThrow(A, Pk, mappedValue)); + } + k += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.pop */ +function* ArrayProto_pop(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + if (len === 0) { + Q(yield* Set(O, Value('length'), F(+0), Value.true)); + return Value.undefined; + } else { + const newLen = len - 1; + const index = Q(yield* ToString(F(newLen))); + const element = Q(yield* Get(O, index)); + Q(yield* DeletePropertyOrThrow(O, index)); + Q(yield* Set(O, Value('length'), F(newLen), Value.true)); + return element; + } +} + +/** https://tc39.es/ecma262/#sec-array.prototype.push */ +function* ArrayProto_push(_items: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const items = [..._items]; + const O = Q(ToObject(thisValue)); + let len = Q(yield* LengthOfArrayLike(O)); + const argCount = items.length; + if (len + argCount > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + while (items.length > 0) { + const E = items.shift()!; + Q(yield* Set(O, X(ToString(F(len))), E, Value.true)); + len += 1; + } + Q(yield* Set(O, Value('length'), F(len), Value.true)); + return F(len); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.shift */ +function* ArrayProto_shift(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + if (len === 0) { + Q(yield* Set(O, Value('length'), F(+0), Value.true)); + return Value.undefined; + } + const first = Q(yield* Get(O, Value('0'))); + let k = 1; + while (k < len) { + const from = X(ToString(F(k))); + const to = X(ToString(F(k - 1))); + const fromPresent = Q(yield* HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromVal = Q(yield* Get(O, from)); + Q(yield* Set(O, to, fromVal, Value.true)); + } else { + Q(yield* DeletePropertyOrThrow(O, to)); + } + k += 1; + } + Q(yield* DeletePropertyOrThrow(O, X(ToString(F(len - 1))))); + Q(yield* Set(O, Value('length'), F(len - 1), Value.true)); + return first; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.slice */ +function* ArrayProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let k; + if (relativeStart < 0) { + k = Math.max(len + relativeStart, 0); + } else { + k = Math.min(relativeStart, len); + } + let relativeEnd; + if (end instanceof UndefinedValue) { + relativeEnd = len; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + 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(yield* ArraySpeciesCreate(O, count)); + let n = 0; + while (k < final) { + const Pk: JSStringValue = X(ToString(F(k))); + const kPresent = Q(yield* HasProperty(O, Pk)); + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + const nStr = X(ToString(F(n))); + Q(yield* CreateDataPropertyOrThrow(A, nStr, kValue)); + } + k += 1; + n += 1; + } + Q(yield* Set(A, Value('length'), F(n), Value.true)); + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.sort */ +function* ArrayProto_sort([comparefn = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + if (comparefn !== Value.undefined && !IsCallable(comparefn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', comparefn); + } + const obj = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(obj)); + + return yield* ArrayProto_sortBody(obj, len, (x, y) => CompareArrayElements(x, y, comparefn)); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.tosorted */ +function* ArrayProto_toSorted([comparator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + if (comparator !== Value.undefined && !IsCallable(comparator)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', comparator); + } + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const A = Q(ArrayCreate(len)); + const SortCompare = function* SortCompare(x: Value, y: Value) { + return yield* CompareArrayElements(x, y, comparator); + }; + const sortedList = Q(yield* SortIndexedProperties(O, len, SortCompare, 'read-through-holes')); + let j = 0; + while (j < len) { + X(CreateDataPropertyOrThrow(A, X(ToString(F(j))), sortedList[j])); + j += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.splice */ +function* ArrayProto_splice(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const [start = Value.undefined, deleteCount = Value.undefined, ...items] = args; + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + 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(yield* ToIntegerOrInfinity(deleteCount)); + actualDeleteCount = Math.min(Math.max(dc, 0), len - actualStart); + } + if (len + insertCount - actualDeleteCount > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + const A = Q(yield* ArraySpeciesCreate(O, actualDeleteCount)); + let k = 0; + while (k < actualDeleteCount) { + const from = X(ToString(F(actualStart + k))); + const fromPresent = Q(yield* HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(yield* Get(O, from)); + Q(yield* CreateDataPropertyOrThrow(A, X(ToString(F(k))), fromValue)); + } + k += 1; + } + Q(yield* Set(A, Value('length'), F(actualDeleteCount), Value.true)); + const itemCount = items.length; + if (itemCount < actualDeleteCount) { + k = actualStart; + while (k < len - actualDeleteCount) { + const from: JSStringValue = X(ToString(F(k + actualDeleteCount))); + const to = X(ToString(F(k + itemCount))); + const fromPresent = Q(yield* HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(yield* Get(O, from)); + Q(yield* Set(O, to, fromValue, Value.true)); + } else { + Q(yield* DeletePropertyOrThrow(O, to)); + } + k += 1; + } + k = len; + while (k > len - actualDeleteCount + itemCount) { + Q(yield* DeletePropertyOrThrow(O, X(ToString(F(k - 1))))); + k -= 1; + } + } else if (itemCount > actualDeleteCount) { + k = len - actualDeleteCount; + while (k > actualStart) { + const from: JSStringValue = X(ToString(F(k + actualDeleteCount - 1))); + const to = X(ToString(F(k + itemCount - 1))); + const fromPresent = Q(yield* HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(yield* Get(O, from)); + Q(yield* Set(O, to, fromValue, Value.true)); + } else { + Q(yield* DeletePropertyOrThrow(O, to)); + } + k -= 1; + } + } + k = actualStart; + while (items.length > 0) { + const E = items.shift()!; + Q(yield* Set(O, X(ToString(F(k))), E, Value.true)); + k += 1; + } + Q(yield* Set(O, Value('length'), F(len - actualDeleteCount + itemCount), Value.true)); + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.tospliced */ +function* ArrayProto_toSpliced(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const [start = Value.undefined, skipCount = Value.undefined, ...items] = args as Value[]; + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let actualStart; + if (relativeStart === -Infinity) { + actualStart = 0; + } else if (relativeStart < 0) { + actualStart = Math.max(len + relativeStart, 0); + } else { + actualStart = Math.min(relativeStart, len); + } + const insertCount = items.length; + let actualSkipCount; + if (args[0] === undefined) { + actualSkipCount = 0; + } else if (args[1] === undefined) { + actualSkipCount = len - actualStart; + } else { + const sc = Q(yield* ToIntegerOrInfinity(skipCount)); + actualSkipCount = Math.min(Math.max(sc, 0), len - actualStart); + } + const newLen = len - actualSkipCount + insertCount; + if (newLen > (2 ** 53) - 1) { + return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength'); + } + const A = Q(ArrayCreate(newLen)); + let i = 0; + let r = actualStart + actualSkipCount; + while (i < actualStart) { + const Pi = X(ToString(F(i))); + const iValue = Q(yield* Get(O, Pi)); + X(CreateDataPropertyOrThrow(A, Pi, iValue)); + i += 1; + } + for (const E of items) { + const Pi = X(ToString(F(i))); + X(CreateDataPropertyOrThrow(A, Pi, E)); + i += 1; + } + while (i < newLen) { + const Pi = X(ToString(F(i))); + const from = X(ToString(F(r))); + const fromValue = Q(yield* Get(O, from)); + X(CreateDataPropertyOrThrow(A, Pi, fromValue)); + i += 1; + r += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.with */ +function* ArrayProto_with([index = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const relativeIndex = Q(yield* ToIntegerOrInfinity(index)); + let actualIndex; + if (relativeIndex >= 0) { + actualIndex = relativeIndex; + } else { + actualIndex = len + relativeIndex; + } + if (actualIndex >= len || actualIndex < 0) { + return surroundingAgent.Throw('RangeError', 'OutOfRange', index); + } + const A = Q(ArrayCreate(len)); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let fromValue; + if (k === actualIndex) { + fromValue = value; + } else { + fromValue = Q(yield* Get(O, Pk)); + } + X(CreateDataPropertyOrThrow(A, Pk, fromValue)); + k += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-array.prototype.tostring */ +function* ArrayProto_toString(_a: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const array = Q(ToObject(thisValue)); + let func = Q(yield* Get(array, Value('join'))); + if (!IsCallable(func)) { + func = surroundingAgent.intrinsic('%Object.prototype.toString%'); + } + return Q(yield* Call(func, array)); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.unshift */ +function* ArrayProto_unshift(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + 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(F(k - 1))); + const to = X(ToString(F(k + argCount - 1))); + const fromPresent = Q(yield* HasProperty(O, from)); + if (fromPresent === Value.true) { + const fromValue = Q(yield* Get(O, from)); + Q(yield* Set(O, to, fromValue, Value.true)); + } else { + Q(yield* DeletePropertyOrThrow(O, to)); + } + k -= 1; + } + let j = 0; + const items = [...args]; + while (items.length !== 0) { + const E = items.shift()!; + const jStr = X(ToString(F(j))); + Q(yield* Set(O, jStr, E, Value.true)); + j += 1; + } + } + Q(yield* Set(O, Value('length'), F(len + argCount), Value.true)); + return F(len + argCount); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.values */ +function ArrayProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = Q(ToObject(thisValue)); + return CreateArrayIterator(O, 'value'); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.at */ +function* ArrayProto_at([index = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 2. Let len be ? LengthOfArrayLike(O). + const len = Q(yield* LengthOfArrayLike(O)); + // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). + const relativeIndex = Q(yield* ToIntegerOrInfinity(index)); + let k; + // 4. If relativeIndex ≥ 0, then + if (relativeIndex >= 0) { + // a. Let k be relativeIndex. + k = relativeIndex; + } else { // 5. Else, + // a. Let k be len + relativeIndex. + k = len + relativeIndex; + } + // 6. If k < 0 or k ≥ len, then return undefined. + if (k < 0 || k >= len) { + return Value.undefined; + } + // 7. Return ? Get(O, ! ToString(k)). + return Q(yield* Get(O, X(ToString(F(k))))); +} + +/** https://tc39.es/ecma262/#sec-array.prototype.toreversed */ +function* ArrayProto_toReversed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const len = Q(yield* LengthOfArrayLike(O)); + const A = Q(ArrayCreate(len)); + let k = 0; + while (k < len) { + const from = X(ToString(F(len - 1 - k))); + const Pk = X(ToString(F(k))); + const fromValue = Q(yield* Get(O, from)); + X(CreateDataPropertyOrThrow(A, Pk, fromValue)); + k += 1; + } + return A; +} + +export function bootstrapArrayPrototype(realmRec: Realm) { + const proto = X(ArrayCreate(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], + ['at', ArrayProto_at, 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], + ['toSorted', ArrayProto_toSorted, 1], + ['splice', ArrayProto_splice, 2], + ['toSpliced', ArrayProto_toSpliced, 2], + ['toString', ArrayProto_toString, 0], + ['unshift', ArrayProto_unshift, 1], + ['values', ArrayProto_values, 0], + ['with', ArrayProto_with, 2], + ['toReversed', ArrayProto_toReversed, 0], + ]); + + bootstrapArrayPrototypeShared(realmRec, proto, 'Array'); + + X(proto.DefineOwnProperty(wellKnownSymbols.iterator, X(proto.GetOwnProperty(Value('values'))) as Descriptor)); + + { + const unscopableList = OrdinaryObjectCreate(Value.null); + Assert(X(CreateDataProperty(unscopableList, Value('at'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('copyWithin'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('entries'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('fill'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('find'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('findIndex'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('findLast'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('findLastIndex'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('flat'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('flatMap'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('includes'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('keys'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('toReversed'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('toSorted'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, Value('toSpliced'), Value.true)) === Value.true); + Assert(X(CreateDataProperty(unscopableList, 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, Value('values'))) as FunctionObject; + + realmRec.Intrinsics['%Array.prototype%'] = proto; +} diff --git a/src/intrinsics/ArrayPrototypeShared.mts b/src/intrinsics/ArrayPrototypeShared.mts new file mode 100644 index 0000000..936e14e --- /dev/null +++ b/src/intrinsics/ArrayPrototypeShared.mts @@ -0,0 +1,691 @@ +import { + NormalCompletion, + Q, ThrowCompletion, X, type ValueEvaluator, + type ValueCompletion, +} from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { + NullValue, NumberValue, ObjectValue, UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { assignProps } from './bootstrap.mts'; +import { ValidateTypedArray } from './TypedArray.mts'; +import { + Assert, + Call, + DeletePropertyOrThrow, + Get, + HasProperty, + Invoke, + IsCallable, + SameValueZero, + Set, + IsStrictlyEqual, + ToBoolean, + ToIntegerOrInfinity, + ToObject, + ToString, + F, R, + Realm, + LengthOfArrayLike, skipDebugger, TypedArrayLength, +} from '#self'; + +// Algorithms and methods shared between %Array.prototype% and +// %TypedArray.prototype%. + +/** https://tc39.es/ecma262/#sec-array.prototype.sort */ +/** https://tc39.es/ecma262/#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: ObjectValue, len: number, SortCompare: (x: Value, y: Value) => ValueEvaluator, internalMethodsRestricted = false): ValueEvaluator { + const items = []; + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + if (internalMethodsRestricted) { + items.push(Q(yield* Get(obj, Pk))); + } else { + const kPresent = Q(yield* HasProperty(obj, Pk)); + if (kPresent === Value.true) { + const kValue = Q(yield* Get(obj, Pk)); + items.push(kValue); + } + } + k += 1; + } + const itemCount = items.length; + + // Mergesort. + const lBuffer = []; + const rBuffer = []; + for (let step = 1; step < items.length; step *= 2) { + for (let start = 0; start < items.length - 1; start += 2 * step) { + const sizeLeft = step; + const mid = start + sizeLeft; + const sizeRight = Math.min(step, items.length - mid); + if (sizeRight < 0) { + continue; + } + + // Merge. + for (let l = 0; l < sizeLeft; l += 1) { + lBuffer[l] = items[start + l]; + } + for (let r = 0; r < sizeRight; r += 1) { + rBuffer[r] = items[mid + r]; + } + + { + let l = 0; + let r = 0; + let o = start; + while (l < sizeLeft && r < sizeRight) { + const cmp = R(Q(yield* SortCompare(lBuffer[l], rBuffer[r]))); + if (cmp <= 0) { + items[o] = lBuffer[l]; + o += 1; + l += 1; + } else { + items[o] = rBuffer[r]; + o += 1; + r += 1; + } + } + while (l < sizeLeft) { + items[o] = lBuffer[l]; + o += 1; + l += 1; + } + while (r < sizeRight) { + items[o] = rBuffer[r]; + o += 1; + r += 1; + } + } + } + } + + let j = 0; + while (j < itemCount) { + Q(yield* Set(obj, X(ToString(F(j))), items[j], Value.true)); + j += 1; + } + while (j < len) { + Q(yield* DeletePropertyOrThrow(obj, X(ToString(F(j))))); + j += 1; + } + + return obj; +} + +/** https://tc39.es/ecma262/#sec-sortindexedproperties */ +export function* SortIndexedProperties(obj: ObjectValue, len: number, SortCompare: (x: Value, y: Value) => ValueEvaluator, holes: 'skip-holes' | 'read-through-holes'): PlainEvaluator { + const items = []; + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let kRead; + if (holes === 'skip-holes') { + kRead = Q(yield* HasProperty(obj, Pk)); + } else { + Assert(holes === 'read-through-holes'); + kRead = Value.true; + } + if (kRead === Value.true) { + const kValue = Q(yield* Get(obj, Pk)); + items.push(kValue); + } + k += 1; + } + let completion: ValueCompletion = NormalCompletion(Value(0)); + items.sort((a, b) => { + if (completion instanceof ThrowCompletion) { + return 0; + } + // TODO: remove skipDebugger + completion = skipDebugger(SortCompare(a, b)); + if (completion instanceof ThrowCompletion) { + return 0; + } + const cmp = R(X(completion)); + return cmp; + }); + if (completion instanceof ThrowCompletion) { + return completion; + } + return items; +} + +export function bootstrapArrayPrototypeShared(realmRec: Realm, proto: ObjectValue, kind: 'Array' | 'TypedArray') { + const Validate = kind === 'Array' ? undefined : (thisValue: Value) => ValidateTypedArray(thisValue, 'seq-cst'); + const ToLength: (O: ObjectValue) => PlainEvaluator = kind === 'Array' + ? function* ArrayToLength(O) { + return yield* LengthOfArrayLike(O); + } + : function* TypedArrayToLength(O): PlainEvaluator { + const rec = Q(ValidateTypedArray(O, 'seq-cst')); + return TypedArrayLength(rec); + }; + /** https://tc39.es/ecma262/#sec-array.prototype.every */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.every */ + function* ArrayProto_every([callbackFn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(callbackFn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackFn); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let kPresent; + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)); + } else { + kPresent = Value.true; + } + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + const testResult = ToBoolean(Q(yield* Call(callbackFn, thisArg, [kValue, F(k), O]))); + if (testResult === Value.false) { + return Value.false; + } + } + k += 1; + } + return Value.true; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.find */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.find */ + function* ArrayProto_find([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(predicate)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = Q(yield* Get(O, Pk)); + const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O]))); + if (testResult === Value.true) { + return kValue; + } + k += 1; + } + return Value.undefined; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.findindex */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex */ + function* ArrayProto_findIndex([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(predicate)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = Q(yield* Get(O, Pk)); + const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O]))); + if (testResult === Value.true) { + return F(k); + } + k += 1; + } + return F(-1); + } + + /** https://tc39.es/ecma262/#sec-array.prototype.findlast */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlast */ + function* ArrayProto_findLast([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + // Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 2. Let len be ? LengthOfArrayLike(O). + const len = Q(yield* ToLength(O)); + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!IsCallable(predicate)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + } + // 4. Let k be len - 1. + let k = len - 1; + // 5. Repeat, while k ≥ 0, + while (k >= 0) { + // a. Let Pk be ! ToString(𝔽(k)). + const Pk = X(ToString(F(k))); + // b. Let kValue be ? Get(O, Pk). + const kValue = Q(yield* Get(O, Pk)); + // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O]))); + // d. If testResult is true, return kValue. + if (testResult === Value.true) { + return kValue; + } + // e. Set k to k - 1. + k -= 1; + } + // 6. Return undefined. + return Value.undefined; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.findlastindex */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlastindex */ + function* ArrayProto_findLastIndex([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + // Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 2. Let len be ? LengthOfArrayLike(O). + const len = Q(yield* ToLength(O)); + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!IsCallable(predicate)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + } + // 4. Let k be len - 1. + let k = len - 1; + // 5. Repeat, while k ≥ 0, + while (k >= 0) { + // a. Let Pk be ! ToString(𝔽(k)). + const Pk = X(ToString(F(k))); + // b. Let kValue be ? Get(O, Pk). + const kValue = Q(yield* Get(O, Pk)); + // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O]))); + // d. If testResult is true, return 𝔽(k). + if (testResult === Value.true) { + return F(k); + } + // e. Set k to k - 1. + k -= 1; + } + // 6. Return Return -1𝔽. + return F(-1); + } + + /** https://tc39.es/ecma262/#sec-array.prototype.foreach */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach */ + function* ArrayProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let kPresent; + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)); + } else { + kPresent = Value.true; + } + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O])); + } + k += 1; + } + return Value.undefined; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.includes */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes */ + function* ArrayProto_includes([searchElement = Value.undefined, fromIndex = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (len === 0) { + return Value.false; + } + const n = Q(yield* ToIntegerOrInfinity(fromIndex)); + 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(F(k))); + const elementK = Q(yield* Get(O, kStr)); + if (SameValueZero(searchElement, elementK) === Value.true) { + return Value.true; + } + k += 1; + } + return Value.false; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.indexof */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof */ + function* ArrayProto_indexOf([searchElement = Value.undefined, fromIndex = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (len === 0) { + return F(-1); + } + const n = Q(yield* ToIntegerOrInfinity(fromIndex)); + if (fromIndex === Value.undefined) { + Assert(n === 0); + } + if (n >= len) { + return F(-1); + } + let k; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } + } + while (k < len) { + const kStr = X(ToString(F(k))); + const kPresent = Q(yield* HasProperty(O, kStr)); + if (kPresent === Value.true) { + const elementK = Q(yield* Get(O, kStr)); + const same = IsStrictlyEqual(searchElement, elementK); + if (same === Value.true) { + return F(k); + } + } + k += 1; + } + return F(-1); + } + + /** https://tc39.es/ecma262/#sec-array.prototype.join */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.join */ + function* ArrayProto_join([separator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + let sep; + if (separator instanceof UndefinedValue) { + sep = ','; + } else { + sep = Q(yield* ToString(separator)).stringValue(); + } + let R = ''; + let k = 0; + while (k < len) { + if (k > 0) { + R = `${R}${sep}`; + } + const kStr = X(ToString(F(k))); + const element = Q(yield* Get(O, kStr)); + let next; + if (element instanceof UndefinedValue || element instanceof NullValue) { + next = ''; + } else { + next = Q(yield* ToString(element)).stringValue(); + } + R = `${R}${next}`; + k += 1; + } + return Value(R); + } + + /** https://tc39.es/ecma262/#sec-array.prototype.lastindexof */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof */ + function* ArrayProto_lastIndexOf([searchElement = Value.undefined, fromIndex]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (len === 0) { + return F(-1); + } + let n; + if (fromIndex !== undefined) { + n = Q(yield* ToIntegerOrInfinity(fromIndex)); + } 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(F(k))); + const kPresent = Q(yield* HasProperty(O, kStr)); + if (kPresent === Value.true) { + const elementK = Q(yield* Get(O, kStr)); + const same = IsStrictlyEqual(searchElement, elementK); + if (same === Value.true) { + return F(k); + } + } + k -= 1; + } + return F(-1); + } + + /** https://tc39.es/ecma262/#sec-array.prototype.reduce */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce */ + function* ArrayProto_reduce([callbackfn = Value.undefined, initialValue]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + if (len === 0 && initialValue === undefined) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + let k = 0; + let accumulator: Value = Value.undefined; + if (initialValue !== undefined) { + accumulator = initialValue; + } else { + let kPresent = false; + while (kPresent === false && k < len) { + const Pk = X(ToString(F(k))); + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)) === Value.true; + } else { + kPresent = true; + } + if (kPresent === true) { + accumulator = Q(yield* Get(O, Pk)); + } + k += 1; + } + if (kPresent === false) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + } + while (k < len) { + const Pk = X(ToString(F(k))); + let kPresent; + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)); + } else { + kPresent = Value.true; + } + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + accumulator = Q(yield* Call(callbackfn, Value.undefined, [accumulator, kValue, F(k), O])); + } + k += 1; + } + return accumulator; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.reduceright */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright */ + function* ArrayProto_reduceRight([callbackfn = Value.undefined, initialValue]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + if (len === 0 && initialValue === undefined) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + let k = len - 1; + let accumulator: Value = Value.undefined; + if (initialValue !== undefined) { + accumulator = initialValue; + } else { + let kPresent = false; + while (kPresent === false && k >= 0) { + const Pk = X(ToString(F(k))); + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)) === Value.true; + } else { + kPresent = true; + } + if (kPresent === true) { + accumulator = Q(yield* Get(O, Pk)); + } + k -= 1; + } + if (kPresent === false) { + return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce'); + } + } + while (k >= 0) { + const Pk = X(ToString(F(k))); + let kPresent; + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)); + } else { + kPresent = Value.true; + } + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + accumulator = Q(yield* Call(callbackfn, Value.undefined, [accumulator, kValue, F(k), O])); + } + k -= 1; + } + return accumulator; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.reverse */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse */ + function* ArrayProto_reverse(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + const middle = Math.floor(len / 2); + let lower = 0; + while (lower !== middle) { + const upper = len - lower - 1; + const upperP = X(ToString(F(upper))); + const lowerP = X(ToString(F(lower))); + const lowerExists = Q(yield* HasProperty(O, lowerP)); + let lowerValue; + let upperValue; + if (lowerExists === Value.true) { + lowerValue = Q(yield* Get(O, lowerP)); + } + const upperExists = Q(yield* HasProperty(O, upperP)); + if (upperExists === Value.true) { + upperValue = Q(yield* Get(O, upperP)); + } + if (lowerExists === Value.true && upperExists === Value.true) { + Q(yield* Set(O, lowerP, upperValue as Value, Value.true)); + Q(yield* Set(O, upperP, lowerValue as Value, Value.true)); + } else if (lowerExists === Value.false && upperExists === Value.true) { + Q(yield* Set(O, lowerP, upperValue as Value, Value.true)); + Q(yield* DeletePropertyOrThrow(O, upperP)); + } else if (lowerExists === Value.true && upperExists === Value.false) { + Q(yield* DeletePropertyOrThrow(O, lowerP)); + Q(yield* Set(O, upperP, lowerValue as Value, Value.true)); + } else { + // no further action is required + } + lower += 1; + } + return O; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.some */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.some */ + function* ArrayProto_some([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const O = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(O)); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let kPresent; + if (kind === 'Array') { + kPresent = Q(yield* HasProperty(O, Pk)); + } else { + kPresent = Value.true; + } + if (kPresent === Value.true) { + const kValue = Q(yield* Get(O, Pk)); + const testResult = ToBoolean(Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O]))); + if (testResult === Value.true) { + return Value.true; + } + } + k += 1; + } + return Value.false; + } + + /** https://tc39.es/ecma262/#sec-array.prototype.tolocalestring */ + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring */ + function* ArrayProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(Validate?.(thisValue)); + const array = Q(ToObject(thisValue)); + const len = Q(yield* ToLength(array)); + const separator = ', '; + let R = ''; + let k = 0; + while (k < len) { + if (k > 0) { + R = `${R}${separator}`; + } + const kStr = X(ToString(F(k))); + const nextElement = Q(yield* Get(array, kStr)); + if (nextElement !== Value.undefined && nextElement !== Value.null) { + const S = Q(yield* ToString(Q(yield* Invoke(nextElement, Value('toLocaleString'))))).stringValue(); + R = `${R}${S}`; + } + k += 1; + } + return Value(R); + } + + assignProps(realmRec, proto, [ + ['every', ArrayProto_every, 1], + ['find', ArrayProto_find, 1], + ['findIndex', ArrayProto_findIndex, 1], + ['findLast', ArrayProto_findLast, 1], + ['findLastIndex', ArrayProto_findLastIndex, 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/src/intrinsics/AsyncFromSyncIteratorPrototype.mts b/src/intrinsics/AsyncFromSyncIteratorPrototype.mts new file mode 100644 index 0000000..df21ba1 --- /dev/null +++ b/src/intrinsics/AsyncFromSyncIteratorPrototype.mts @@ -0,0 +1,164 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { + IfAbruptRejectPromise, NormalCompletion, X, +} from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + AsyncFromSyncIteratorContinuation, + Call, + CreateIteratorResultObject, + GetMethod, + IteratorNext, + NewPromiseCapability, + Assert, + type OrdinaryObject, + type IteratorRecord, + IteratorClose, + type FunctionObject, + Realm, +} from '#self'; + +export interface AsyncFromSyncIteratorObject extends OrdinaryObject { + readonly SyncIteratorRecord: IteratorRecord; +} +/** https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.next */ +function* AsyncFromSyncIteratorPrototype_next([value]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let O be the this value. + const O = thisValue as AsyncFromSyncIteratorObject; + // 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot. + Assert(O instanceof ObjectValue && '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 = yield* IteratorNext(syncIteratorRecord, value); + } else { // 6. Else, + // a. Let result be IteratorNext(syncIteratorRecord). + result = yield* IteratorNext(syncIteratorRecord); + } + // 7. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + __ts_cast__(result); + // 8. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, true). + return X(AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, Value.true)); +} + +/** https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.return */ +function* AsyncFromSyncIteratorPrototype_return([value]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let O be the this value. + const O = thisValue as AsyncFromSyncIteratorObject; + // 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot. + Assert(O instanceof ObjectValue && 'SyncIteratorRecord' in O); + // 3. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. + const syncIteratorRecord = O.SyncIteratorRecord; + const syncIterator = syncIteratorRecord.Iterator; + // 5. Let return be GetMethod(syncIterator, "return"). + const ret = yield* GetMethod(syncIterator, Value('return')); + // 6. IfAbruptRejectPromise(return, promiseCapability). + IfAbruptRejectPromise(ret, promiseCapability); + __ts_cast__(ret); + // 7. If return is undefined, then + if (ret === Value.undefined) { + // a. Let iteratorResult be CreateIteratorResultObject(value, true). + const iteratorResult = CreateIteratorResultObject(value || Value.undefined, Value.true); + // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). + X(Call(promiseCapability.Resolve, Value.undefined, [iteratorResult])); + // 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 = yield* Call(ret, syncIterator, [value]); + } else { // 9. Else, + // a. Let result be Call(return, syncIterator). + result = yield* Call(ret, syncIterator); + } + // 10. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + __ts_cast__(result); + // 11. If result is not an Object, then + if (!(result instanceof ObjectValue)) { + // 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, syncIteratorRecord, false). + return X(AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, Value.false)); +} + +/** https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.throw */ +function* AsyncFromSyncIteratorPrototype_throw([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let O be this value. + const O = thisValue as AsyncFromSyncIteratorObject; + // 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot. + Assert(O instanceof ObjectValue && 'SyncIteratorRecord' in O); + // 3. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. + const syncIteratorRecord = O.SyncIteratorRecord; + const syncIterator = syncIteratorRecord.Iterator; + // 5. Let throw be GetMethod(syncIterator, "throw"). + const thr = yield* GetMethod(syncIterator, Value('throw')); + // 6. IfAbruptRejectPromise(throw, promiseCapability). + IfAbruptRejectPromise(thr, promiseCapability); + __ts_cast__(thr); + // 7. If throw is undefined, then + if (thr === Value.undefined) { + const closeCompletion = NormalCompletion(undefined); + const result = yield* IteratorClose(syncIteratorRecord, closeCompletion); + IfAbruptRejectPromise(result, promiseCapability); + X(Call(promiseCapability.Reject, Value.undefined, [ + // TODO: error message should be no throw method + surroundingAgent.Throw('TypeError', 'NotAnObject', value).Value, + ])); + return promiseCapability.Promise; + } + // 8. If value is present, then + let result; + if (value !== undefined) { + // a. Let result be Call(throw, syncIterator, « value »). + result = yield* Call(thr, syncIterator, [value]); + } else { // 9. Else, + // a. Let result be Call(throw, syncIterator). + result = yield* Call(thr, syncIterator); + } + // 10. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + __ts_cast__(result); + // 11. If Type(result) is not Object, then + if (!(result instanceof ObjectValue)) { + // 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, syncIteratorRecord, true). + return X(AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, Value.true)); +} + +export function bootstrapAsyncFromSyncIteratorPrototype(realmRec: Realm) { + 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/src/intrinsics/AsyncFunction.mts b/src/intrinsics/AsyncFunction.mts new file mode 100644 index 0000000..3063f59 --- /dev/null +++ b/src/intrinsics/AsyncFunction.mts @@ -0,0 +1,33 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, X } from '../completion.mts'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mts'; +import { Descriptor, Value } from '../value.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import type { + Arguments, ValueEvaluator, FunctionCallContext, FunctionObject, Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-async-function-constructor-arguments */ +function* AsyncFunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + const bodyArg = args[args.length - 1] || Value(''); + args = args.slice(0, -1) as Arguments; + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject as FunctionObject; + // 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(yield* CreateDynamicFunction(C, NewTarget, 'async', args, bodyArg)); +} + +export function bootstrapAsyncFunction(realmRec: Realm) { + const cons = bootstrapConstructor(realmRec, AsyncFunctionConstructor, 'AsyncFunction', 1, realmRec.Intrinsics['%AsyncFunction.prototype%'], []); + + X(cons.DefineOwnProperty(Value('prototype'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + cons.Prototype = realmRec.Intrinsics['%Function%']; + + realmRec.Intrinsics['%AsyncFunction%'] = cons; +} diff --git a/src/intrinsics/AsyncFunctionPrototype.mts b/src/intrinsics/AsyncFunctionPrototype.mts new file mode 100644 index 0000000..b7ab9f6 --- /dev/null +++ b/src/intrinsics/AsyncFunctionPrototype.mts @@ -0,0 +1,8 @@ +import { bootstrapPrototype } from './bootstrap.mts'; +import type { Realm } from '#self'; + +export function bootstrapAsyncFunctionPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [], realmRec.Intrinsics['%Function.prototype%'], 'AsyncFunction'); + + realmRec.Intrinsics['%AsyncFunction.prototype%'] = proto; +} diff --git a/src/intrinsics/AsyncGeneratorFunction.mts b/src/intrinsics/AsyncGeneratorFunction.mts new file mode 100644 index 0000000..10639d9 --- /dev/null +++ b/src/intrinsics/AsyncGeneratorFunction.mts @@ -0,0 +1,39 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, X } from '../completion.mts'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mts'; +import { Descriptor, Value } from '../value.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import type { + Arguments, ValueEvaluator, FunctionCallContext, FunctionObject, Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-asyncgeneratorfunction */ +function* AsyncGeneratorFunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + const bodyArg = args[args.length - 1] || Value(''); + args = args.slice(0, -1) as Arguments; + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject as FunctionObject; + // 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(yield* CreateDynamicFunction(C, NewTarget, 'asyncGenerator', args, bodyArg)); +} + +export function bootstrapAsyncGeneratorFunction(realmRec: Realm) { + const cons = bootstrapConstructor(realmRec, AsyncGeneratorFunctionConstructor, 'AsyncGeneratorFunction', 1, realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'], []); + + X(cons.DefineOwnProperty(Value('prototype'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + X((realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%']).DefineOwnProperty(Value('constructor'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + cons.Prototype = realmRec.Intrinsics['%Function%']; + + realmRec.Intrinsics['%AsyncGeneratorFunction%'] = cons; +} diff --git a/src/intrinsics/AsyncGeneratorFunctionPrototype.mts b/src/intrinsics/AsyncGeneratorFunctionPrototype.mts new file mode 100644 index 0000000..18e3ba1 --- /dev/null +++ b/src/intrinsics/AsyncGeneratorFunctionPrototype.mts @@ -0,0 +1,19 @@ +import { X } from '../completion.mts'; +import { Descriptor, Value } from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { Realm } from '#self'; + +export function bootstrapAsyncGeneratorFunctionPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['prototype', realmRec.Intrinsics['%AsyncGeneratorFunction.prototype.prototype%'], undefined, { Writable: Value.false }], + ], realmRec.Intrinsics['%Function.prototype%'], 'AsyncGeneratorFunction'); + + X((realmRec.Intrinsics['%AsyncGeneratorFunction.prototype.prototype%']).DefineOwnProperty(Value('constructor'), Descriptor({ + Value: proto, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'] = proto; +} diff --git a/src/intrinsics/AsyncGeneratorFunctionPrototypePrototype.mts b/src/intrinsics/AsyncGeneratorFunctionPrototypePrototype.mts new file mode 100644 index 0000000..7c4423c --- /dev/null +++ b/src/intrinsics/AsyncGeneratorFunctionPrototypePrototype.mts @@ -0,0 +1,148 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + X, + NormalCompletion, + ThrowCompletion, + IfAbruptRejectPromise, + ReturnCompletion, +} from '../completion.mts'; +import { Value, type Arguments, type FunctionCallContext } from '../value.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Assert, + Call, + NewPromiseCapability, + AsyncGeneratorValidate, + AsyncGeneratorEnqueue, + AsyncGeneratorResume, + AsyncGeneratorAwaitReturn, + CreateIteratorResultObject, + type AsyncGeneratorObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next */ +function* AsyncGeneratorPrototype_next([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let generator be the this value. + const generator = thisValue; + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 3. Let result be AsyncGeneratorValidate(generator, empty). + const result = AsyncGeneratorValidate(generator, undefined); + // 4. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + __ts_cast__(generator); + // 5. Let state be generator.[[AsyncGeneratorState]]. + const state = generator.AsyncGeneratorState; + // 6. If state is completed, then + if (state === 'completed') { + // a. Let iteratorResult be CreateIteratorResultObject(undefined, true). + const iteratorResult = CreateIteratorResultObject(Value.undefined, Value.true); + // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). + X(Call(promiseCapability.Resolve, Value.undefined, [iteratorResult])); + // c. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // 7. Let completion be NormalCompletion(value). + const completion = NormalCompletion(value); + // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). + AsyncGeneratorEnqueue(generator, completion, promiseCapability); + // 9. If state is either suspendedStart or suspendedYield, then + if (state === 'suspendedStart' || state === 'suspendedYield') { + // a. Perform AsyncGeneratorResume(generator, completion). + yield* AsyncGeneratorResume(generator, completion); + } else { // 10. Else, + // a. Assert: state is either executing or draining-queue. + Assert(state === 'executing' || state === 'draining-queue'); + } + // 11. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} + +/** https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return */ +function* AsyncGeneratorPrototype_return([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let generator be the this value. + const generator = thisValue; + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 3. Let result be AsyncGeneratorValidate(generator, empty). + const result = AsyncGeneratorValidate(generator, undefined); + // 4. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + __ts_cast__(generator); + // 5. Let completion be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + const completion = ReturnCompletion(value); + // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). + AsyncGeneratorEnqueue(generator, completion, promiseCapability); + // 7. Let state be generator.[[AsyncGeneratorState]]. + const state = generator.AsyncGeneratorState; + // 8. If state is either suspendedStart or completed, then + if (state === 'suspendedStart' || state === 'completed') { + // a. Set generator.[[AsyncGeneratorState]] to draining-queue. + generator.AsyncGeneratorState = 'draining-queue'; + // b. Perform AsyncGeneratorAwaitReturn(generator). + yield* AsyncGeneratorAwaitReturn(generator); + } else if (state === 'suspendedYield') { // 9. Else if state is suspendedYield, then + // a. Perform AsyncGeneratorResume(generator, completion). + yield* AsyncGeneratorResume(generator, completion); + } else { // 10. Else, + // a. Assert: state is either executing or draining-queue. + Assert(state === 'executing' || state === 'draining-queue'); + } + // 11. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} + +/** https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw */ +function* AsyncGeneratorPrototype_throw([exception = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let generator be the this value. + const generator = thisValue; + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 3. Let result be AsyncGeneratorValidate(generator, empty). + const result = AsyncGeneratorValidate(generator, undefined); + // 4. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + __ts_cast__(generator); + // 5. Let state be generator.[[AsyncGeneratorState]]. + let state = generator.AsyncGeneratorState; + // 6. If state is suspendedStart, then + if (state === 'suspendedStart') { + // a. Set generator.[[AsyncGeneratorState]] to completed. + generator.AsyncGeneratorState = 'completed'; + // b. Set state to completed. + state = 'completed'; + } + // 7. If state is completed, then + if (state === 'completed') { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »). + X(Call(promiseCapability.Reject, Value.undefined, [exception])); + // b. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // 8. Let completion be ThrowCompletion(exception). + const completion = ThrowCompletion(exception); + // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). + AsyncGeneratorEnqueue(generator, completion, promiseCapability); + // 10. If state is suspendedYield, then + if (state === 'suspendedYield') { + // a. Perform AsyncGeneratorResume(generator, completion). + yield* AsyncGeneratorResume(generator, completion); + } else { // 11. Else, + // a. Assert: state is either executing or draining-queue. + Assert(state === 'executing' || state === 'draining-queue'); + } + // 12. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} + +export function bootstrapAsyncGeneratorFunctionPrototypePrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', AsyncGeneratorPrototype_next, 1], + ['return', AsyncGeneratorPrototype_return, 1], + ['throw', AsyncGeneratorPrototype_throw, 1], + ], realmRec.Intrinsics['%AsyncIteratorPrototype%'], 'AsyncGenerator'); + + realmRec.Intrinsics['%AsyncGeneratorFunction.prototype.prototype%'] = proto; +} diff --git a/src/intrinsics/AsyncIteratorPrototype.mts b/src/intrinsics/AsyncIteratorPrototype.mts new file mode 100644 index 0000000..820c70b --- /dev/null +++ b/src/intrinsics/AsyncIteratorPrototype.mts @@ -0,0 +1,17 @@ +import { wellKnownSymbols } from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { Arguments, FunctionCallContext, Realm } from '#self'; + +/** https://tc39.es/ecma262/#sec-asynciteratorprototype-asynciterator */ +function AsyncIteratorPrototype_asyncIterator(_args: Arguments, { thisValue }: FunctionCallContext) { + // 1. Return the this value. + return thisValue; +} + +export function bootstrapAsyncIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + [wellKnownSymbols.asyncIterator, AsyncIteratorPrototype_asyncIterator, 0], + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%AsyncIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/BigInt.mts b/src/intrinsics/BigInt.mts new file mode 100644 index 0000000..0ac3879 --- /dev/null +++ b/src/intrinsics/BigInt.mts @@ -0,0 +1,69 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BigIntValue, NumberValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { NumberToBigInt } from '../runtime-semantics/all.mts'; +import { Q, type ValueEvaluator } from '../completion.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + ToBigInt, + ToIndex, + ToPrimitive, + Z, R, + type OrdinaryObject, + Realm, +} from '#self'; + +export interface BigIntObject extends OrdinaryObject { + readonly BigIntData: BigIntValue; +} +export function isBigIntObject(o: Value): o is BigIntObject { + return 'BigIntData' in o; +} +/** https://tc39.es/ecma262/#sec-bigint-constructor */ +function* BigIntConstructor([value = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 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, number). + const prim = Q(yield* ToPrimitive(value, 'number')); + // 3. If Type(prim) is Number, return ? NumberToBigInt(prim). + // 4. Otherwise, return ? ToBigInt(prim). + if (prim instanceof NumberValue) { + return Q(NumberToBigInt(prim)); + } else { + return Q(yield* ToBigInt(prim)); + } +} + +/** https://tc39.es/ecma262/#sec-bigint.asintn */ +function* BigInt_asIntN([_bits = Value.undefined, _bigint = Value.undefined]: Arguments): ValueEvaluator { + // 1. Set bits to ? ToIndex(bits). + const bits = Q(yield* ToIndex(_bits)); + // 2. Set bigint to ? ToBigInt(bigint). + const bigint = Q(yield* 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 Z(BigInt.asIntN(bits, R(bigint))); +} + +/** https://tc39.es/ecma262/#sec-bigint.asuintn */ +function* BigInt_asUintN([_bits = Value.undefined, _bigint = Value.undefined]: Arguments): ValueEvaluator { + // 1. Set bits to ? ToIndex(bits). + const bits = Q(yield* ToIndex(_bits)); + // 2. Set bigint to ? ToBigInt(bigint). + const bigint = Q(yield* ToBigInt(_bigint)); + // 3. Let mod be ℝ(bigint) modulo 2 ** bits. + // 4. If mod ≥ 2 ** (bits - 1), return Z(mod - 2 ** bits); otherwise, return Z(mod). + return Z(BigInt.asUintN(bits, R(bigint))); +} + +export function bootstrapBigInt(realmRec: Realm) { + 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/src/intrinsics/BigIntPrototype.mts b/src/intrinsics/BigIntPrototype.mts new file mode 100644 index 0000000..382c0b1 --- /dev/null +++ b/src/intrinsics/BigIntPrototype.mts @@ -0,0 +1,83 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, BigIntValue, Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Assert, ToIntegerOrInfinity, ToString, R, +} from '#self'; +import type { Realm } from '#self'; + +/** https://tc39.es/ecma262/#sec-thisbigintvalue */ +function thisBigIntValue(value: Value) { + // 1. If Type(value) is BigInt, return value. + if (value instanceof BigIntValue) { + return value; + } + // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then + if (value instanceof ObjectValue && 'BigIntData' in value) { + // a. Assert: Type(value.[[BigIntData]]) is BigInt. + Assert(value.BigIntData instanceof BigIntValue); + // b. Return value.[[BigIntData]]. + return value.BigIntData; + } + // 3. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'BigInt', value); +} + +/** https://tc39.es/ecma262/#sec-bigint.prototype.tolocalestring */ +function BigIntProto_toLocaleString(args: Arguments, context: FunctionCallContext): ValueEvaluator { + return BigIntProto_toString(args, context); +} + +/** https://tc39.es/ecma262/#sec-bigint.prototype.tostring */ +function* BigIntProto_toString([radix]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 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 ? ToIntegerOrInfinity(radix). + radixNumber = Q(yield* ToIntegerOrInfinity(radix)); + } + // 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 Value(R(x).toString(radixNumber)); +} + +/** https://tc39.es/ecma262/#sec-bigint.prototype.tostring */ +function BigIntProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // Return ? thisBigIntValue(this value). + return Q(thisBigIntValue(thisValue)); +} + +export function bootstrapBigIntPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['toLocaleString', BigIntProto_toLocaleString, 0], + ['toString', BigIntProto_toString, 0], + ['valueOf', BigIntProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'BigInt'); + + realmRec.Intrinsics['%BigInt.prototype%'] = proto; +} diff --git a/src/intrinsics/Boolean.mts b/src/intrinsics/Boolean.mts new file mode 100644 index 0000000..cedaa29 --- /dev/null +++ b/src/intrinsics/Boolean.mts @@ -0,0 +1,47 @@ +import { + BooleanValue, UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + OrdinaryCreateFromConstructor, + ToBoolean, + type OrdinaryObject, + Realm, +} from '#self'; + +export interface BooleanObject extends OrdinaryObject { + readonly BooleanData: BooleanValue; +} +export function isBooleanObject(o: Value): o is BooleanObject { + return 'BooleanData' in o; +} +/** https://tc39.es/ecma262/#sec-boolean-constructor-boolean-value */ +function* BooleanConstructor([value = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 1. Let b be ! ToBoolean(value). + const b = X(ToBoolean(value)); + // 2. If NewTarget is undefined, return b. + if (NewTarget instanceof UndefinedValue) { + return b; + } + // 3. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Boolean.prototype%", « [[BooleanData]] »). + const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Boolean.prototype%', ['BooleanData'])) as Mutable; + // 4. Set O.[[BooleanData]] to b. + O.BooleanData = b; + // 5. Return O. + return O; +} + +export function bootstrapBoolean(realmRec: Realm) { + const cons = bootstrapConstructor( + realmRec, + BooleanConstructor, + 'Boolean', + 1, + realmRec.Intrinsics['%Boolean.prototype%'], + [], + ); + + realmRec.Intrinsics['%Boolean%'] = cons; +} diff --git a/src/intrinsics/BooleanPrototype.mts b/src/intrinsics/BooleanPrototype.mts new file mode 100644 index 0000000..965923c --- /dev/null +++ b/src/intrinsics/BooleanPrototype.mts @@ -0,0 +1,59 @@ +import { + ObjectValue, + BooleanValue, + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { Q, type ValueCompletion } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { BooleanObject } from './Boolean.mts'; +import { Assert } from '#self'; +import type { Realm } from '#self'; + + +function thisBooleanValue(value: Value) { + if (value instanceof BooleanValue) { + return value; + } + + if (value instanceof ObjectValue && 'BooleanData' in value) { + const b = value.BooleanData; + Assert(b instanceof BooleanValue); + return b; + } + + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Boolean', value); +} + +/** https://tc39.es/ecma262/#sec-boolean.prototype.tostring */ +function BooleanProto_toString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 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 Value('true'); + } + return Value('false'); +} + +/** https://tc39.es/ecma262/#sec-boolean.prototype.valueof */ +function BooleanProto_valueOf(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Return ? thisBooleanValue(this value). + return Q(thisBooleanValue(thisValue)); +} + +export function bootstrapBooleanPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['toString', BooleanProto_toString, 0], + ['valueOf', BooleanProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%']); + + (proto as Mutable).BooleanData = Value.false; + + realmRec.Intrinsics['%Boolean.prototype%'] = proto; +} diff --git a/src/intrinsics/DataView.mts b/src/intrinsics/DataView.mts new file mode 100644 index 0000000..00bfa5f --- /dev/null +++ b/src/intrinsics/DataView.mts @@ -0,0 +1,82 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + IsDetachedBuffer, + OrdinaryCreateFromConstructor, + ToIndex, + RequireInternalSlot, + type OrdinaryObject, + type FunctionObject, + type ArrayBufferObject, + Realm, +} from '#self'; + +export interface DataViewObject extends OrdinaryObject { + readonly DataView: string; + readonly ViewedArrayBuffer: Value; + readonly ByteLength: number; + readonly ByteOffset: number; +} +export function isDataViewObject(V: Value): V is DataViewObject { + return 'DataView' in V; +} +/** https://tc39.es/ecma262/#sec-dataview-constructor */ +function* DataViewConstructor(this: FunctionObject, [buffer = Value.undefined, byteOffset = Value.undefined, byteLength = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). + Q(RequireInternalSlot(buffer, 'ArrayBufferData')); + // 3. Let offset be ? ToIndex(byteOffset). + const offset = Q(yield* ToIndex(byteOffset)); + // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + __ts_cast__(buffer); + if (IsDetachedBuffer(buffer)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. + const bufferByteLength = (buffer).ArrayBufferByteLength; + // 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(yield* ToIndex(byteLength)); + // 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(yield* OrdinaryCreateFromConstructor(NewTarget, '%DataView.prototype%', ['DataView', 'ViewedArrayBuffer', 'ByteLength', 'ByteOffset'])) as Mutable; + // 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 11. Set O.[[ViewedArrayBuffer]] to buffer. + O.ViewedArrayBuffer = buffer; + // 12. Set O.[[ByteLength]] to viewByteLength. + O.ByteLength = viewByteLength; + // 13. Set O.[[ByteOffset]] to offset. + O.ByteOffset = offset; + // 14. Return O. + return O; +} + +export function bootstrapDataView(realmRec: Realm) { + const dvConstructor = bootstrapConstructor(realmRec, DataViewConstructor, 'DataView', 1, realmRec.Intrinsics['%DataView.prototype%'], []); + + realmRec.Intrinsics['%DataView%'] = dvConstructor; +} diff --git a/src/intrinsics/DataViewPrototype.mts b/src/intrinsics/DataViewPrototype.mts new file mode 100644 index 0000000..4899aa9 --- /dev/null +++ b/src/intrinsics/DataViewPrototype.mts @@ -0,0 +1,271 @@ +import { Q, type ValueCompletion, type ValueEvaluator } from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value, type Arguments, type FunctionCallContext } from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { DataViewObject } from './DataView.mts'; +import { + Assert, + GetViewValue, + SetViewValue, + IsDetachedBuffer, + RequireInternalSlot, + F, + type ArrayBufferObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-get-dataview.prototype.buffer */ +function DataViewProto_buffer(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as DataViewObject; + // 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; +} + +/** https://tc39.es/ecma262/#sec-get-dataview.prototype.bytelength */ +function* DataViewProto_byteLength(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue as DataViewObject; + // 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 as ArrayBufferObject; + // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 6. Let size be O.[[ByteLength]]. + const size = O.ByteLength; + // 7. Return 𝔽(size). + return F(size); +} + +/** https://tc39.es/ecma262/#sec-get-dataview.prototype.byteoffset */ +function* DataViewProto_byteOffset(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue as DataViewObject; + // 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 as ArrayBufferObject; + // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if (IsDetachedBuffer(buffer)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + // 6. Let offset be O.[[ByteOffset]]. + const offset = O.ByteOffset; + // 7. Return 𝔽(offset). + return F(offset); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getbigint64 */ +function* DataViewProto_getBigInt64([byteOffset = Value.undefined, littleEndian = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let v be the this value. + const v = thisValue; + // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'BigInt64')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getbiguint64 */ +function* DataViewProto_getBigUint64([byteOffset = Value.undefined, littleEndian = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let v be the this value. + const v = thisValue; + // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigUint64). + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'BigUint64')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getfloat32 */ +function* DataViewProto_getFloat32([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Float32')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getfloat64 */ +function* DataViewProto_getFloat64([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Float64')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getint8 */ +function* DataViewProto_getInt8([byteOffset = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + return Q(yield* GetViewValue(v, byteOffset, Value.true, 'Int8')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getint16 */ +function* DataViewProto_getInt16([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Int16')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getint32 */ +function* DataViewProto_getInt32([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Int32')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getuint8 */ +function* DataViewProto_getUint8([byteOffset = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + return Q(yield* GetViewValue(v, byteOffset, Value.true, 'Uint8')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getuint16 */ +function* DataViewProto_getUint16([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Uint16')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.getuint32 */ +function* DataViewProto_getUint32([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Uint32')); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setbigint64 */ +function* DataViewProto_setBigInt64([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 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(yield* SetViewValue(v, byteOffset, littleEndian, 'BigInt64', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setbiguint64 */ +function* DataViewProto_setBigUint64([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 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(yield* SetViewValue(v, byteOffset, littleEndian, 'BigUint64', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setfloat32 */ +function* DataViewProto_setFloat32([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Float32', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setfloat64 */ +function* DataViewProto_setFloat64([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Float64', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setint8 */ +function* DataViewProto_setInt8([byteOffset = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + return Q(yield* SetViewValue(v, byteOffset, Value.true, 'Int8', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setint16 */ +function* DataViewProto_setInt16([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Int16', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setint32 */ +function* DataViewProto_setInt32([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Int32', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setuint8 */ +function* DataViewProto_setUint8([byteOffset = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + return Q(yield* SetViewValue(v, byteOffset, Value.true, 'Uint8', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setuint16 */ +function* DataViewProto_setUint16([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Uint16', value)); +} + +/** https://tc39.es/ecma262/#sec-dataview.prototype.setuint32 */ +function* DataViewProto_setUint32([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const v = thisValue; + if (littleEndian === undefined) { + littleEndian = Value.false; + } + return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Uint32', value)); +} + +export function bootstrapDataViewPrototype(realmRec: Realm) { + 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/src/intrinsics/Date.mts b/src/intrinsics/Date.mts new file mode 100644 index 0000000..3159f95 --- /dev/null +++ b/src/intrinsics/Date.mts @@ -0,0 +1,218 @@ +import { + Value, JSStringValue, ObjectValue, type Arguments, type FunctionCallContext, NumberValue, +} from '../value.mts'; +import { + AbruptCompletion, + Q, ValueOfNormalCompletion, X, + type ValueEvaluator, +} from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { ToDateString, thisTimeValue } from './DatePrototype.mts'; +import { + Assert, + OrdinaryCreateFromConstructor, + ToPrimitive, + ToNumber, + ToIntegerOrInfinity, + ToString, + MakeDate, + MakeDay, + MakeTime, + UTC, + TimeClip, + F, + type OrdinaryObject, + type FunctionObject, + Realm, +} from '#self'; + +export interface DateObject extends OrdinaryObject { + DateValue: NumberValue; +} +export function isDateObject(value: Value): value is DateObject { + return 'DateValue' in value; +} +/** https://tc39.es/ecma262/#sec-date-constructor */ +function* DateConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + const numberOfArgs = args.length; + if (numberOfArgs >= 2) { + /** https://tc39.es/ecma262/#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(F(now)); + } else { + const y = Q(yield* ToNumber(year!)); + const m = Q(yield* ToNumber(month!)); + let dt; + if (date !== undefined) { + dt = Q(yield* ToNumber(date)); + } else { + dt = F(1); + } + let h; + if (hours !== undefined) { + h = Q(yield* ToNumber(hours)); + } else { + h = F(+0); + } + let min; + if (minutes !== undefined) { + min = Q(yield* ToNumber(minutes)); + } else { + min = F(+0); + } + let s; + if (seconds !== undefined) { + s = Q(yield* ToNumber(seconds)); + } else { + s = F(+0); + } + let milli; + if (ms !== undefined) { + milli = Q(yield* ToNumber(ms)); + } else { + milli = F(+0); + } + let yr; + if (y.isNaN()) { + yr = F(NaN); + } else { + const yi = X(ToIntegerOrInfinity(y)); + if (yi >= 0 && yi <= 99) { + yr = F(1900 + yi); + } else { + yr = y; + } + } + const finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)); + const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Date.prototype%', ['DateValue'])) as Mutable; + O.DateValue = TimeClip(UTC(finalDate)); + return O; + } + } else if (numberOfArgs === 1) { + const [value] = args; + /** https://tc39.es/ecma262/#sec-date-value */ + Assert(numberOfArgs === 1); + if (NewTarget === Value.undefined) { + const now = Date.now(); + return ToDateString(F(now)); + } else { + let tv; + if (value instanceof ObjectValue && 'DateValue' in value) { + tv = X(thisTimeValue(value)); + } else { + const v = Q(yield* ToPrimitive(value!)); + if (v instanceof JSStringValue) { + // Assert: The next step never returns an abrupt completion because Type(v) is String. + tv = parseDate(v); + } else { + tv = Q(yield* ToNumber(v)); + } + } + const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Date.prototype%', ['DateValue'])) as Mutable; + O.DateValue = TimeClip(tv); + return O; + } + } else { + /** https://tc39.es/ecma262/#sec-date-constructor-date */ + Assert(numberOfArgs === 0); + if (NewTarget === Value.undefined) { + const now = Date.now(); + return ToDateString(F(now)); + } else { + const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Date.prototype%', ['DateValue'])) as Mutable; + O.DateValue = F(Date.now()); + return O; + } + } +} + +/** https://tc39.es/ecma262/#sec-date.now */ +function Date_now() { + const now = Date.now(); + return F(now); +} + +/** https://tc39.es/ecma262/#sec-date.parse */ +function* Date_parse([string = Value.undefined]: Arguments): ValueEvaluator { + const str = yield* ToString(string); + if (str instanceof AbruptCompletion) { + return str; + } + return parseDate(ValueOfNormalCompletion(str)); +} + +/** https://tc39.es/ecma262/#sec-date.utc */ +function* Date_UTC([year = Value.undefined, month, date, hours, minutes, seconds, ms]: Arguments): ValueEvaluator { + const y = Q(yield* ToNumber(year)); + let m; + if (month !== undefined) { + m = Q(yield* ToNumber(month)); + } else { + m = F(+0); + } + let dt; + if (date !== undefined) { + dt = Q(yield* ToNumber(date)); + } else { + dt = F(1); + } + let h; + if (hours !== undefined) { + h = Q(yield* ToNumber(hours)); + } else { + h = F(+0); + } + let min; + if (minutes !== undefined) { + min = Q(yield* ToNumber(minutes)); + } else { + min = F(+0); + } + let s; + if (seconds !== undefined) { + s = Q(yield* ToNumber(seconds)); + } else { + s = F(+0); + } + let milli; + if (ms !== undefined) { + milli = Q(yield* ToNumber(ms)); + } else { + milli = F(+0); + } + + let yr; + if (y.isNaN()) { + yr = F(NaN); + } else { + const yi = X(ToIntegerOrInfinity(y)); + if (yi >= 0 && yi <= 99) { + yr = F(1900 + yi); + } else { + yr = y; + } + } + + return TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli))); +} + +function parseDate(dateTimeString: JSStringValue) { + /** https://tc39.es/ecma262/#sec-date-time-string-format */ + // TODO: implement parsing without the host. + const parsed = Date.parse(dateTimeString.stringValue()); + return F(parsed); +} + +export function bootstrapDate(realmRec: Realm) { + 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/src/intrinsics/DatePrototype.mts b/src/intrinsics/DatePrototype.mts new file mode 100644 index 0000000..79d9aaf --- /dev/null +++ b/src/intrinsics/DatePrototype.mts @@ -0,0 +1,788 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + JSStringValue, + NumberValue, + ObjectValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { NumberToBigInt, StringPad } from '../runtime-semantics/all.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { DateObject } from './Date.mts'; +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, + F, R, + CreateTemporalInstant, +} from '#self'; +import type { Realm } from '#self'; + + +export function thisTimeValue(value: Value): ValueCompletion { + if (value instanceof ObjectValue && 'DateValue' in value) { + return (value as DateObject).DateValue; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', value); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getdate */ +function DateProto_getDate(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return DateFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getday */ +function DateProto_getDay(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return WeekDay(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getfullyear */ +function DateProto_getFullYear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return YearFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.gethours */ +function DateProto_getHours(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return HourFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getmilliseconds */ +function DateProto_getMilliseconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return msFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getminutes */ +function DateProto_getMinutes(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return MinFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getmonth */ +function DateProto_getMonth(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return MonthFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getseconds */ +function DateProto_getSeconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return SecFromTime(LocalTime(t)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.gettime */ +function DateProto_getTime(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + return Q(thisTimeValue(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.gettimezoneoffset */ +function DateProto_getTimezoneOffset(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return F((R(t) - R(LocalTime(t))) / msPerMinute); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcdate */ +function DateProto_getUTCDate(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return DateFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcday */ +function DateProto_getUTCDay(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return WeekDay(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcfullyear */ +function DateProto_getUTCFullYear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return YearFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutchours */ +function DateProto_getUTCHours(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return HourFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcmilliseconds */ +function DateProto_getUTCMilliseconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return msFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcminutes */ +function DateProto_getUTCMinutes(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return MinFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcmonth */ +function DateProto_getUTCMonth(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return MonthFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.getutcseconds */ +function DateProto_getUTCSeconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + return F(NaN); + } + return SecFromTime(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setdate */ +function* DateProto_setDate([date = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + const dt = Q(yield* ToNumber(date)); + if (t.isNaN()) { + return t; + } + t = LocalTime(t); + const newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)); + const u = TimeClip(UTC(newDate)); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setfullyear */ +function* DateProto_setFullYear([year = Value.undefined, month, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + const y = Q(yield* ToNumber(year)); + t = t.isNaN() ? F(+0) : LocalTime(t); + let m; + if (month !== undefined) { + m = Q(yield* ToNumber(month)); + } else { + m = MonthFromTime(t); + } + let dt; + if (date !== undefined) { + dt = Q(yield* ToNumber(date)); + } else { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)); + const u = TimeClip(UTC(newDate)); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.sethours */ +function* DateProto_setHours([hour = Value.undefined, min, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + const h = Q(yield* ToNumber(hour)); + let m; + if (min) { + m = Q(yield* ToNumber(min)); + } + let s; + if (sec) { + s = Q(yield* ToNumber(sec)); + } + let milli; + if (ms !== undefined) { + milli = Q(yield* ToNumber(ms)); + } + if (t.isNaN()) { + return t; + } + t = LocalTime(t); + if (!m) { + m = MinFromTime(t); + } + if (!s) { + s = SecFromTime(t); + } + if (!milli) { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(h, m, s, milli)); + const u = TimeClip(UTC(date)); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds */ +function* DateProto_setMilliseconds([ms = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + ms = Q(yield* ToNumber(ms)); + if (t.isNaN()) { + return t; + } + t = LocalTime(t); + const time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms); + const u = TimeClip(UTC(MakeDate(Day(t), time))); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setminutes */ +function* DateProto_setMinutes([min = Value.undefined, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let t be LocalTime(? thisTimeValue(this value)). + const t = Q(thisTimeValue(thisValue)); + // 2. Let m be ? ToNumber(min). + const m = Q(yield* ToNumber(min)); + let s; + if (sec) { + s = Q(yield* ToNumber(sec)); + } + let milli; + if (ms) { + milli = Q(yield* ToNumber(ms)); + } + if (t.isNaN()) { + return t; + } + if (!s) { + s = SecFromTime(t); + } + if (!milli) { + 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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + // 8. Return u. + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setmonth */ +function* DateProto_setMonth([month = Value.undefined, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + const m = Q(yield* ToNumber(month)); + let dt; + if (date) { + dt = Q(yield* ToNumber(date)); + } + if (t.isNaN()) { + return t; + } + t = LocalTime(t); + if (!dt) { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)); + const u = TimeClip(UTC(newDate)); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setseconds */ +function* DateProto_setSeconds([sec = Value.undefined, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + const s = Q(yield* ToNumber(sec)); + let milli; + if (ms) { + milli = Q(yield* ToNumber(ms)); + } + if (t.isNaN()) { + return t; + } + t = LocalTime(t); + if (!milli) { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + const u = TimeClip(UTC(date)); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = u; + return u; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.settime */ +function* DateProto_setTime([time = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(thisTimeValue(thisValue)); + const t = Q(yield* ToNumber(time)); + const v = TimeClip(t); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutcdate */ +function* DateProto_setUTCDate([date = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const t = Q(thisTimeValue(thisValue)); + const dt = Q(yield* ToNumber(date)); + if (t.isNaN()) { + return t; + } + const newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)); + const v = TimeClip(newDate); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutcfullyear */ +function* DateProto_setUTCFullYear([year = Value.undefined, month, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + let t = Q(thisTimeValue(thisValue)); + if (t.isNaN()) { + t = F(+0); + } + const y = Q(yield* ToNumber(year)); + let m; + if (month !== undefined) { + m = Q(yield* ToNumber(month)); + } else { + m = MonthFromTime(t); + } + let dt; + if (date !== undefined) { + dt = Q(yield* ToNumber(date)); + } else { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)); + const v = TimeClip(newDate); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutchours */ +function* DateProto_setUTCHours([hour = Value.undefined, min, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const t = Q(thisTimeValue(thisValue)); + const h = Q(yield* ToNumber(hour)); + let m; + if (min) { + m = Q(yield* ToNumber(min)); + } + let s; + if (sec) { + s = Q(yield* ToNumber(sec)); + } + let milli; + if (ms) { + milli = Q(yield* ToNumber(ms)); + } + if (t.isNaN()) { + return t; + } + if (!m) { + m = MinFromTime(t); + } + if (!s) { + s = SecFromTime(t); + } + if (!milli) { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(h, m, s, milli)); + const v = TimeClip(date); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutcmilliseconds */ +function* DateProto_setUTCMilliseconds([ms = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const t = Q(thisTimeValue(thisValue)); + ms = Q(yield* ToNumber(ms)); + if (t.isNaN()) { + return t; + } + const time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms); + const v = TimeClip(MakeDate(Day(t), time)); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutcminutes */ +function* DateProto_setUTCMinutes([min = Value.undefined, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const t = Q(thisTimeValue(thisValue)); + const m = Q(yield* ToNumber(min)); + let s; + if (sec) { + s = Q(yield* ToNumber(sec)); + } + let milli; + if (ms) { + milli = Q(yield* ToNumber(ms)); + } + if (t.isNaN()) { + return t; + } + if (!s) { + s = SecFromTime(t); + } + if (!milli) { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + const v = TimeClip(date); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutcmonth */ +function* DateProto_setUTCMonth([month = Value.undefined, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const t = Q(thisTimeValue(thisValue)); + const m = Q(yield* ToNumber(month)); + let dt; + if (date) { + dt = Q(yield* ToNumber(date)); + } + if (t.isNaN()) { + return t; + } + if (!dt) { + dt = DateFromTime(t); + } + const newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)); + const v = TimeClip(newDate); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.setutcseconds */ +function* DateProto_setUTCSeconds([sec = Value.undefined, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const t = Q(thisTimeValue(thisValue)); + const s = Q(yield* ToNumber(sec)); + let milli; + if (ms) { + milli = Q(yield* ToNumber(ms)); + } + if (t.isNaN()) { + return t; + } + if (!milli) { + milli = msFromTime(t); + } + const date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + const v = TimeClip(date); + Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject)); + (thisValue as DateObject).DateValue = v; + return v; +} + +/** https://tc39.es/ecma262/#sec-date.prototype.todatestring */ +function* DateProto_toDateString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + const tv = Q(thisTimeValue(O)); + if (tv.isNaN()) { + return Value('Invalid Date'); + } + const t = LocalTime(tv); + return DateString(t); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.toisostring */ +export function DateProto_toISOString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const t = Q(thisTimeValue(thisValue)); + if (!Number.isFinite(R(t))) { + return surroundingAgent.Throw('RangeError', 'DateInvalidTime'); + } + const year = R(YearFromTime(t)); + const month = R(MonthFromTime(t)) + 1; + const date = R(DateFromTime(t)); + const hour = R(HourFromTime(t)); + const min = R(MinFromTime(t)); + const sec = R(SecFromTime(t)); + const ms = R(msFromTime(t)); + + // 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 Value(format); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.tojson */ +function* DateProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = Q(ToObject(thisValue)); + const tv = Q(yield* ToPrimitive(O, 'number')); + if (tv instanceof NumberValue && !Number.isFinite(R(tv))) { + return Value.null; + } + return Q(yield* Invoke(O, Value('toISOString'))); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.tolocaledatestring */ +function DateProto_toLocaleDateString(_args: Arguments, context: FunctionCallContext) { + return DateProto_toString([], context); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.tolocalestring */ +function DateProto_toLocaleString(_args: Arguments, context: FunctionCallContext) { + return DateProto_toString([], context); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.tolocaletimestring */ +function DateProto_toLocaleTimeString(_args: Arguments, context: FunctionCallContext) { + return DateProto_toString([], context); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.tostring */ +function DateProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const tv = Q(thisTimeValue(thisValue)); + return ToDateString(tv); +} + +/** https://tc39.es/ecma262/#sec-timestring */ +function TimeString(tv: NumberValue) { + Assert(tv instanceof NumberValue); + Assert(!tv.isNaN()); + const hour = String(R(HourFromTime(tv))).padStart(2, '0'); + const minute = String(R(MinFromTime(tv))).padStart(2, '0'); + const second = String(R(SecFromTime(tv))).padStart(2, '0'); + return Value(`${hour}:${minute}:${second} GMT`); +} + +/** https://tc39.es/ecma262/#sec-todatestring-day-names */ +const daysOfTheWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; +/** https://tc39.es/ecma262/#sec-todatestring-month-names */ +const monthsOfTheYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +/** https://tc39.es/ecma262/#sec-datestring */ +function DateString(tv: NumberValue) { + Assert(tv instanceof NumberValue); + Assert(!tv.isNaN()); + const weekday = daysOfTheWeek[R(WeekDay(tv))]; + const month = monthsOfTheYear[R(MonthFromTime(tv))]; + const day = String(R(DateFromTime(tv))).padStart(2, '0'); + const yv = R(YearFromTime(tv)); + const yearSign = yv >= 0 ? '' : '-'; + const year = Value(String(Math.abs(yv))); + const paddedYear = X(StringPad(year, F(4), Value('0'), 'start')).stringValue(); + return Value(`${weekday} ${month} ${day} ${yearSign}${paddedYear}`); +} + +/** https://tc39.es/ecma262/#sec-timezoneestring */ +export function TimeZoneString(tv: NumberValue) { + Assert(tv instanceof NumberValue); + Assert(!tv.isNaN()); + const offset = LocalTZA(tv, true); + const offsetSign = offset >= 0 ? '+' : '-'; + const offsetMin = String(R(MinFromTime(F(Math.abs(offset))))).padStart(2, '0'); + const offsetHour = String(R(HourFromTime(F(Math.abs(offset))))).padStart(2, '0'); + const tzName = ''; + return Value(`${offsetSign}${offsetHour}${offsetMin}${tzName}`); +} + +/** https://tc39.es/ecma262/#sec-todatestring */ +export function ToDateString(tv: NumberValue) { + Assert(tv instanceof NumberValue); + if (tv.isNaN()) { + return Value('Invalid Date'); + } + const t = LocalTime(tv); + return Value(`${DateString(t).stringValue()} ${TimeString(t).stringValue()}${TimeZoneString(t).stringValue()}`); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.totimestring */ +function DateProto_toTimeString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = thisValue; + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + const tv = Q(thisTimeValue(O)); + if (tv.isNaN()) { + return Value('Invalid Date'); + } + const t = LocalTime(tv); + return Value(`${TimeString(t).stringValue()}${TimeZoneString(tv).stringValue()}`); +} + +function DateProto_toTemporalInstant(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const dateObject = thisValue; + const t = Q(thisTimeValue(dateObject)); + const ns = R(Q(NumberToBigInt(t))) * BigInt(1e6); + return CreateTemporalInstant(ns); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.toutcstring */ +function DateProto_toUTCString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = thisValue; + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + const tv = Q(thisTimeValue(O)); + if (tv.isNaN()) { + return Value('Invalid Date'); + } + const weekday = daysOfTheWeek[R(WeekDay(tv))]; + const month = monthsOfTheYear[R(MonthFromTime(tv))]; + const day = String(R(DateFromTime(tv))).padStart(2, '0'); + const yv = R(YearFromTime(tv)); + const yearSign = yv >= 0 ? '' : '-'; + const year = Value(String(Math.abs(yv))); + const paddedYear = X(StringPad(year, F(4), Value('0'), 'start')).stringValue(); + return Value(`${weekday}, ${day} ${month} ${yearSign}${paddedYear} ${TimeString(tv).stringValue()}`); +} + +/** https://tc39.es/ecma262/#sec-date.prototype.valueof */ +function DateProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + return Q(thisTimeValue(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-date.prototype-@@toprimitive */ +function* DateProto_toPrimitive([hint = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O); + } + let tryFirst: 'string' | 'number'; + if (hint instanceof JSStringValue && (hint.stringValue() === 'string' || hint.stringValue() === 'default')) { + tryFirst = 'string'; + } else if (hint instanceof JSStringValue && hint.stringValue() === 'number') { + tryFirst = 'number'; + } else { + return surroundingAgent.Throw('TypeError', 'InvalidHint', hint); + } + return Q(yield* OrdinaryToPrimitive(O, tryFirst)); +} + +export function bootstrapDatePrototype(realmRec: Realm) { + 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], + surroundingAgent.feature('temporal') ? ['toTemporalInstant', DateProto_toTemporalInstant, 0] : undefined, + ['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/src/intrinsics/Error.mts b/src/intrinsics/Error.mts new file mode 100644 index 0000000..b836726 --- /dev/null +++ b/src/intrinsics/Error.mts @@ -0,0 +1,85 @@ +import { + Descriptor, + Value, + type Arguments, + type FunctionCallContext, + type JSStringValue, + type ObjectValue, + type UndefinedValue, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + captureStack, callSiteToErrorString, type CallSite, CallFrame, +} from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + DefinePropertyOrThrow, + OrdinaryCreateFromConstructor, + InstallErrorCause, + ToString, + type FunctionObject, + IsError, + Realm, +} from '#self'; + +export interface ErrorObject extends ObjectValue { + ErrorData: JSStringValue; + HostDefinedErrorStack?: (CallSite | CallFrame)[] | UndefinedValue; +} + +export { IsError as isErrorObject } from '../abstract-ops/error-objects.mts'; + +/** https://tc39.es/ecma262/#sec-error-constructor */ +function* ErrorConstructor([message = Value.undefined, options = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 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(yield* OrdinaryCreateFromConstructor(newTarget as FunctionObject, '%Error.prototype%', [ + 'ErrorData', + 'HostDefinedErrorStack', + ])) as ErrorObject; + // 3. If message is not undefined, then + if (message !== Value.undefined) { + // a. Let msg be ? ToString(message). + const msg = Q(yield* 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, Value('message'), msgDesc)); + } + + // 4. Perform ? InstallErrorCause(O, options). + Q(yield* InstallErrorCause(O, options)); + + // NON-SPEC + const S = captureStack(); + O.HostDefinedErrorStack = S.stack; + O.ErrorData = X(callSiteToErrorString(O, S.stack, S.nativeStack)); + + // 5. Return O. + return O; +} + +/** https://tc39.es/proposal-is-error/#sec-error.iserror */ +function Error_isError([value = Value.undefined]: Arguments) { + return Value(IsError(value)); +} + +export function bootstrapError(realmRec: Realm) { + const error = bootstrapConstructor(realmRec, ErrorConstructor, 'Error', 1, realmRec.Intrinsics['%Error.prototype%'], [ + ['isError', Error_isError, 1], + ]); + + realmRec.Intrinsics['%Error%'] = error; +} diff --git a/src/intrinsics/ErrorPrototype.mts b/src/intrinsics/ErrorPrototype.mts new file mode 100644 index 0000000..e2b15bf --- /dev/null +++ b/src/intrinsics/ErrorPrototype.mts @@ -0,0 +1,113 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + JSStringValue, + ObjectValue, + Value, + type Arguments, + type FunctionCallContext, + type UndefinedValue, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { isErrorObject } from './Error.mts'; +import { + Assert, + Get, + SetterThatIgnoresPrototypeProperties, + ToString, + type BuiltinFunctionObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-error.prototype.tostring */ +function* ErrorProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be this value. + const O = thisValue; + // 2. If Type(O) is not Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let name be ? Get(O, "name"). + let name = Q(yield* Get(O, Value('name'))); + // 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name). + if (name === Value.undefined) { + name = Value('Error'); + } else { + name = Q(yield* ToString(name)); + } + // 5. Let msg be ? Get(O, "message"). + let msg = Q(yield* Get(O, Value('message'))); + // 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg). + if (msg === Value.undefined) { + msg = Value(''); + } else { + msg = Q(yield* 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 Value(`${name.stringValue()}: ${msg.stringValue()}`); +} + +/** https://tc39.es/proposal-error-stack-accessor/#sec-get-error.prototype.stack */ +function* ErrorProto_getStack(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let E be the this value. + const E = thisValue; + // 2. If E is not an Object, throw a TypeError exception. + if (!(E instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', E); + } + // 3. If E does not have an [[ErrorData]] internal slot, return undefined. + if (!isErrorObject(E)) { + return Value.undefined; + } + // 4. Return an implementation-defined string that represents the stack trace of E. + Assert(E.ErrorData instanceof JSStringValue); + return E.ErrorData; +} + +/** https://tc39.es/proposal-error-stack-accessor/#sec-set-error.prototype.stack */ +function* ErrorProto_setStack(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const [v = Value.undefined] = args; + + // 1. Let E be the this value. + const E = thisValue; + // 2. If E is not an Object, throw a TypeError exception. + if (!(E instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', E); + } + // 3. Let numberOfArgs be the number of arguments passed to this function call. + const numberOfArgs = args.length; + // 4. If numberOfArgs is 0, throw a TypeError exception. + if (numberOfArgs === 0) { + return surroundingAgent.Throw('TypeError', 'NotEnoughArguments', numberOfArgs, 1); + } + // 5. If E does not have an [[ErrorData]] internal slot, return undefined. + if (!isErrorObject(E)) { + return Value.undefined; + } + // 6. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Error.prototype%, "stack", v). + Q(yield* SetterThatIgnoresPrototypeProperties(thisValue, surroundingAgent.intrinsic('%Error.prototype%'), Value('stack'), v)); + // 7. Return undefined. + return Value.undefined; +} + +export function bootstrapErrorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['toString', ErrorProto_toString, 0], + ['message', Value('')], + ['name', Value('Error')], + ['stack', [ErrorProto_getStack, ErrorProto_setStack]], + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%Error.prototype%'] = proto; + realmRec.Intrinsics['%Error.prototype.toString%'] = X(Get(proto, Value('toString'))) as BuiltinFunctionObject; +} diff --git a/src/intrinsics/FinalizationRegistry.mts b/src/intrinsics/FinalizationRegistry.mts new file mode 100644 index 0000000..13b8534 --- /dev/null +++ b/src/intrinsics/FinalizationRegistry.mts @@ -0,0 +1,68 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { HostMakeJobCallback } from '../execution-context/Job.mts'; +import { type JobCallbackRecord } from '../execution-context/Job.mts'; +import { + UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + IsCallable, OrdinaryCreateFromConstructor, + type BuiltinFunctionObject, type FunctionObject, type OrdinaryObject, + Realm, +} from '#self'; + +export interface FinalizationRegistryCell { + WeakRefTarget: Value | undefined; + readonly HeldValue: Value; + readonly UnregisterToken: Value | undefined; +} +export interface FinalizationRegistryObject extends OrdinaryObject { + readonly Realm: Realm; + readonly CleanupCallback: JobCallbackRecord; + Cells: FinalizationRegistryCell[]; +} +export function isFinalizationRegistryObject(object: object): object is FinalizationRegistryObject { + return 'Cells' in object; +} +/** https://tc39.es/ecma262/#sec-finalization-registry-cleanup-callback */ +function* FinalizationRegistryConstructor(this: BuiltinFunctionObject, [cleanupCallback = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception. + if (!IsCallable(cleanupCallback)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', cleanupCallback); + } + // 3. Let finalizationGroup be ? OrdinaryCreateFromConstructor(NewTarget, "%FinalizationRegistryPrototype%", « [[Realm]], [[CleanupCallback]], [[Cells]] »). + const finalizationGroup = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%FinalizationRegistry.prototype%', [ + 'Realm', + 'CleanupCallback', + 'Cells', + ])) as Mutable; + // 4. Let fn be the active function object. + const fn = surroundingAgent.activeFunctionObject; + // 5. Set finalizationGroup.[[Realm]] to fn.[[Realm]]. + finalizationGroup.Realm = (fn as FunctionObject).Realm; + // 6. Set finalizationGroup.[[CleanupCallback]] to HostMakeJobCallback(cleanupCallback). + finalizationGroup.CleanupCallback = HostMakeJobCallback(cleanupCallback); + // 7. Set finalizationGroup.[[Cells]] to be an empty List. + finalizationGroup.Cells = []; + // 8. Return finalizationGroup. + return finalizationGroup as FinalizationRegistryObject; +} + +export function bootstrapFinalizationRegistry(realmRec: Realm) { + const cons = bootstrapConstructor( + realmRec, + FinalizationRegistryConstructor, + 'FinalizationRegistry', + 1, + realmRec.Intrinsics['%FinalizationRegistry.prototype%'], + [], + ); + + realmRec.Intrinsics['%FinalizationRegistry%'] = cons; +} diff --git a/src/intrinsics/FinalizationRegistryPrototype.mts b/src/intrinsics/FinalizationRegistryPrototype.mts new file mode 100644 index 0000000..c89dc2c --- /dev/null +++ b/src/intrinsics/FinalizationRegistryPrototype.mts @@ -0,0 +1,109 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, type Arguments, type FunctionCallContext, BooleanValue, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { FinalizationRegistryCell, FinalizationRegistryObject } from './FinalizationRegistry.mts'; +import { + CanBeHeldWeakly, + CleanupFinalizationRegistry, + IsCallable, + RequireInternalSlot, + SameValue, + type FunctionObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-finalization-registry.prototype.cleanupSome */ +function* FinalizationRegistryProto_cleanupSome([callback = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 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)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callback); + } + // 4. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback). + Q(yield* CleanupFinalizationRegistry(finalizationRegistry as FinalizationRegistryObject, { Callback: callback as FunctionObject, HostDefined: undefined })); + // 5. Return *undefined*. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-finalization-registry.prototype.register */ +function FinalizationRegistryProto_register([target = Value.undefined, heldValue = Value.undefined, unregisterToken = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let finalizationRegistry be the this value. + const finalizationRegistry = thisValue as FinalizationRegistryObject; + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + Q(RequireInternalSlot(finalizationRegistry, 'Cells')); + // 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(target)) { + return surroundingAgent.Throw('TypeError', 'NotAWeakKey', target); + } + // 4. If SameValue(target, heldValue), throw a TypeError exception. + if (SameValue(target, heldValue) === Value.true) { + return surroundingAgent.Throw('TypeError', 'TargetMatchesHeldValue', heldValue); + } + // 5. If CanBeHeldWeakly(unregisterToken) is false, then + if (!CanBeHeldWeakly(unregisterToken)) { + // a. If unregisterToken is not undefined, throw a TypeError exception. + if (unregisterToken !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'NotAWeakKey', unregisterToken); + } + // b. Set unregisterToken to empty. + unregisterToken = undefined!; + } + // 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }. + const cell: FinalizationRegistryCell = { + WeakRefTarget: target, + HeldValue: heldValue, + UnregisterToken: unregisterToken, + }; + // 7. Append cell to finalizationRegistry.[[Cells]]. + Q(surroundingAgent.debugger_tryTouchDuringPreview(finalizationRegistry)); + finalizationRegistry.Cells.push(cell); + // 8. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister */ +function FinalizationRegistryProto_unregister([unregisterToken = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let finalizationRegistry be the this value. + const finalizationRegistry = thisValue as FinalizationRegistryObject; + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + Q(RequireInternalSlot(finalizationRegistry, 'Cells')); + // 3. If CanBeHeldWeakly(unregisterToken) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(unregisterToken)) { + return surroundingAgent.Throw('TypeError', 'NotAWeakKey', unregisterToken); + } + // 4. Let removed be false. + let removed: BooleanValue = Value.false; + Q(surroundingAgent.debugger_tryTouchDuringPreview(finalizationRegistry)); + // 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: Realm) { + 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/src/intrinsics/ForInIteratorPrototype.mts b/src/intrinsics/ForInIteratorPrototype.mts new file mode 100644 index 0000000..20f6fa8 --- /dev/null +++ b/src/intrinsics/ForInIteratorPrototype.mts @@ -0,0 +1,120 @@ +import { + Value, JSStringValue, ObjectValue, type Arguments, + type FunctionCallContext, + UndefinedValue, + NullValue, +} from '../value.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, type ValueEvaluator } from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Assert, + SameValue, + OrdinaryObjectCreate, + CreateIteratorResultObject, + type OrdinaryObject, + Realm, +} from '#self'; + +export interface ForInIteratorInstance extends OrdinaryObject { + Object: ObjectValue | NullValue; + ObjectWasVisited: Value; + readonly VisitedKeys: JSStringValue[]; + readonly RemainingKeys: JSStringValue[]; +} +/** https://tc39.es/ecma262/#sec-createforiniterator */ +export function CreateForInIterator(object: ObjectValue) { + // 1. Assert: Type(object) is Object. + Assert(object instanceof ObjectValue); + // 2. Let iterator be ObjectCreate(%ForInIteratorPrototype%, « [[Object]], [[ObjectWasVisited]], [[VisitedKeys]], [[RemainingKeys]] »). + const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%ForInIteratorPrototype%'), [ + 'Object', + 'ObjectWasVisited', + 'VisitedKeys', + 'RemainingKeys', + ]) as Mutable; + // 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; +} + +/** https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next */ +function* ForInIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be this value. + const O = thisValue; + // 2. Assert: Type(O) is Object. + Assert(O instanceof ObjectValue); + // 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); + __ts_cast__(O); + // 4. Let object be O.[[Object]]. + let object: ObjectValue | NullValue = 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) { + __ts_cast__(object); + // a. If O.[[ObjectWasVisited]] is false, then + if (O.ObjectWasVisited === Value.false) { + // i. Let keys be ? object.[[OwnPropertyKeys]](). + const keys = Q(yield* 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 (key instanceof JSStringValue) { + // 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(yield* object.GetOwnProperty(r)); + // 2. If desc is not undefined, then, + if (!(desc instanceof UndefinedValue)) { + // a. Append r to visited. + visited.push(r); + // b. If desc.[[Enumerable]] is true, return CreateIteratorResultObject(r, false). + if (desc.Enumerable === Value.true) { + return CreateIteratorResultObject(r, Value.false); + } + } + } + } + // c. Set object to ? object.[[GetPrototypeOf]](). + object = Q(yield* 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 CreateIteratorResultObject(undefined, true). + if (object === Value.null) { + return CreateIteratorResultObject(Value.undefined, Value.true); + } + } +} + +export function bootstrapForInIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', ForInIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%Iterator.prototype%']); + + realmRec.Intrinsics['%ForInIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/Function.mts b/src/intrinsics/Function.mts new file mode 100644 index 0000000..11f9a72 --- /dev/null +++ b/src/intrinsics/Function.mts @@ -0,0 +1,24 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q } from '../completion.mts'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + type Arguments, type ValueEvaluator, type FunctionCallContext, type FunctionObject, type Realm, + Value, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-function-p1-p2-pn-body */ +function* FunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + const bodyArg = args[args.length - 1] || Value(''); + args = args.slice(0, -1) as Arguments; + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject as FunctionObject; + // 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(yield* CreateDynamicFunction(C, NewTarget, 'normal', args, bodyArg)); +} + +export function bootstrapFunction(realmRec: Realm) { + const cons = bootstrapConstructor(realmRec, FunctionConstructor, 'Function', 1, realmRec.Intrinsics['%Function.prototype%'], []); + realmRec.Intrinsics['%Function%'] = cons; +} diff --git a/src/intrinsics/FunctionPrototype.mts b/src/intrinsics/FunctionPrototype.mts new file mode 100644 index 0000000..497b3bb --- /dev/null +++ b/src/intrinsics/FunctionPrototype.mts @@ -0,0 +1,232 @@ +import { + surroundingAgent, + HostHasSourceTextAvailable, +} from '../host-defined/engine.mts'; +import { + JSStringValue, + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { assignProps } from './bootstrap.mts'; +import { + Assert, + Call, + Construct, + CreateListFromArrayLike, + IsCallable, + IsConstructor, + OrdinaryHasInstance, + PrepareForTailCall, + SameValue, + CreateBuiltinFunction, + MakeBasicObject, + type ExoticObject, + type FunctionObject, + isBuiltinFunctionObject, + type BuiltinFunctionObject, + hasSourceTextInternalSlot, + CopyNameAndLength, + Realm, +} from '#self'; + +export interface BoundFunctionObject extends ExoticObject, BuiltinFunctionObject { + readonly BoundTargetFunction: FunctionObject; + readonly BoundThis: Value; + readonly BoundArguments: Arguments; +} + +export function isBoundFunctionObject(object: object): object is BoundFunctionObject { + return 'BoundTargetFunction' in object; +} + +/** https://tc39.es/ecma262/#sec-properties-of-the-function-prototype-object */ +function FunctionProto() { + // * accepts any arguments and returns undefined when invoked. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-function.prototype.apply */ +function* FunctionProto_apply([thisArg = Value.undefined, argArray = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let func be the this value. + const func = thisValue; + // 2. If IsCallable(func) is false, throw a TypeError exception. + if (!IsCallable(func)) { + return surroundingAgent.Throw('TypeError', 'ThisNotAFunction', 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(yield* Call(func, thisArg)); + } + // 4. Let argList be ? CreateListFromArrayLike(argArray). + const argList = Q(yield* CreateListFromArrayLike(argArray)); + // 5. Perform PrepareForTailCall(). + PrepareForTailCall(); + // 6. Return ? Call(func, thisArg, argList). + return Q(yield* Call(func, thisArg, argList)); +} + +function* BoundFunctionExoticObjectCall(this: BoundFunctionObject, _thisArgument: ObjectValue, argumentsList: Arguments): ValueEvaluator { + const F = this; + + const target = F.BoundTargetFunction; + const boundThis = F.BoundThis; + const boundArgs = F.BoundArguments; + const args = [...boundArgs.values(), ...argumentsList.values()]; + return Q(yield* Call(target, boundThis, args)); +} + +function* BoundFunctionExoticObjectConstruct(this: BoundFunctionObject, argumentsList: Arguments, newTarget: FunctionObject | UndefinedValue): ValueEvaluator { + const F = this; + + const target = F.BoundTargetFunction; + Assert(IsConstructor(target)); + const boundArgs = F.BoundArguments; + const args = [...boundArgs.values(), ...argumentsList.values()]; + if (SameValue(F, newTarget) === Value.true) { + newTarget = target; + } + return Q(yield* Construct(target, args, newTarget)); +} + +/** https://tc39.es/ecma262/#sec-boundfunctioncreate */ +function* BoundFunctionCreate(targetFunction: ObjectValue, boundThis: Value, boundArgs: Arguments): ValueEvaluator { + // 1. Assert: Type(targetFunction) is Object. + Assert(targetFunction instanceof ObjectValue); + // 2. Let proto be ? targetFunction.[[GetPrototypeOf]](). + const proto = Q(yield* 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)) as Mutable; + // 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)) { + // a. Set obj.[[Construct]] as described in 9.4.1.2. + obj.Construct = BoundFunctionExoticObjectConstruct; + } + // 8. Set obj.[[BoundTargetFunction]] to targetFunction. + obj.BoundTargetFunction = targetFunction as FunctionObject; + // 9. Set obj.[[BoundThis]] to boundThis. + obj.BoundThis = boundThis; + // 10. Set obj.[[BoundArguments]] to boundArguments. + obj.BoundArguments = boundArgs; + // 11. Return obj. + return obj; +} + +/** https://tc39.es/ecma262/#sec-function.prototype.bind */ +function* FunctionProto_bind([thisArg = Value.undefined, ...args]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let Target be the this value. + const Target = thisValue; + // 2. If IsCallable(Target) is false, throw a TypeError exception. + if (!IsCallable(Target)) { + return surroundingAgent.Throw('TypeError', 'ThisNotAFunction', Target); + } + __ts_cast__(Target); + // 3. Let F be ? BoundFunctionCreate(Target, thisArg, args). + const F = Q(yield* BoundFunctionCreate(Target, thisArg, args as Arguments)); + Q(yield* CopyNameAndLength(F, Target, 'bound', args.length)); + return F; +} + +/** https://tc39.es/ecma262/#sec-function.prototype.call */ +function* FunctionProto_call([thisArg = Value.undefined, ...args]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let func be the this value. + const func = thisValue; + // 2. If IsCallable(func) is false, throw a TypeError exception. + if (!IsCallable(func)) { + return surroundingAgent.Throw('TypeError', 'ThisNotAFunction', func); + } + // 3. Let argList be a new empty List. + const argList: Value[] = []; + // 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(yield* Call(func, thisArg, argList)); +} + +/** https://tc39.es/ecma262/#sec-function.prototype.tostring */ +export function FunctionProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let func be the this value. + const func = thisValue; + // 2. 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 (hasSourceTextInternalSlot(func) + && X(HostHasSourceTextAvailable(func)) === Value.true) { + // Return ! UTF16Encode(func.[[SourceText]]). + return Value(func.SourceText); + } + // 3. 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 (isBuiltinFunctionObject(func)) { + if (func.InitialName instanceof JSStringValue) { + return Value(`function ${func.InitialName.stringValue()}() { [native code] }`); + } + return Value('function() { [native code] }'); + } + // 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 (func instanceof ObjectValue && IsCallable(func)) { + return Value('function() { [native code] }'); + } + // 5. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); +} + +/** https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance */ +function* FunctionProto_hasInstance([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let F be this value. + const F = thisValue; + // 2. Return ? OrdinaryHasInstance(F, V). + return Q(yield* OrdinaryHasInstance(F, V)); +} + +export function bootstrapFunctionPrototype(realmRec: Realm) { + const proto = CreateBuiltinFunction( + FunctionProto, + 0, + Value(''), + [], + realmRec, + realmRec.Intrinsics['%Object.prototype%'], + ); + realmRec.Intrinsics['%Function.prototype%'] = proto; + + 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/src/intrinsics/GeneratorFunction.mts b/src/intrinsics/GeneratorFunction.mts new file mode 100644 index 0000000..a2f4aca --- /dev/null +++ b/src/intrinsics/GeneratorFunction.mts @@ -0,0 +1,34 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Descriptor, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { CreateDynamicFunction } from '../runtime-semantics/all.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { DefinePropertyOrThrow, type FunctionObject, Realm } from '#self'; + +/** https://tc39.es/ecma262/#sec-generatorfunction */ +function* GeneratorFunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + const bodyArg = args[args.length - 1] || Value(''); + args = args.slice(0, -1) as Arguments; + // 1. Let C be the active function object. + const C = surroundingAgent.activeFunctionObject as FunctionObject; + // 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(yield* CreateDynamicFunction(C, NewTarget, 'generator', args, bodyArg)); +} + +export function bootstrapGeneratorFunction(realmRec: Realm) { + const generator = realmRec.Intrinsics['%GeneratorFunction.prototype%']; + + const cons = bootstrapConstructor(realmRec, GeneratorFunctionConstructor, 'GeneratorFunction', 1, generator, []); + X(DefinePropertyOrThrow(cons, Value('prototype'), Descriptor({ + Writable: Value.false, + Configurable: Value.false, + }))); + X(DefinePropertyOrThrow(generator, Value('constructor'), Descriptor({ + Writable: Value.false, + }))); + + realmRec.Intrinsics['%GeneratorFunction%'] = cons; +} diff --git a/src/intrinsics/GeneratorFunctionPrototype.mts b/src/intrinsics/GeneratorFunctionPrototype.mts new file mode 100644 index 0000000..7412f62 --- /dev/null +++ b/src/intrinsics/GeneratorFunctionPrototype.mts @@ -0,0 +1,22 @@ +import { Descriptor, Value } from '../value.mts'; +import { X } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { DefinePropertyOrThrow } from '#self'; +import type { Realm } from '#self'; + +export function bootstrapGeneratorFunctionPrototype(realmRec: Realm) { + const generatorPrototype = realmRec.Intrinsics['%GeneratorFunction.prototype.prototype%']; + + const generator = bootstrapPrototype(realmRec, [ + ['prototype', generatorPrototype, undefined, { Writable: Value.false }], + ], realmRec.Intrinsics['%Function.prototype%'], 'GeneratorFunction'); + + X(DefinePropertyOrThrow(generatorPrototype, Value('constructor'), Descriptor({ + Value: generator, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%GeneratorFunction.prototype%'] = generator; +} diff --git a/src/intrinsics/GeneratorFunctionPrototypePrototype.mts b/src/intrinsics/GeneratorFunctionPrototypePrototype.mts new file mode 100644 index 0000000..4870fb6 --- /dev/null +++ b/src/intrinsics/GeneratorFunctionPrototypePrototype.mts @@ -0,0 +1,59 @@ +import { + Completion, + ThrowCompletion, + Q, + X, + type ValueEvaluator, +} from '../completion.mts'; +import { + Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + GeneratorResume, + GeneratorResumeAbrupt, + type FunctionObject, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-generator.prototype.next */ +function* GeneratorProto_next([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let g be the this value. + const g = thisValue; + // 2. Return ? GeneratorResume(g, value, empty). + return Q(yield* GeneratorResume(g, value, undefined)); +} + +/** https://tc39.es/ecma262/#sec-generator.prototype.return */ +function* GeneratorProto_return([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 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, empty). + return Q(yield* GeneratorResumeAbrupt(g, C, undefined)); +} + +/** https://tc39.es/ecma262/#sec-generator.prototype.throw */ +function* GeneratorProto_throw([exception = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 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, empty). + return Q(yield* GeneratorResumeAbrupt(g, C, undefined)); +} + +export function bootstrapGeneratorFunctionPrototypePrototype(realmRec: Realm) { + const generatorPrototype = bootstrapPrototype(realmRec, [ + ['next', GeneratorProto_next, 1], + ['return', GeneratorProto_return, 1], + ['throw', GeneratorProto_throw, 1], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'Generator'); + + realmRec.Intrinsics['%GeneratorFunction.prototype.prototype%'] = generatorPrototype; + realmRec.Intrinsics['%GeneratorPrototype%'] = realmRec.Intrinsics['%GeneratorFunction.prototype.prototype%']; + + // Used by `CreateListIteratorRecord`: + realmRec.Intrinsics['%GeneratorFunction.prototype.prototype.next%'] = X(generatorPrototype.Get(Value('next'), generatorPrototype)) as FunctionObject; +} diff --git a/src/intrinsics/Iterator.mts b/src/intrinsics/Iterator.mts new file mode 100644 index 0000000..2408057 --- /dev/null +++ b/src/intrinsics/Iterator.mts @@ -0,0 +1,122 @@ +import { AbruptCompletion, Q, type ValueEvaluator } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, + type BooleanValue, + UndefinedValue, + Value, + type Arguments, + type FunctionCallContext, + wellKnownSymbols, +} from '../value.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Call, + CreateIteratorFromClosure, + GetIteratorDirect, + GetIteratorFlattenable, + GetMethod, + IteratorClose, + IteratorStepValue, + OrdinaryCreateFromConstructor, + OrdinaryHasInstance, + OrdinaryObjectCreate, + Yield, + type BuiltinFunctionObject, + type FunctionObject, + type IteratorObject, + type Realm, + type YieldEvaluator, +} from '#self'; + + +/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-iterator-constructor */ +function* IteratorConstructor( + this: BuiltinFunctionObject, + _args: Arguments, + { NewTarget }: FunctionCallContext, +): ValueEvaluator { + // 1. If NewTarget is either undefined or the active function object, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + if (NewTarget === surroundingAgent.activeFunctionObject) { + return surroundingAgent.Throw('TypeError', 'CannotConstructAbstractFunction', NewTarget); + } + + // 2. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Iterator.prototype%"). + return Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Iterator.prototype%')); +} + +/** https://tc39.es/ecma262/#sec-iterator.from */ +function* Iterator_from([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let iteratorRecord be ? GetIteratorFlattenable(O, iterate-string-primitives). + const iteratorRecord = Q(yield* GetIteratorFlattenable(O, 'iterate-string-primitives')); + + // 2. Let hasInstance be ? OrdinaryHasInstance(%Iterator%, iteratorRecord.[[Iterator]]). + const hasInstance: BooleanValue = Q(yield* OrdinaryHasInstance(surroundingAgent.intrinsic('%Iterator%'), iteratorRecord.Iterator)); + // 3. If hasInstance is true, then + if (hasInstance === Value.true) { + // a. Return iteratorRecord.[[Iterator]]. + return iteratorRecord.Iterator; + } + + // 4. Let wrapper be OrdinaryObjectCreate(%WrapForValidIteratorPrototype%, « [[Iterated]] »). + const wrapper = OrdinaryObjectCreate( + surroundingAgent.intrinsic('%WrapForValidIteratorPrototype%'), + ['Iterated'], + ) as Mutable; + // 5. Set wrapper.[[Iterated]] to iteratorRecord. + wrapper.Iterated = iteratorRecord; + // 6. Return wrapper. + return wrapper; +} + +/** https://tc39.es/ecma262/#sec-iterator.concat */ +function* Iterator_concat(items: Arguments): ValueEvaluator { + const iterables: { OpenMethod: FunctionObject, Iterable: ObjectValue }[] = []; + for (const item of items.values()) { + if (!(item instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', item); + } + const method = Q(yield* GetMethod(item, wellKnownSymbols.iterator)); + if (method instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'NotIterable', item); + } + iterables.push({ OpenMethod: method, Iterable: item }); + } + const gen = CreateIteratorFromClosure(function* Iterator_concat(): YieldEvaluator { + for (const iterable of iterables) { + const iter = Q(yield* Call(iterable.OpenMethod, iterable.Iterable)); + if (!(iter instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotIterable', iter); + } + const iteratorRecord = Q(yield* GetIteratorDirect(iter)); + let innerAlive = true; + while (innerAlive) { + const innerValue = Q(yield* IteratorStepValue(iteratorRecord)); + if (innerValue === 'done') { + innerAlive = false; + } else { + const completion = yield* Yield(innerValue); + if (completion instanceof AbruptCompletion) { + return Q(yield* IteratorClose(iteratorRecord, completion)); + } + } + } + } + return Value.undefined; + }, Value('Iterator Helper'), surroundingAgent.intrinsic('%IteratorHelperPrototype%'), ['UnderlyingIterators']); + gen.UnderlyingIterators = []; + return gen; +} + +export function bootstrapIterator(realmRec: Realm) { + const cons = bootstrapConstructor(realmRec, IteratorConstructor, 'Iterator', 0, realmRec.Intrinsics['%Iterator.prototype%'], [ + ['from', Iterator_from, 1], + ['concat', Iterator_concat, 0], + ]); + + realmRec.Intrinsics['%Iterator%'] = cons; +} diff --git a/src/intrinsics/IteratorHelperPrototype.mts b/src/intrinsics/IteratorHelperPrototype.mts new file mode 100644 index 0000000..0d47683 --- /dev/null +++ b/src/intrinsics/IteratorHelperPrototype.mts @@ -0,0 +1,61 @@ +import { + NormalCompletion, Q, ReturnCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { + Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Assert, + CreateIteratorResultObject, + GeneratorResume, + GeneratorResumeAbrupt, + IteratorCloseAll, + Realm, + RequireInternalSlot, + type GeneratorObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-%iteratorhelperprototype%.next */ +function* IteratorHelperPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + return Q(yield* GeneratorResume(thisValue, Value.undefined, Value('Iterator Helper'))); +} + +/** https://tc39.es/ecma262/#sec-%iteratorhelperprototype%.return */ +function* IteratorHelperPrototype_return(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[UnderlyingIterators]]). + Q(RequireInternalSlot(O, 'UnderlyingIterators')); + // 3. Assert: O has a [[GeneratorState]] internal slot. + Assert('GeneratorState' in O); + + // 4. If O.[[GeneratorState]] is suspended-start, then + if (O.GeneratorState === 'suspendedStart') { + // a. Set O.[[GeneratorState]] to completed. + O.GeneratorState = 'completed'; + + // b. NOTE: Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. + // Any execution state associated with O can be discarded at this point. + // c. Perform ? IteratorCloseAll(O.[[UnderlyingIterators]], NormalCompletion(unused)). + Q(yield* IteratorCloseAll((O as GeneratorObject).UnderlyingIterators!, NormalCompletion(undefined))); + + // d. Return CreateIteratorResultObject(undefined, true). + return CreateIteratorResultObject(Value.undefined, Value.true); + } + + // 5. Let C be ReturnCompletion(undefined). + const C = ReturnCompletion(Value.undefined); + // 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + return Q(yield* GeneratorResumeAbrupt(O, C, Value('Iterator Helper'))); +} + +export function bootstrapIteratorHelperPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', IteratorHelperPrototype_next, 0], + ['return', IteratorHelperPrototype_return, 0], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'Iterator Helper'); + + realmRec.Intrinsics['%IteratorHelperPrototype%'] = proto; +} diff --git a/src/intrinsics/IteratorPrototype.mts b/src/intrinsics/IteratorPrototype.mts new file mode 100644 index 0000000..f2beb9e --- /dev/null +++ b/src/intrinsics/IteratorPrototype.mts @@ -0,0 +1,751 @@ +import { + AbruptCompletion, + EnsureCompletion, + IfAbruptCloseIterator, + NormalCompletion, + Q, + ReturnCompletion, + X, + type PlainCompletion, + type ValueCompletion, + type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BooleanValue, + NumberValue, + ObjectValue, + UndefinedValue, + Value, wellKnownSymbols, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Call, + CreateArrayFromList, + CreateIteratorFromClosure, + GetIteratorDirect, + GetIteratorFlattenable, + IsCallable, + IteratorClose, + IteratorStep, + IteratorStepValue, + SetterThatIgnoresPrototypeProperties, + ToBoolean, + ToIntegerOrInfinity, + ToNumber, + ToString, + Yield, + type GeneratorObject, + type IteratorRecord, + type Realm, +} from '#self'; + +/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-get-iterator.prototype.constructor */ +function IteratorProto_constructorGetter() { + // 1. Return %Iterator%. + return surroundingAgent.intrinsic('%Iterator%'); +} + +/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-set-iterator.prototype.constructor */ +function* IteratorProto_constructorSetter([v = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Iterator.prototype%, "constructor", v). + Q(yield* SetterThatIgnoresPrototypeProperties( + thisValue, + surroundingAgent.intrinsic('%Iterator.prototype%'), + Value('constructor'), + v, + )); + // 2. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.drop */ +function* IteratorPrototype_drop([limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. Let numLimit be Completion(ToNumber(limit)). + const numLimit: ValueCompletion = EnsureCompletion(yield* ToNumber(limit)); + // 5. IfAbruptCloseIterator(numLimit, iterated). + IfAbruptCloseIterator(numLimit, iterated); + __ts_cast__(numLimit); + // 6. If numLimit is NaN, then + if (numLimit.isNaN()) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + const integerLimit: number = X(yield* ToIntegerOrInfinity(numLimit instanceof NormalCompletion ? numLimit.Value : numLimit)); + // 8. If integerLimit < 0, then + if (integerLimit < 0) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 9. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 10. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs the following steps when called: + const closure = function* closure() { + // a. Let remaining be integerLimit. + let remaining: number = integerLimit; + // b. Repeat, while remaining > 0, + while (remaining > 0) { + // i. If remaining ≠ +∞, then + if (remaining !== +Infinity) { + // 1. Set remaining to remaining - 1. + remaining -= 1; + } + // ii. Let next be ? IteratorStep(iterated). + const next = Q(yield* IteratorStep(iterated)); + // iii. If next is done, return ReturnCompletion(undefined). + if (next === 'done') { + return ReturnCompletion(Value.undefined); + } + } + // c. Repeat, + while (true) { + // i. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // ii. If value is done, return ReturnCompletion(undefined). + if (value === 'done') { + return ReturnCompletion(Value.undefined); + } + // iii. Let completion be Completion(Yield(value)). + const completion = EnsureCompletion(yield* Yield(value)); + // iv. IfAbruptCloseIterator(completion, iterated). + IfAbruptCloseIterator(completion, iterated); + } + }; + // 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). + const result: Mutable = CreateIteratorFromClosure( + closure, + Value('Iterator Helper'), + surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'], + ['UnderlyingIterators'], + ); + // 12. Set result.[[UnderlyingIterators]] to iterated. + result.UnderlyingIterators = [iterated]; + // 13. Return result. + return result; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.every */ +function* IteratorPrototype_every([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(predicate) is false, then + if (IsCallable(predicate) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let counter be 0. + let counter = 0; + // 7. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // b. If value is done, return true. + if (value === 'done') { + return Value.true; + } + // c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)). + const result: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]); + // d. IfAbruptCloseIterator(result, iterated). + IfAbruptCloseIterator(result, iterated); + __ts_cast__(result); + // e. If ToBoolean(result) is false, return ? IteratorClose(iterated, NormalCompletion(false)). + if (ToBoolean(result) === Value.false) { + return Q(yield* IteratorClose(iterated, EnsureCompletion(Value.false))); + } + // f. Set counter to counter + 1. + counter += 1; + } +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.filter */ +function* IteratorPrototype_filter([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(predicate) is false, then + if (IsCallable(predicate) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let closure be a new Abstract Closure with no parameters that captures iterated and predicate and performs the following steps when called: + const closure = function* closure() { + // a. Let counter be 0. + let counter = 0; + // b. Repeat, + while (true) { + // i. Let value be ? IteratorStepValue(iterated). + const value = Q(yield* IteratorStepValue(iterated)); + // ii. If value is done, return ReturnCompletion(undefined). + if (value === 'done') { + return ReturnCompletion(Value.undefined); + } + // iii. Let selected be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)). + const selected: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]); + // iv. IfAbruptCloseIterator(selected, iterated). + IfAbruptCloseIterator(selected, iterated); + // v. If ToBoolean(selected) is true, then + __ts_cast__(selected); + if (ToBoolean(selected) === Value.true) { + // 1. Let completion be Completion(Yield(value)). + const completion = EnsureCompletion(yield* Yield(value)); + // 2. IfAbruptCloseIterator(completion, iterated). + IfAbruptCloseIterator(completion, iterated); + } + // vi. Set counter to counter + 1. + counter += 1; + } + }; + // 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). + const result = CreateIteratorFromClosure( + closure, + Value('Iterator Helper'), + surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'], + ['UnderlyingIterators'], + ); + // 8. Set result.[[UnderlyingIterators]] to iterated. + result.UnderlyingIterators = [iterated]; + // 9. Return result. + return result; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.find */ +function* IteratorPrototype_find([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(predicate) is false, then + if (IsCallable(predicate) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let counter be 0. + let counter = 0; + // 7. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // b. If value is done, return undefined. + if (value === 'done') { + return Value.undefined; + } + // c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)). + const result: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]); + // d. IfAbruptCloseIterator(result, iterated). + IfAbruptCloseIterator(result, iterated); + // e. If ToBoolean(result) is true, return ? IteratorClose(iterated, NormalCompletion(value)). + __ts_cast__(result); + if (ToBoolean(result) === Value.true) { + return Q(yield* IteratorClose(iterated, EnsureCompletion(value))); + } + // f. Set counter to counter + 1. + counter += 1; + } +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.flatmap */ +function* IteratorPrototype_flatMap([mapper = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(mapper) is false, then + if (IsCallable(mapper) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', mapper); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the following steps when called: + const closure = function* closure() { + // a. Let counter be 0. + let counter = 0; + // b. Repeat, + while (true) { + // i. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // ii. If value is done, return ReturnCompletion(undefined). + if (value === 'done') { + return ReturnCompletion(Value.undefined); + } + // iii. Let mapped be Completion(Call(mapper, undefined, « value, 𝔽(counter) »)). + const mapped: ValueCompletion = EnsureCompletion(yield* Call(mapper, Value.undefined, [value, Value(counter)])); + // iv. IfAbruptCloseIterator(mapped, iterated). + IfAbruptCloseIterator(mapped, iterated); + __ts_cast__(mapped); + // v. Let innerIterator be Completion(GetIteratorFlattenable(mapped, reject-primitives)). + const innerIterator: PlainCompletion = EnsureCompletion(yield* GetIteratorFlattenable(mapped, 'reject-primitives')); + // vi. IfAbruptCloseIterator(innerIterator, iterated). + IfAbruptCloseIterator(innerIterator, iterated); + __ts_cast__(innerIterator); + // vii. Let innerAlive be true. + let innerAlive = true; + // viii. Repeat, while innerAlive is true, + while (innerAlive) { + // 1. Let innerValue be Completion(IteratorStepValue(innerIterator)). + const innerValue: PlainCompletion = yield* IteratorStepValue(innerIterator); + // 2. IfAbruptCloseIterator(innerValue, iterated). + IfAbruptCloseIterator(innerValue, iterated); + __ts_cast__(innerValue); + // 3. If innerValue is done, then + if (innerValue === 'done') { + // a. Set innerAlive to false. + innerAlive = false; + // 4. Else, + } else { + // a. Let completion be Completion(Yield(innerValue)). + const completion = EnsureCompletion(yield* Yield(innerValue)); + // b. If completion is an abrupt completion, then + if (completion instanceof AbruptCompletion) { + // i. Let backupCompletion be Completion(IteratorClose(innerIterator, completion)). + const backupCompletion = EnsureCompletion(yield* IteratorClose(innerIterator, completion)); + // ii. IfAbruptCloseIterator(backupCompletion, iterated). + IfAbruptCloseIterator(backupCompletion, iterated); + // iii. Return ? IteratorClose(iterated, completion). + return Q(yield* IteratorClose(iterated, completion)); + } + } + } + // ix. Set counter to counter + 1. + counter += 1; + } + }; + + // 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). + const result = CreateIteratorFromClosure( + closure, + Value('Iterator Helper'), + surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'], + ['UnderlyingIterators'], + ); + // 8. Set result.[[UnderlyingIterators]] to iterated. + result.UnderlyingIterators = [iterated]; + // 9. Return result. + return result; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.foreach */ +function* IteratorPrototype_forEach([procedure = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(procedure) is false, then + if (IsCallable(procedure) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', procedure); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let counter be 0. + let counter = 0; + // 7. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // b. If value is done, return undefined. + if (value === 'done') { + return Value.undefined; + } + // c. Let result be Completion(Call(procedure, undefined, « value, 𝔽(counter) »)). + const result: ValueCompletion = yield* Call(procedure, Value.undefined, [value, Value(counter)]); + // d. IfAbruptCloseIterator(result, iterated). + IfAbruptCloseIterator(result, iterated); + // e. Set counter to counter + 1. + counter += 1; + } +} + +/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-iterator.prototype-%symbol.iterator% */ +function IteratorPrototype_iterator(_args: Arguments, { thisValue }: FunctionCallContext) { + // 1. Return the this value. + return thisValue; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.map */ +function* IteratorPrototype_map([mapper = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(mapper) is false, then + if (IsCallable(mapper) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', mapper); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the following steps when called: + const closure = function* closure() { + // a. Let counter be 0. + let counter = 0; + // b. Repeat, + while (true) { + // i. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // ii. If value is done, return ReturnCompletion(undefined). + if (value === 'done') { + return ReturnCompletion(Value.undefined); + } + // iii. Let mapped be Completion(Call(mapper, undefined, « value, 𝔽(counter) »)). + const mapped: ValueCompletion = yield* Call(mapper, Value.undefined, [value, Value(counter)]); + // iv. IfAbruptCloseIterator(mapped, iterated). + IfAbruptCloseIterator(mapped, iterated); + // v. Let completion be Completion(Yield(mapped)). + __ts_cast__(mapped); + const completion = EnsureCompletion(yield* Yield(mapped)); + // vi. IfAbruptCloseIterator(completion, iterated). + IfAbruptCloseIterator(completion, iterated); + // vii. Set counter to counter + 1. + counter += 1; + } + }; + // 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). + const result = CreateIteratorFromClosure( + closure, + Value('Iterator Helper'), + surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'], + ['UnderlyingIterators'], + ); + // 8. Set result.[[UnderlyingIterators]] to [iterated]. + result.UnderlyingIterators = [iterated]; + // 9. Return result. + return result; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.reduce */ +function* IteratorPrototype_reduce(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(reducer) is false, then + const reducer = args[0] ?? Value.undefined; + if (IsCallable(reducer) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', reducer); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. If initialValue is not present, then + let accumulator: Value | 'done'; + let counter: number; + if (args.length < 2) { + // a. Let accumulator be ? IteratorStepValue(iterated). + accumulator = Q(yield* IteratorStepValue(iterated)); + // b. If accumulator is done, throw a TypeError exception. + if (accumulator === 'done') { + return surroundingAgent.Throw('TypeError', 'IteratorCompleted'); + } + // c. Let counter be 1. + counter = 1; + } else { + // 7. Else, + // a. Let accumulator be initialValue. + // b. Let counter be 0. + accumulator = args[1] ?? Value.undefined; + counter = 0; + } + // 8. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // b. If value is done, return accumulator. + if (value === 'done') { + return accumulator; + } + // c. Let result be Completion(Call(reducer, undefined, « accumulator, value, 𝔽(counter) »)). + const result: ValueCompletion = yield* Call(reducer, Value.undefined, [accumulator, value, Value(counter)]); + // d. IfAbruptCloseIterator(result, iterated). + IfAbruptCloseIterator(result, iterated); + // e. Set accumulator to result. + __ts_cast__(result); + accumulator = result; + // f. Set counter to counter + 1. + counter += 1; + } +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.some */ +function* IteratorPrototype_some([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. If IsCallable(predicate) is false, then + if (IsCallable(predicate) === false) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 5. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 6. Let counter be 0. + let counter = 0; + // 7. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // b. If value is done, return false. + if (value === 'done') { + return Value.false; + } + // c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)). + const result: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]); + // d. IfAbruptCloseIterator(result, iterated). + IfAbruptCloseIterator(result, iterated); + __ts_cast__(result); + // e. If ToBoolean(result) is true, return ? IteratorClose(iterated, NormalCompletion(true)). + if (ToBoolean(result) === Value.true) { + return Q(yield* IteratorClose(iterated, EnsureCompletion(Value.true))); + } + // f. Set counter to counter + 1. + counter += 1; + } +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.take */ +function* IteratorPrototype_take([limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + // 4. Let numLimit be Completion(ToNumber(limit)). + const numLimit: ValueCompletion = yield* ToNumber(limit); + // 5. IfAbruptCloseIterator(numLimit, iterated). + IfAbruptCloseIterator(numLimit, iterated); + __ts_cast__(numLimit); + // 6. If numLimit is NaN, then + if (numLimit.isNaN()) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + const integerLimit: number = X(yield* ToIntegerOrInfinity(numLimit instanceof NormalCompletion ? numLimit.Value : numLimit)); + // 8. If integerLimit < 0, then + if (integerLimit < 0) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit); + // b. Return ? IteratorClose(iterated, error). + return Q(yield* IteratorClose(iterated, error)); + } + // 9. Set iterated to ? GetIteratorDirect(O). + iterated = Q(yield* GetIteratorDirect(O)); + // 10. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs the following steps when called: + const closure = function* closure() { + // a. Let remaining be integerLimit. + let remaining: number = integerLimit; + // b. Repeat, + while (true) { + // i. If remaining = 0, then + // 1. Return ? IteratorClose(iterated, ReturnCompletion(undefined)). + if (remaining === 0) { + return Q(yield* IteratorClose(iterated, ReturnCompletion(Value.undefined))); + } + // ii. If remaining ≠ +∞, then + // 1. Set remaining to remaining - 1. + if (remaining !== +Infinity) { + remaining -= 1; + } + // iii. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // iv. If value is done, return ReturnCompletion(undefined). + if (value === 'done') { + return ReturnCompletion(Value.undefined); + } + // v. Let completion be Completion(Yield(value)). + const completion = EnsureCompletion(yield* Yield(value)); + // vi. IfAbruptCloseIterator(completion, iterated). + IfAbruptCloseIterator(completion, iterated); + } + }; + // 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). + const result: Mutable = CreateIteratorFromClosure( + closure, + Value('Iterator Helper'), + surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'], + ['UnderlyingIterators'], + ); + // 12. Set result.[[UnderlyingIterators]] to iterated. + result.UnderlyingIterators = [iterated]; + // 13. Return result. + return result; +} + +/** https://tc39.es/ecma262/#sec-iterator.prototype.toarray */ +function* IteratorPrototype_toArray(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. If O is not an Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 3. Let iterated be ? GetIteratorDirect(O). + const iterated: IteratorRecord = Q(yield* GetIteratorDirect(O)); + // 4. Let items be a new empty List. + const items: Value[] = []; + // 5. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + const value: Value | 'done' = Q(yield* IteratorStepValue(iterated)); + // b. If value is done, return CreateArrayFromList(items). + if (value === 'done') { + return CreateArrayFromList(items); + } + // c. Append value to items. + items.push(value); + } +} + +/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-get-iterator.prototype-%symbol.tostringtag% */ +function IteratorPrototype_toStringTagGetter() { + return Value('Iterator'); +} + +/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-set-iterator.prototype-%symbol.tostringtag% */ +function* IteratorPrototype_toStringTagSetter([v = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Iterator.prototype%, %Symbol.toStringTag%, v). + Q(yield* SetterThatIgnoresPrototypeProperties( + thisValue, + surroundingAgent.intrinsic('%Iterator.prototype%'), + wellKnownSymbols.toStringTag, + v, + )); + // 2. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/proposal-iterator-join/#sec-iterator.prototype.join */ +function* IteratorPrototype_join([separator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false }; + let sep; + if (separator === Value.undefined) { + sep = ','; + } else { + const completion = yield* ToString(separator); + IfAbruptCloseIterator(completion, iterated); + sep = X(completion).stringValue(); + } + iterated = Q(yield* GetIteratorDirect(O)); + let R = ''; + let first = true; + while (true) { + const value = Q(yield* IteratorStepValue(iterated)); + if (value === 'done') { + return Value(R); + } + if (first) { + first = false; + } else { + R += sep; + } + if (value !== Value.undefined && value !== Value.null) { + const S_completion = yield* ToString(value); + IfAbruptCloseIterator(S_completion, iterated); + const S = X(S_completion).stringValue(); + R += S; + } + } +} + +export function bootstrapIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['constructor', [IteratorProto_constructorGetter, IteratorProto_constructorSetter]], + ['drop', IteratorPrototype_drop, 1], + ['every', IteratorPrototype_every, 1], + ['filter', IteratorPrototype_filter, 1], + ['find', IteratorPrototype_find, 1], + ['flatMap', IteratorPrototype_flatMap, 1], + ['forEach', IteratorPrototype_forEach, 1], + ['map', IteratorPrototype_map, 1], + ['reduce', IteratorPrototype_reduce, 1], + ['some', IteratorPrototype_some, 1], + ['take', IteratorPrototype_take, 1], + ['toArray', IteratorPrototype_toArray, 0], + [wellKnownSymbols.iterator, IteratorPrototype_iterator, 0], + [wellKnownSymbols.toStringTag, [IteratorPrototype_toStringTagGetter, IteratorPrototype_toStringTagSetter]], + surroundingAgent.feature('iterator.join') ? ['join', IteratorPrototype_join, 1] : undefined, + ], realmRec.Intrinsics['%Object.prototype%']); + + realmRec.Intrinsics['%Iterator.prototype%'] = proto; +} diff --git a/src/intrinsics/JSON.mts b/src/intrinsics/JSON.mts new file mode 100644 index 0000000..d7ecd06 --- /dev/null +++ b/src/intrinsics/JSON.mts @@ -0,0 +1,738 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BooleanValue, + NullValue, + NumberValue, + ObjectValue, + JSStringValue, + UndefinedValue, + Value, +} from '../value.mts'; +import { + CodePointsToString, + PropName, + UTF16EncodeCodePoint, +} from '../static-semantics/all.mts'; +import { + NormalCompletion, + Q, X, +} from '../completion.mts'; +import { isArray, JSStringSet, kInternal } from '../helpers.mts'; +import { + BigIntValue, F, ParseScript, Realm, ScriptEvaluation, ThrowCompletion, skipDebugger, type Arguments, + type CodePoint, + type FunctionObject, + type PlainCompletion, + isLeadingSurrogate, + isTrailingSurrogate, + type ParseNode, + type BuiltinFunctionObject, + SetIntegrityLevel, + SameValue, + type PropertyKeyValue, +} from '../index.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { + ArrayLiteralContentNodes, avoid_using_children, Contains, PropertyDefinitionNodes, +} from '../parser/utils.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { isBooleanObject } from './Boolean.mts'; +import { isBigIntObject } from './BigInt.mts'; +import { + Assert, + Call, + CreateDataProperty, + CreateDataPropertyOrThrow, + EnumerableOwnProperties, + Get, + GetV, + IsArray, + IsCallable, + OrdinaryObjectCreate, + LengthOfArrayLike, + ToIntegerOrInfinity, + ToNumber, + ToString, +} from '#self'; + +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 { + input; + + pos = 0; + + char: string | null; + + constructor(input: string) { + this.input = input; + 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: string | readonly string[]) { + 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: string | readonly string[]) { + 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(): PlainCompletion { + 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(): PlainCompletion { + 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(): PlainCompletion { + 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: string) { + const v = new JSONValidator(input); + return v.validate(); + } +} + +/** https://tc39.es/ecma262/pr/3714/#sec-json-parse-record */ +interface JSONParseRecord { + readonly ParseNode: ParseNode; + readonly Key: PropertyKeyValue; + readonly Value: Value; + readonly Elements: readonly JSONParseRecord[]; + readonly Entries: readonly JSONParseRecord[]; +} + +/** https://tc39.es/ecma262/pr/3714/#sec-internalizejsonproperty */ +function* InternalizeJSONProperty(holder: ObjectValue, name: JSStringValue, reviver: Value, parseRecord: JSONParseRecord | undefined): ValueEvaluator { + const val = Q(yield* Get(holder, name)); + const context = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + let elementRecords: readonly JSONParseRecord[]; + let entryRecords: readonly JSONParseRecord[]; + if (parseRecord && SameValue(parseRecord.Value, val) === Value.true) { + if (!(val instanceof ObjectValue)) { + const parseNode = parseRecord.ParseNode; + Assert(parseNode.type !== 'ArrayLiteral' && parseNode.type !== 'ObjectLiteral'); + const sourceText = parseNode.sourceText; + X(CreateDataPropertyOrThrow(context, Value('source'), Value(CodePointsToString(sourceText)))); + } + elementRecords = parseRecord.Elements; + entryRecords = parseRecord.Entries; + } else { + elementRecords = []; + entryRecords = []; + } + if (val instanceof ObjectValue) { + const isArray = Q(IsArray(val)); + if (isArray === Value.true) { + // Let _elementRecordsLen_ be the number of elements in _elementRecords_. + const elementRecordsLen = elementRecords.length; + let I = 0; + const len = Q(yield* LengthOfArrayLike(val)); + while (I < len) { + const prop = X(ToString(F(I))); + const elementRecord = I < elementRecordsLen ? elementRecords[I] : undefined; + const newElement = Q(yield* InternalizeJSONProperty(val, prop, reviver, elementRecord)); + if (newElement instanceof UndefinedValue) { + Q(yield* val.Delete(prop)); + } else { + Q(yield* CreateDataProperty(val, prop, newElement)); + } + I += 1; + } + } else { + const keys = Q(yield* EnumerableOwnProperties(val, 'key')); + for (const P of keys) { + const entryRecord = entryRecords.find((record) => SameValue(record.Key, P) === Value.true); + const newElement = Q(yield* InternalizeJSONProperty(val, P, reviver, entryRecord)); + // const newElement = Q(yield* InternalizeJSONProperty(val, P, reviver)); + if (newElement instanceof UndefinedValue) { + Q(yield* val.Delete(P)); + } else { + Q(yield* CreateDataProperty(val, P, newElement)); + } + } + } + } + return Q(yield* Call(reviver, holder, [name, val, context])); +} + +/** https://tc39.es/ecma262/pr/3714/#sec-createjsonparserecord */ +function CreateJSONParseRecord(parseNode: ParseNode, key: PropertyKeyValue, val: Value): JSONParseRecord { + const typedValNode = ShallowestContainedJSONValue(parseNode); + Assert(!!typedValNode); + const elements = []; + const entries = []; + if (val instanceof ObjectValue) { + const isArray = X(IsArray(val)); + if (isArray === Value.true) { + Assert(typedValNode.type === 'ArrayLiteral'); + const contentNodes = ArrayLiteralContentNodes(typedValNode); + const len = contentNodes.length; + const valLen = X(LengthOfArrayLike(val)); + Assert(valLen === len); + let I = 0; + while (I < len) { + const propName = X(ToString(F(I))); + const elementParseRecord = CreateJSONParseRecord(contentNodes[I], propName, X(Get(val, propName))); + elements.push(elementParseRecord); + I += 1; + } + } else { + Assert(typedValNode.type === 'ObjectLiteral'); + const propertyNodes = PropertyDefinitionNodes(typedValNode); + const keys = X(EnumerableOwnProperties(val, 'key')); + for (const P of keys) { + let propertyDefinition: ParseNode; + for (const propertyNode of propertyNodes) { + const propName = PropName(propertyNode); + if (propName === P.stringValue()) { + propertyDefinition = propertyNode; + } + } + Assert(!!(propertyDefinition!.type === 'PropertyDefinition' && propertyDefinition.PropertyName && propertyDefinition.AssignmentExpression)); + const propertyValueNode = propertyDefinition.AssignmentExpression; + const entryParseRecord = CreateJSONParseRecord(propertyValueNode, P, X(Get(val, P))); + entries.push(entryParseRecord); + } + } + } else { + Assert(typedValNode.type !== 'ArrayLiteral' && typedValNode.type !== 'ObjectLiteral'); + } + return { + ParseNode: typedValNode, Key: key, Value: val, Elements: elements, Entries: entries, + }; +} + +export function ParseJSON(text: string): PlainCompletion<{ ParseNode: ParseNode, Value: Value }> { + // 1. If StringToCodePoints(text) is not a valid JSON text as specified in ECMA-404, throw a SyntaxError exception. + Q(JSONValidator.validate(text)); + const scriptString = `(${text});`; + const script = ParseScript(scriptString, surroundingAgent.currentRealmRecord, { [kInternal]: { json: true } }); + Assert(!isArray(script)); // array means parse error + const result = X(skipDebugger(ScriptEvaluation(script))); + Assert(result instanceof JSStringValue || result instanceof NumberValue || result instanceof BooleanValue || result instanceof ObjectValue || result === Value.null); + return { ParseNode: script.ECMAScriptCode, Value: result }; +} + +/** https://tc39.es/ecma262/#sec-json.parse */ +function* JSON_parse([text = Value.undefined, reviver = Value.undefined]: Arguments): ValueEvaluator { + const jsonString = Q(yield* ToString(text)); + const parseResult = Q(ParseJSON(jsonString.stringValue())); + const unfiltered = parseResult.Value; + Assert(unfiltered instanceof JSStringValue + || unfiltered instanceof NumberValue + || unfiltered instanceof BooleanValue + || unfiltered instanceof NullValue + || unfiltered instanceof ObjectValue); + if (IsCallable(reviver)) { + const root = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + const rootName = Value(''); + X(CreateDataPropertyOrThrow(root, rootName, unfiltered)); + const snapshot = CreateJSONParseRecord(parseResult.ParseNode, rootName, unfiltered); + return Q(yield* InternalizeJSONProperty(root, rootName, reviver, snapshot)); + } else { + return unfiltered; + } +} + +const codeUnitTable = new Map([ + [0x0008, '\\b'], + [0x0009, '\\t'], + [0x000A, '\\n'], + [0x000C, '\\f'], + [0x000D, '\\r'], + [0x0022, '\\"'], + [0x005C, '\\\\'], +]); + +interface State { + ReplacerFunction: ObjectValue | UndefinedValue; + Stack: ObjectValue[]; + Indent: string; + Gap: string; + PropertyList: JSStringSet | UndefinedValue; +} +/** https://tc39.es/ecma262/#sec-serializejsonproperty */ +function* SerializeJSONProperty(state: State, key: JSStringValue, holder: ObjectValue): ValueEvaluator { + let value = Q(yield* Get(holder, key)); // eslint-disable-line no-shadow + if (value instanceof ObjectValue || value instanceof BigIntValue) { + const toJSON = Q(yield* GetV(value, Value('toJSON'))); + if (IsCallable(toJSON)) { + value = Q(yield* Call(toJSON, value, [key])); + } + } + if (state.ReplacerFunction !== Value.undefined) { + value = Q(yield* Call(state.ReplacerFunction, holder, [key, value])); + } + if (value instanceof ObjectValue) { + if ('IsRawJSON' in value) { + return X(Get(value, Value('rawJSON'))) as JSStringValue; + } + if ('NumberData' in value) { + value = Q(yield* ToNumber(value)); + } else if ('StringData' in value) { + value = Q(yield* ToString(value)); + } else if (isBooleanObject(value)) { + value = value.BooleanData; + } else if (isBigIntObject(value)) { + value = value.BigIntData; + } + } + if (value === Value.null) { + return Value('null'); + } + if (value === Value.true) { + return Value('true'); + } + if (value === Value.false) { + return Value('false'); + } + if (value instanceof JSStringValue) { + return QuoteJSONString(value); + } + if (value instanceof NumberValue) { + if (value.isFinite()) { + return X(ToString(value)); + } + return Value('null'); + } + if (value instanceof BigIntValue) { + return surroundingAgent.Throw('TypeError', 'CannotJSONSerializeBigInt'); + } + if (value instanceof ObjectValue && !IsCallable(value)) { + const isArray = Q(IsArray(value)); + if (isArray === Value.true) { + return Q(yield* SerializeJSONArray(state, value)); + } + return Q(yield* SerializeJSONObject(state, value)); + } + return Value.undefined; +} + +export function UnicodeEscape(C: string) { + const n = C.charCodeAt(0); + Assert(n < 0xFFFF); + return `\u005Cu${n.toString(16).padStart(4, '0')}`; +} + +/** https://tc39.es/ecma262/pr/3714/#sec-quotejsonstring */ +function QuoteJSONString(value: JSStringValue) { // 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 += UnicodeEscape(unit); + } else { + product += UTF16EncodeCodePoint(C as CodePoint); + } + } + product = `${product}\u0022`; + return Value(product); +} + +/** https://tc39.es/ecma262/#sec-serializejsonobject */ +function* SerializeJSONObject(state: State, value: ObjectValue): ValueEvaluator { + 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: IterableIterator; + if (!(state.PropertyList instanceof UndefinedValue)) { + K = state.PropertyList.keys(); + } else { + K = Q(yield* EnumerableOwnProperties(value, 'key')).values(); + } + const partial = []; + for (const P of K) { + const strP = Q(yield* SerializeJSONProperty(state, P, value)); + if (!(strP instanceof UndefinedValue)) { + 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 = Value('{}'); + } else { + if (state.Gap === '') { + const properties = partial.join(','); + final = Value(`{${properties}}`); + } else { + const separator = `,\u000A${state.Indent}`; + const properties = partial.join(separator); + final = Value(`{\u000A${state.Indent}${properties}\u000A${stepback}}`); + } + } + state.Stack.pop(); + state.Indent = stepback; + return final; +} + +/** https://tc39.es/ecma262/#sec-serializejsonarray */ +function* SerializeJSONArray(state: State, value: ObjectValue): PlainEvaluator { + 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(yield* LengthOfArrayLike(value)); + let index = 0; + while (index < len) { + const indexStr = X(ToString(F(index))); + const strP = Q(yield* SerializeJSONProperty(state, indexStr, value)); + if (strP instanceof UndefinedValue) { + partial.push('null'); + } else { + partial.push(strP.stringValue()); + } + index += 1; + } + let final; + if (partial.length === 0) { + final = Value('[]'); + } else { + if (state.Gap === '') { + const properties = partial.join(','); + final = Value(`[${properties}]`); + } else { + const separator = `,\u000A${state.Indent}`; + const properties = partial.join(separator); + final = Value(`[\u000A${state.Indent}${properties}\u000A${stepback}]`); + } + } + state.Stack.pop(); + state.Indent = stepback; + return final; +} + +/** https://tc39.es/ecma262/#sec-json.stringify */ +function* JSON_stringify([value = Value.undefined, replacer = Value.undefined, _space = Value.undefined]: Arguments): ValueEvaluator { + const stack: ObjectValue[] = []; + const indent = ''; + let PropertyList: JSStringSet | UndefinedValue = Value.undefined; + let ReplacerFunction: ObjectValue | UndefinedValue = Value.undefined; + if (replacer instanceof ObjectValue) { + if (IsCallable(replacer)) { + ReplacerFunction = replacer; + } else { + const isArray = Q(IsArray(replacer)); + if (isArray === Value.true) { + PropertyList = new JSStringSet(); + const len = Q(yield* LengthOfArrayLike(replacer)); + let k = 0; + while (k < len) { + const vStr = X(ToString(F(k))); + const v = Q(yield* Get(replacer, vStr)); + let item: JSStringValue | UndefinedValue = Value.undefined; + if (v instanceof JSStringValue) { + item = v; + } else if (v instanceof NumberValue) { + item = X(ToString(v)); + } else if (v instanceof ObjectValue) { + if ('StringData' in v || 'NumberData' in v) { + item = Q(yield* ToString(v)); + } + } + if (!(item instanceof UndefinedValue) && !PropertyList.has(item)) { + PropertyList.add(item); + } + k += 1; + } + } + } + } + let space: Value | number = _space; + if (space instanceof ObjectValue) { + if ('NumberData' in space) { + space = Q(yield* ToNumber(space)); + } else if ('StringData' in space) { + space = Q(yield* ToString(space)); + } + } + let gap: string; + if (space instanceof NumberValue) { + space = Math.min(10, X(ToIntegerOrInfinity(space))); + if (space < 1) { + gap = ''; + } else { + gap = ' '.repeat(space); + } + } else if (space instanceof JSStringValue) { + 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, Value(''), value)); + const state: State = { + ReplacerFunction, Stack: stack, Indent: indent, Gap: gap, PropertyList, + }; + return Q(yield* SerializeJSONProperty(state, Value(''), wrapper)); +} + +/** https://tc39.es/ecma262/#sec-json.rawjson */ +function* JSON_rawJSON([text = Value.undefined]: Arguments): ValueEvaluator { + const jsonString = Q(yield* ToString(text)); + const str = jsonString.stringValue(); + if (str === '') { + return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken'); + } + const forbiddenChar = ['\u0009', '\u000A', '\u000D', '\u0020', '\u005B', '\u007B']; + if (forbiddenChar.includes(str[0]) || forbiddenChar.includes(str[str.length - 1])) { + return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken'); + } + const parseResult = Q(ParseJSON(jsonString.stringValue())); + const value = parseResult.Value; + Assert(value instanceof JSStringValue || value instanceof NumberValue || value instanceof BooleanValue || value === Value.null); + { + const firstCodeUnit = str[0].charCodeAt(0); + Assert( + (firstCodeUnit >= 0x0061 && firstCodeUnit <= 0x007A) + || (firstCodeUnit >= 0x0030 && firstCodeUnit <= 0x0039) + || firstCodeUnit === 0x0022 + || firstCodeUnit === 0x002D, + ); + } + { + const lastCodeUnit = str[str.length - 1].charCodeAt(0); + Assert( + (lastCodeUnit >= 0x0061 && lastCodeUnit <= 0x007A) + || (lastCodeUnit >= 0x0030 && lastCodeUnit <= 0x0039) + || lastCodeUnit === 0x0022, + ); + } + const obj = OrdinaryObjectCreate(Value.null, ['IsRawJSON']); + X(CreateDataPropertyOrThrow(obj, Value('rawJSON'), jsonString)); + X(SetIntegrityLevel(obj, 'frozen')); + return obj; +} + +/** https://tc39.es/ecma262/pr/3714/#sec-json.israwjson */ +function JSON_isRawJSON([value = Value.undefined]: Arguments) { + if (value instanceof ObjectValue && 'IsRawJSON' in value) { + return Value.true; + } + return Value.false; +} + +/** https://tc39.es/ecma262/pr/3714/#sec-static-semantics-shallowestcontainedjsonvalue */ +function ShallowestContainedJSONValue(node: ParseNode): ParseNode | undefined { + const F = surroundingAgent.activeFunctionObject; + Assert((F as BuiltinFunctionObject).nativeFunction === JSON_parse); + const types: ParseNode['type'][] = [ + 'NullLiteral', 'BooleanLiteral', 'NumericLiteral', 'StringLiteral', 'ArrayLiteral', 'ObjectLiteral', 'UnaryExpression', + ]; + let unaryExpression: ParseNode | undefined; + let queue = [node]; + while (queue.length > 0) { + const candidate = queue.shift()!; + let queuedChildren = false; + for (const type of types) { + if (candidate?.type === type) { + if (type === 'UnaryExpression') { + unaryExpression = candidate; + } else if (type === 'NumericLiteral') { + // skip Assert: candidate is contained within unaryExpression + // our AST is different from the spec's AST + // Return unaryExpression. + return unaryExpression || candidate; + } else { + return candidate; + } + } + const children = [...avoid_using_children(candidate)]; + if (!queuedChildren && children.length && Contains(candidate, type)) { + queue = queue.concat(children); + queuedChildren = true; + } + } + } + return undefined; +} + +export function bootstrapJSON(realmRec: Realm) { + const json = bootstrapPrototype(realmRec, [ + ['parse', JSON_parse, 2], + ['stringify', JSON_stringify, 3], + ['rawJSON', JSON_rawJSON, 1], + ['isRawJSON', JSON_isRawJSON, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'JSON'); + + realmRec.Intrinsics['%JSON%'] = json; + realmRec.Intrinsics['%JSON.parse%'] = X(Get(json, Value('parse'))) as FunctionObject; + realmRec.Intrinsics['%JSON.stringify%'] = X(Get(json, Value('stringify'))) as FunctionObject; +} diff --git a/src/intrinsics/Map.mts b/src/intrinsics/Map.mts new file mode 100644 index 0000000..64a85f9 --- /dev/null +++ b/src/intrinsics/Map.mts @@ -0,0 +1,128 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + IfAbruptCloseIterator, + Q, + X, + type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Assert, + Call, + Construct, + CreateArrayFromList, + Get, + GetIterator, + GroupBy, + IsCallable, + IteratorClose, + IteratorStepValue, + OrdinaryCreateFromConstructor, + Realm, + type FunctionObject, + type KeyedGroupRecord, + type OrdinaryObject, +} from '#self'; + +export function* AddEntriesFromIterable(target: ObjectValue, iterable: Value, adder: FunctionObject): ValueEvaluator { + Assert(iterable !== Value.undefined && iterable !== Value.null); + const iteratorRecord = Q(yield* GetIterator(iterable, 'sync')); + while (true) { + const next = Q(yield* IteratorStepValue(iteratorRecord)); + if (next === 'done') { + return target; + } + if (!(next instanceof ObjectValue)) { + const error = surroundingAgent.Throw('TypeError', 'NotAnObject', next); + return Q(yield* IteratorClose(iteratorRecord, error)); + } + // e. Let k be Get(nextItem, "0"). + const k = yield* Get(next, Value('0')); + // f. IfAbruptCloseIterator(k, iteratorRecord). + IfAbruptCloseIterator(k, iteratorRecord); + __ts_cast__(k); + // g. Let v be Get(nextItem, "1"). + const v = yield* Get(next, Value('1')); + // h. IfAbruptCloseIterator(v, iteratorRecord). + IfAbruptCloseIterator(v, iteratorRecord); + __ts_cast__(v); + // i. Let status be Call(adder, target, « k, v »). + const status = yield* Call(adder, target, [k, v]); + // j. IfAbruptCloseIterator(status, iteratorRecord). + IfAbruptCloseIterator(status, iteratorRecord); + } +} + +export interface MapObject extends OrdinaryObject { + readonly MapData: { Key: Value | undefined, Value: Value | undefined }[]; +} +export function isMapObject(value: Value): value is MapObject { + return 'MapData' in value; +} +/** https://tc39.es/ecma262/#sec-map-iterable */ +function* MapConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »). + const map = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Map.prototype%', ['MapData'])) as Mutable; + // 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(yield* Get(map, Value('set'))); + if (!IsCallable(adder)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + // 6. Return ? AddEntriesFromIterable(map, iterable, adder). + return Q(yield* AddEntriesFromIterable(map, iterable, adder)); +} + +/** https://tc39.es/ecma262/#sec-map.groupby */ +function* Map_groupBy([items = Value.undefined, callback = Value.undefined]: Arguments): ValueEvaluator { + /* + 1. Let groups be ? GroupBy(items, callback, collection). + 2. Let map be ! Construct(%Map%). + 3. For each Record { [[Key]], [[Elements]] } g of groups, do + a. Let elements be CreateArrayFromList(g.[[Elements]]). + b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }. + c. Append entry to map.[[MapData]]. + 4. Return map. + */ + const groups: KeyedGroupRecord[] = Q(yield* GroupBy(items, callback, 'collection')); + const map: MapObject = X(Construct(surroundingAgent.intrinsic('%Map%'))) as MapObject; + for (const g of groups) { + const elements = CreateArrayFromList(g.Elements); + const entry = { Key: g.Key, Value: elements }; + map.MapData.push(entry); + } + return map; +} + +/** https://tc39.es/ecma262/#sec-get-map-@@species */ +function Map_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) { + // 1. Return the this value. + return thisValue; +} + +export function bootstrapMap(realmRec: Realm) { + const mapConstructor = bootstrapConstructor(realmRec, MapConstructor, 'Map', 0, realmRec.Intrinsics['%Map.prototype%'], [ + ['groupBy', Map_groupBy, 2], + [wellKnownSymbols.species, [Map_speciesGetter]], + ]); + + realmRec.Intrinsics['%Map%'] = mapConstructor; +} diff --git a/src/intrinsics/MapIteratorPrototype.mts b/src/intrinsics/MapIteratorPrototype.mts new file mode 100644 index 0000000..fb2ba64 --- /dev/null +++ b/src/intrinsics/MapIteratorPrototype.mts @@ -0,0 +1,81 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, X } from '../completion.mts'; +import { Value, type Arguments } from '../value.mts'; +import type { YieldEvaluator } from '../evaluator.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Assert, + CreateArrayFromList, + CreateIteratorFromClosure, + GeneratorResume, + Realm, + RequireInternalSlot, + Yield, +} from '#self'; +import type { + ValueEvaluator, FunctionCallContext, GeneratorObject, MapObject, + ValueCompletion, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-createmapiterator */ +export function CreateMapIterator(map: Value, kind: 'key+value' | 'key' | 'value'): ValueCompletion { + Assert(kind === 'key+value' || kind === 'key' || kind === 'value'); + // 1. Perform ? RequireInternalSlot(map, [[MapData]]). + Q(RequireInternalSlot(map, 'MapData')); + // 2. Let closure be a new Abstract Closure with no parameters that captures map and kind and performs the following steps when called: + const closure = function* closure(): YieldEvaluator { + // a. Let entries be the List that is map.[[MapData]]. + const entries = (map as MapObject).MapData; + // b. Let index be 0. + let index = 0; + // c. Let numEntries be the number of elements of entries. + let numEntries = entries.length; + // d. Repeat, while index < numEntries, + while (index < numEntries) { + // i. Let e be the Record { [[Key]], [[Value]] } that is the value of entries[index]. + const e = entries[index]; + // ii. Set index to index + 1. + index += 1; + // iii. If e.[[Key]] is not empty, then + if (e.Key !== undefined) { + let result; + // 1. If kind is key, let result be e.[[Key]]. + if (kind === 'key') { + result = e.Key; + } else if (kind === 'value') { // 2. Else if kind is value, let result be e.[[Value]]. + result = e.Value; + } else { // 3. Else, + // a. Assert: kind is key+value. + Assert(kind === 'key+value'); + // b. Let result be ! CreateArrayFromList(« e.[[Key]], e.[[Value]] »). + result = X(CreateArrayFromList([e.Key, e.Value!])); + } + // 4. Perform ? Yield(result). + Q(yield* Yield(result!)); + } + // iv. Set numEntries to the number of elements of entries. + numEntries = entries.length; + } + // NON-SPEC + generator.HostCapturedValues = undefined; + // e. Return undefined. + return Value.undefined; + }; + // 3. Return ! CreateIteratorFromClosure(closure, "%MapIteratorPrototype%", %MapIteratorPrototype%). + const generator = X(CreateIteratorFromClosure(closure, Value('%MapIteratorPrototype%'), surroundingAgent.intrinsic('%MapIteratorPrototype%'), ['HostCapturedValues'], [map])); + return generator; +} + +/** https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next */ +function* MapIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Return ? GeneratorResume(this value, empty, "%MapIteratorPrototype%") + return Q(yield* GeneratorResume(thisValue, undefined, Value('%MapIteratorPrototype%'))); +} + +export function bootstrapMapIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', MapIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'Map Iterator'); + + realmRec.Intrinsics['%MapIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/MapPrototype.mts b/src/intrinsics/MapPrototype.mts new file mode 100644 index 0000000..f8bc659 --- /dev/null +++ b/src/intrinsics/MapPrototype.mts @@ -0,0 +1,295 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + NumberValue, + Value, + wellKnownSymbols, +} from '../value.mts'; +import { Q, X } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { CreateMapIterator } from './MapIteratorPrototype.mts'; +import type { MapObject } from './Map.mts'; +import { + Call, + CanonicalizeKeyedCollectionKey, + F, + IsCallable, + RequireInternalSlot, + SameValue, SameValueZero, R, +} from '#self'; +import type { + Arguments, Descriptor, ValueEvaluator, FunctionCallContext, Realm, + ValueCompletion, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-map.prototype.clear */ +function MapProto_clear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 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 + if (entries.length) { + Q(surroundingAgent.debugger_tryTouchDuringPreview(M)); + } + 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; +} + +/** https://tc39.es/ecma262/#sec-map.prototype.delete */ +function MapProto_delete([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 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) { + Q(surroundingAgent.debugger_tryTouchDuringPreview(M)); + // 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; +} + +/** https://tc39.es/ecma262/#sec-map.prototype.entries */ +function MapProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue; + // 2. Return ? CreateMapIterator(M, key+value); + return Q(CreateMapIterator(M, 'key+value')); +} + +/** https://tc39.es/ecma262/#sec-map.prototype.foreach */ +function* MapProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!IsCallable(callbackfn)) { + 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(yield* Call(callbackfn, thisArg, [e.Value!, e.Key, M])); + } + } + // 6. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-map.prototype.get */ +function MapProto_get([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 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; +} + +/** https://tc39.es/proposal-upsert/#sec-map.prototype.getOrInsert */ +function MapProto_getOrInsert([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. Set key to CanonicalizeKeyedCollectionKey(key). + key = CanonicalizeKeyedCollectionKey(key); + // 4. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do + const entries = M.MapData; + 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!; + } + } + // 5. Let p be the Record { [[Key]]: key, [[Value]]: value }. + const p = { Key: key, Value: value }; + // 6. Append p to M.[[MapData]]. + entries.push(p); + // 7. Return value. + return value; +} + +/** https://tc39.es/proposal-upsert/#sec-map.prototype.getOrInsertComputed */ +function* MapProto_getOrInsertComputed([key = Value.undefined, callbackfn = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 2. Perform ? RequireInternalSlot(M, [[MapData]]). + Q(RequireInternalSlot(M, 'MapData')); + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 4. Set key to CanonicalizeKeyedCollectionKey(key). + key = CanonicalizeKeyedCollectionKey(key); + // 5. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do + const entries = M.MapData; + 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 && SameValueZero(p.Key, key) === Value.true) { + return p.Value!; + } + } + // 6. Let value be ? Call(callbackfn, undefined, « key »). + const value = Q(yield* Call(callbackfn, Value.undefined, [key])); + // 7. NOTE: The Map may have been modified during execution of callbackfn. + // 8. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], 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 && SameValueZero(p.Key, key) === Value.true) { + // i. Set p.[[Value]] to value. + p.Value = value; + // ii. Return value. + return value; + } + } + // 9. Let p be the Record { [[Key]]: key, [[Value]]: value }. + const p = { Key: key, Value: value }; + // 10. Append p to M.[[MapData]]. + entries.push(p); + // 11. Return value. + return value; +} + +/** https://tc39.es/ecma262/#sec-map.prototype.has */ +function MapProto_has([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 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; +} + +/** https://tc39.es/ecma262/#sec-map.prototype.keys */ +function MapProto_keys(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue; + // 2. Return ? CreateMapIterator(M, key). + return Q(CreateMapIterator(M, 'key')); +} + +/** https://tc39.es/ecma262/#sec-map.prototype.set */ +function MapProto_set([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(M)); + p.Value = value; + // ii. Return M. + return M; + } + } + // 5. If key is -0𝔽, set key to +0𝔽. + if (key instanceof NumberValue && Object.is(R(key), -0)) { + key = F(+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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(M)); + entries.push(p); + // 8. Return M. + return M; +} + +/** https://tc39.es/ecma262/#sec-get-map.prototype.size */ +function MapProto_sizeGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as MapObject; + // 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 F(count); +} + +/** https://tc39.es/ecma262/#sec-map.prototype.values */ +function MapProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue; + // 2. Return ? CreateMapIterator(M, value). + return Q(CreateMapIterator(M, 'value')); +} + +export function bootstrapMapPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['clear', MapProto_clear, 0], + ['delete', MapProto_delete, 1], + ['entries', MapProto_entries, 0], + ['forEach', MapProto_forEach, 1], + ['get', MapProto_get, 1], + ['getOrInsert', MapProto_getOrInsert, 2], + ['getOrInsertComputed', MapProto_getOrInsertComputed, 2], + ['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(Value('entries'))); + X(proto.DefineOwnProperty(wellKnownSymbols.iterator, entriesFunc as Descriptor)); + + realmRec.Intrinsics['%Map.prototype%'] = proto; +} diff --git a/src/intrinsics/Math.mts b/src/intrinsics/Math.mts new file mode 100644 index 0000000..f6db9ff --- /dev/null +++ b/src/intrinsics/Math.mts @@ -0,0 +1,288 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Descriptor, + Value, + NumberValue, + type Arguments, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + CreateBuiltinFunction, + ToNumber, + F, R, + Realm, + RequireObjectCoercible, + GetIterator, + IteratorStepValue, + IteratorClose, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-math.abs */ +function* Math_abs([x = Value.undefined]: Arguments): ValueEvaluator { + const n = Q(yield* ToNumber(x)); + if (n.isNaN()) { + return n; + } else if (Object.is(R(n), -0)) { + return F(+0); + } else if (n.isInfinity()) { + return F(Infinity); + } + + if (R(n) < 0) { + return F(-R(n)); + } + return n; +} + +/** https://tc39.es/ecma262/#sec-math.acos */ +function* Math_acos([x = Value.undefined]: Arguments): ValueEvaluator { + const n = Q(yield* ToNumber(x)); + if (n.isNaN()) { + return n; + } else if (R(n) > 1) { + return F(NaN); + } else if (R(n) < -1) { + return F(NaN); + } else if (R(n) === 1) { + return F(+0); + } + + return F(Math.acos(R(n))); +} + +/** https://tc39.es/ecma262/#sec-math.pow */ +function* Math_pow([base = Value.undefined, exponent = Value.undefined]: Arguments): ValueEvaluator { + // 1. Set base to ? ToNumber(base). + base = Q(yield* ToNumber(base)); + // 2. Set exponent to ? ToNumber(exponent). + exponent = Q(yield* ToNumber(exponent)); + // 3. Return ! Number::exponentiate(base, exponent). + return X(NumberValue.exponentiate(base, exponent)); +} + +/** @param {bigint} h */ +function fmix64(h: bigint) { + 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); +/** https://tc39.es/ecma262/#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 F(result); +} + +/** https://tc39.es/ecma262/#sec-math.sumprecise */ +function* Math_sumPrecise([items = Value.undefined]: Arguments): ValueEvaluator { + Q(RequireObjectCoercible(items)); + const iteratorRecord = Q(yield* GetIterator(items, 'sync')); + let state: 'minus-zero' | 'not-a-number' | 'minus-infinity' | 'plus-infinity' | 'finite' = 'minus-zero'; + const sums: number[] = []; + let count = 0; + let next: 'not-started' | 'done' | Value = 'not-started'; + while (next !== 'done') { + next = Q(yield* IteratorStepValue(iteratorRecord)); + if (next !== 'done') { + if (count >= 2 ** 53 - 1) { + const error = surroundingAgent.Throw('RangeError', 'OutOfRange', ''); + return Q(yield* IteratorClose(iteratorRecord, error)); + } + if (!(next instanceof NumberValue)) { + const error = surroundingAgent.Throw('TypeError', 'NotANumber', next); + return Q(yield* IteratorClose(iteratorRecord, error)); + } + const n = R(next); + if (state !== 'not-a-number') { + if (Number.isNaN(n)) { + state = 'not-a-number'; + } else if (n === Infinity) { + if (state === 'minus-infinity') { + state = 'not-a-number'; + } else { + state = 'plus-infinity'; + } + } else if (n === -Infinity) { + if (state === 'plus-infinity') { + state = 'not-a-number'; + } else { + state = 'minus-infinity'; + } + } else if (!Object.is(n, -0) && (state === 'minus-zero' || state === 'finite')) { + state = 'finite'; + sums.push(n); + } + } + count += 1; + } + } + if (state === 'not-a-number') { + return F(NaN); + } + if (state === 'plus-infinity') { + return F(Infinity); + } + if (state === 'minus-infinity') { + return F(-Infinity); + } + if (state === 'minus-zero') { + return F(-0); + } + return F(sum(sums)); + + function sum(items: number[]) { + if ('sumPrecise' in Math) { + // @ts-expect-error + return Math.sumPrecise(items); + } + const fractional_parts: number[] = []; + let whole_part_sum = 0n; + items.forEach((n) => { + const whole_num = Math.trunc(n); + fractional_parts.push(n - whole_num); + whole_part_sum += BigInt(whole_num); + }); + const fractional_parts_as_hex = fractional_parts.map((n) => n.toString(32)); + + const fractional: number[] = []; + for (const fractional_str of fractional_parts_as_hex) { + const neg = fractional_str[0] === '-'; + const prefix = neg ? 3 : 2; // -0.xxx or 0.xxx + for (let index = prefix; index < fractional_str.length; index += 1) { + fractional[index - prefix] ??= 0; + if (neg) { + fractional[index - prefix] -= parseInt(fractional_str[index], 32); + } else { + fractional[index - prefix] += parseInt(fractional_str[index], 32); + } + } + } + for (let index = fractional.length - 1; index >= 0; index -= 1) { + const element = fractional[index]; + if (element >= 32) { + fractional[index] = element % 32; + fractional[index - 1] ??= 0; + fractional[index - 1] += Math.floor(element / 32); + } + if (element < 0) { + fractional[index] = 32 + element; + fractional[index - 1] ??= 0; + fractional[index - 1] -= 1; + } + } + const fractional_part = fractional.reduceRight((acc, digit, index) => acc + digit * 32 ** -(index + 1), 0); + if (fractional[-1]) { + whole_part_sum += BigInt(fractional[-1]); + } + return Number(whole_part_sum) + fractional_part; + } +} + +/** https://tc39.es/ecma262/#sec-math-object */ +export function bootstrapMath(realmRec: Realm) { + /** https://tc39.es/ecma262/#sec-value-properties-of-the-math-object */ + const readonly = { Writable: Value.false, Configurable: Value.false }; + + // @@toStringTag is handled in the bootstrapPrototype() call. + const mathObj = bootstrapPrototype(realmRec, [ + ['E', F(2.718281828459045), undefined, readonly], + ['LN10', F(2.302585092994046), undefined, readonly], + ['LN2', F(0.6931471805599453), undefined, readonly], + ['LOG10E', F(0.4342944819032518), undefined, readonly], + ['LOG2E', F(1.4426950408889634), undefined, readonly], + ['PI', F(3.141592653589793), undefined, readonly], + ['SQRT1_2', F(0.7071067811865476), undefined, readonly], + ['SQRT2', F(1.4142135623730951), undefined, readonly], + ['abs', Math_abs, 1], + ['acos', Math_acos, 1], + ['pow', Math_pow, 2], + ['random', Math_random, 0], + ['sumPrecise', Math_sumPrecise, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'Math'); + + /** https://tc39.es/ecma262/#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], + ] as const).forEach(([name, length]) => { + // TODO(18): Math + /** https://tc39.es/ecma262/#sec-function-properties-of-the-math-object */ + const method = function* method(args: Arguments): ValueEvaluator { + const nextArgs: number[] = []; + for (let i = 0; i < args.length; i += 1) { + nextArgs[i] = R(Q(yield* ToNumber(args[i]!))); + } + // we're calling host Math functions here. + return F((Math[name] as (...args: unknown[]) => number)(...nextArgs)); + }; + const func = CreateBuiltinFunction(method, length, Value(name), [], realmRec); + X(mathObj.DefineOwnProperty(Value(name), Descriptor({ + Value: func, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + }); + + realmRec.Intrinsics['%Math%'] = mathObj; +} diff --git a/src/intrinsics/NativeError.mts b/src/intrinsics/NativeError.mts new file mode 100644 index 0000000..c214602 --- /dev/null +++ b/src/intrinsics/NativeError.mts @@ -0,0 +1,88 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + Descriptor, + UndefinedValue, + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { captureStack, callSiteToErrorString } from '../helpers.mts'; +import { bootstrapConstructor, bootstrapPrototype } from './bootstrap.mts'; +import type { ErrorObject } from './Error.mts'; +import { + DefinePropertyOrThrow, + OrdinaryCreateFromConstructor, + InstallErrorCause, + ToString, + Realm, + type FunctionObject, +} from '#self'; + +const nativeErrorNames = [ + 'EvalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError', +] as const; +export type NativeErrorNames = typeof nativeErrorNames[number]; +export function bootstrapNativeError(realmRec: Realm) { + for (const name of nativeErrorNames) { + const proto = bootstrapPrototype(realmRec, [ + ['name', Value(name)], + ['message', Value('')], + ], realmRec.Intrinsics['%Error.prototype%']); + + /** https://tc39.es/ecma262/#sec-nativeerror */ + const Constructor = function* Constructor([message = Value.undefined, options = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let newTarget; + if (NewTarget instanceof UndefinedValue) { + newTarget = surroundingAgent.activeFunctionObject; + } else { + newTarget = NewTarget; + } + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + const O = Q(yield* OrdinaryCreateFromConstructor(newTarget as FunctionObject, `%${name}.prototype%`, [ + 'ErrorData', + 'HostDefinedErrorStack', + ])) as ErrorObject; + // 3. If message is not undefined, then + if (message !== Value.undefined) { + // a. Let msg be ? ToString(message). + const msg = Q(yield* 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, Value('message'), msgDesc)); + } + // 4. Perform ? InstallErrorCause(O, options). + Q(yield* InstallErrorCause(O, options)); + // NON-SPEC + const S = captureStack(); + O.HostDefinedErrorStack = S.stack; + O.ErrorData = X(callSiteToErrorString(O, S.stack, S.nativeStack)); + // 5. 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/src/intrinsics/Number.mts b/src/intrinsics/Number.mts new file mode 100644 index 0000000..8839a37 --- /dev/null +++ b/src/intrinsics/Number.mts @@ -0,0 +1,135 @@ +import { + Descriptor, + NumberValue, + BigIntValue, + Value, + type Arguments, + type FunctionCallContext, + UndefinedValue, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + IsIntegralNumber, + OrdinaryCreateFromConstructor, + ToNumeric, + F, R, + Realm, + type OrdinaryObject, +} from '#self'; + +export interface NumberObject extends OrdinaryObject { + readonly NumberData: NumberValue; +} +export function isNumberObject(o: Value): o is NumberObject { + return 'NumberData' in o; +} + +/** https://tc39.es/ecma262/#sec-number-constructor-number-value */ +function* NumberConstructor([value]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + let n; + if (value !== undefined) { + const prim = Q(yield* ToNumeric(value)); + if (prim instanceof BigIntValue) { + n = F(Number(R(prim))); + } else { + n = prim; + } + } else { + n = F(+0); + } + if (NewTarget instanceof UndefinedValue) { + return n; + } + const O = (yield* OrdinaryCreateFromConstructor(NewTarget, '%Number.prototype%', ['NumberData'])) as Mutable; + O.NumberData = n; + return O; +} + +/** https://tc39.es/ecma262/#sec-number.isfinite */ +function Number_isFinite([number = Value.undefined]: Arguments) { + if (!(number instanceof NumberValue)) { + return Value.false; + } + + if (number.isNaN() || number.isInfinity()) { + return Value.false; + } + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-number.isinteger */ +function Number_isInteger([number = Value.undefined]: Arguments) { + return X(IsIntegralNumber(number)); +} + +/** https://tc39.es/ecma262/#sec-number.isnan */ +function Number_isNaN([number = Value.undefined]: Arguments) { + if (!(number instanceof NumberValue)) { + return Value.false; + } + + if (number.isNaN()) { + return Value.true; + } + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-number.issafeinteger */ +function Number_isSafeInteger([number = Value.undefined]: Arguments) { + if (!(number instanceof NumberValue)) { + return Value.false; + } + + if (X(IsIntegralNumber(number)) === Value.true) { + if (Math.abs(R(number)) <= (2 ** 53) - 1) { + return Value.true; + } + } + + return Value.false; +} + +export function bootstrapNumber(realmRec: Realm) { + const override = { + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }; + const numberConstructor = bootstrapConstructor(realmRec, NumberConstructor, 'Number', 1, realmRec.Intrinsics['%Number.prototype%'], [ + ['EPSILON', F(Number.EPSILON), undefined, override], + ['MAX_SAFE_INTEGER', F(Number.MAX_SAFE_INTEGER), undefined, override], + ['MAX_VALUE', F(Number.MAX_VALUE), undefined, override], + ['MIN_SAFE_INTEGER', F(Number.MIN_SAFE_INTEGER), undefined, override], + ['MIN_VALUE', F(Number.MIN_VALUE), undefined, override], + ['NaN', F(NaN), undefined, override], + ['NEGATIVE_INFINITY', F(-Infinity), undefined, override], + ['POSITIVE_INFINITY', F(+Infinity), undefined, override], + + ['isFinite', Number_isFinite, 1], + ['isInteger', Number_isInteger, 1], + ['isNaN', Number_isNaN, 1], + ['isSafeInteger', Number_isSafeInteger, 1], + ]); + + /** https://tc39.es/ecma262/#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(Value('parseFloat'), Descriptor({ + Value: realmRec.Intrinsics['%parseFloat%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + /** https://tc39.es/ecma262/#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(Value('parseInt'), Descriptor({ + Value: realmRec.Intrinsics['%parseInt%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%Number%'] = numberConstructor; +} diff --git a/src/intrinsics/NumberPrototype.mts b/src/intrinsics/NumberPrototype.mts new file mode 100644 index 0000000..d64785e --- /dev/null +++ b/src/intrinsics/NumberPrototype.mts @@ -0,0 +1,125 @@ +import { + ObjectValue, + Value, + NumberValue, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { NumberObject } from './Number.mts'; +import { + Assert, + ToIntegerOrInfinity, + ToString, + F, R, + Realm, +} from '#self'; + +function thisNumberValue(value: Value) { + if (value instanceof NumberValue) { + return value; + } + if (value instanceof ObjectValue && 'NumberData' in value) { + const n = value.NumberData; + Assert(n instanceof NumberValue); + return n; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Number', value); +} + +/** https://tc39.es/ecma262/#sec-number.prototype.toexponential */ +function* NumberProto_toExponential([fractionDigits = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const x = Q(thisNumberValue(thisValue)); + const f = Q(yield* ToIntegerOrInfinity(fractionDigits)); + Assert(fractionDigits !== Value.undefined || f === 0); + if (!x.isFinite()) { + return NumberValue.toString(x, 10); + } + if (f < 0 || f > 100) { + return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toExponential'); + } + return Value(R(x).toExponential(fractionDigits === Value.undefined ? undefined : f)); +} + +/** https://tc39.es/ecma262/#sec-number.prototype.tofixed */ +function* NumberProto_toFixed([fractionDigits = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const x = Q(thisNumberValue(thisValue)); + const f = Q(yield* ToIntegerOrInfinity(fractionDigits)); + 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, 10)); + } + return Value(R(x).toFixed(f)); +} + +/** https://tc39.es/ecma262/#sec-number.prototype.tolocalestring */ +function NumberProto_toLocaleString(_args: Arguments, context: FunctionCallContext): ValueEvaluator { + return NumberProto_toString([], context); +} + +/** https://tc39.es/ecma262/#sec-number.prototype.toprecision */ +function* NumberProto_toPrecision([precision = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const x = Q(thisNumberValue(thisValue)); + if (precision === Value.undefined) { + return X(ToString(x)); + } + const p = Q(yield* ToIntegerOrInfinity(precision)); + if (!x.isFinite()) { + return X(NumberValue.toString(x, 10)); + } + if (p < 1 || p > 100) { + return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toPrecision'); + } + return Value(R(x).toPrecision(p)); +} + +/** https://tc39.es/ecma262/#sec-number.prototype.tostring */ +function* NumberProto_toString([radix = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const x = Q(thisNumberValue(thisValue)); + let radixNumber; + if (radix === Value.undefined) { + radixNumber = 10; + } else { + radixNumber = Q(yield* ToIntegerOrInfinity(radix)); + } + if (radixNumber < 2 || radixNumber > 36) { + return surroundingAgent.Throw('RangeError', '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 Value(R(x).toString(radixNumber)); +} + +/** https://tc39.es/ecma262/#sec-number.prototype.valueof */ +function NumberProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + return Q(thisNumberValue(thisValue)); +} + +export function bootstrapNumberPrototype(realmRec: Realm) { + 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 as Mutable).NumberData = F(+0); + + realmRec.Intrinsics['%Number.prototype%'] = proto; +} diff --git a/src/intrinsics/Object.mts b/src/intrinsics/Object.mts new file mode 100644 index 0000000..7ce6415 --- /dev/null +++ b/src/intrinsics/Object.mts @@ -0,0 +1,465 @@ +import { + NullValue, + ObjectValue, + Value, + type Arguments, + type FunctionCallContext, + UndefinedValue, + type PropertyKeyValue, + Descriptor, + SymbolValue, + JSStringValue, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { AddEntriesFromIterable } from './Map.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Assert, + CreateArrayFromList, + CreateDataProperty, + DefinePropertyOrThrow, + CreateDataPropertyOrThrow, + EnumerableOwnProperties, + FromPropertyDescriptor, + Get, + HasOwnProperty, + IsExtensible, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + RequireObjectCoercible, + SameValue, + Set, + SetIntegrityLevel, + TestIntegrityLevel, + ToObject, + ToPropertyDescriptor, + ToPropertyKey, + CreateBuiltinFunction, + Realm, + type FunctionObject, + GroupBy, + type KeyedGroupRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-object-value */ +function* ObjectConstructor([value = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 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 yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%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)); +} + +/** https://tc39.es/ecma262/#sec-object.assign */ +function* Object_assign([target = Value.undefined, ...sources]: Arguments): ValueEvaluator { + // 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 as Arguments).values()) { + // 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(yield* 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(yield* from.GetOwnProperty(nextKey)); + // 2. If desc is not undefined and desc.[[Enumerable]] is true, then + if (!(desc instanceof UndefinedValue) && desc.Enumerable === Value.true) { + // a. Let propValue be ? Get(from, nextKey). + const propValue = Q(yield* Get(from, nextKey)); + // b. Perform ? Set(to, nextKey, propValue, true). + Q(yield* Set(to, nextKey, propValue, Value.true)); + } + } + } + } + // 5. Return to. + return to; +} + +/** https://tc39.es/ecma262/#sec-object.create */ +function* Object_create([O = Value.undefined, Properties = Value.undefined]: Arguments) { + // 1. If Type(O) is neither Object nor Null, throw a TypeError exception. + if (!(O instanceof ObjectValue) && !(O instanceof NullValue)) { + 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(yield* ObjectDefineProperties(obj, Properties)); + } + // 4. Return obj. + return obj; +} + +/** https://tc39.es/ecma262/#sec-object.defineproperties */ +function* Object_defineProperties([O = Value.undefined, Properties = Value.undefined]: Arguments): ValueEvaluator { + // 1. Return ? ObjectDefineProperties(O, Properties). + return Q(yield* ObjectDefineProperties(O, Properties)); +} + +/** https://tc39.es/ecma262/#sec-objectdefineproperties ObjectDefineProperties */ +function* ObjectDefineProperties(O: Value, Properties: Value) { + // 1. If Type(O) is not Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + 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(yield* props.OwnPropertyKeys()); + // 4. Let descriptors be a new empty List. + const descriptors: [PropertyKeyValue, Descriptor][] = []; + // 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(yield* props.GetOwnProperty(nextKey)); + // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then + if (!(propDesc instanceof UndefinedValue) && propDesc.Enumerable === Value.true) { + // i. Let descObj be ? Get(props, nextKey). + const descObj = Q(yield* Get(props, nextKey)); + // ii. Let desc be ? ToPropertyDescriptor(descObj). + const desc = Q(yield* 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(yield* DefinePropertyOrThrow(O, P, desc)); + } + // 7. Return O. + return O; +} + +/** https://tc39.es/ecma262/#sec-object.defineproperty */ +function* Object_defineProperty([O = Value.undefined, P = Value.undefined, Attributes = Value.undefined]: Arguments) { + // 1. If Type(O) is not Object, throw a TypeError exception. + if (!(O instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', O); + } + // 2. Let key be ? ToPropertyKey(P). + const key = Q(yield* ToPropertyKey(P)); + // 3. Let desc be ? ToPropertyDescriptor(Attributes). + const desc = Q(yield* ToPropertyDescriptor(Attributes)); + // 4. Perform ? DefinePropertyOrThrow(O, key, desc). + Q(yield* DefinePropertyOrThrow(O, key, desc)); + // 5. Return O. + return O; +} + +/** https://tc39.es/ecma262/#sec-object.entries */ +function* Object_entries([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value). + const nameList = Q(yield* EnumerableOwnProperties(obj, 'key+value')); + // 3. Return CreateArrayFromList(nameList). + return CreateArrayFromList(nameList); +} + +/** https://tc39.es/ecma262/#sec-object.freeze */ +function* Object_freeze([O = Value.undefined]: Arguments) { + // 1. If Type(O) is not Object, return O. + if (!(O instanceof ObjectValue)) { + return O; + } + // 2. Let status be ? SetIntegrityLevel(O, frozen). + const status = Q(yield* 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; +} + +/** https://tc39.es/ecma262/#sec-object.fromentries */ +function* Object_fromEntries([iterable = Value.undefined]: Arguments): ValueEvaluator { + // 1. Perform ? RequireObjectCoercible(iterable). + Q(RequireObjectCoercible(iterable)); + // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). + const obj = X(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 closure be a new Abstract Closure with parameters (key, value) that captures obj and performs the following steps when called: + function* closure([key = Value.undefined, value = Value.undefined]: Arguments): ValueEvaluator { + // a. Let propertyKey be ? ToPropertyKey(key). + const propertyKey = Q(yield* ToPropertyKey(key)); + // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value). + X(CreateDataPropertyOrThrow(obj, propertyKey, value)); + // c. Return undefined. + return Value.undefined; + } + // 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »). + const adder = X(CreateBuiltinFunction(closure, 2, Value(''), [])); + // 6. Return ? AddEntriesFromIterable(obj, iterable, adder). + return Q(yield* AddEntriesFromIterable(obj, iterable, adder)); +} + +/** https://tc39.es/ecma262/#sec-object.getownpropertydescriptor */ +function* Object_getOwnPropertyDescriptor([O = Value.undefined, P = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let key be ? ToPropertyKey(P). + const key = Q(yield* ToPropertyKey(P)); + // 3. Let desc be ? obj.[[GetOwnProperty]](key). + const desc = Q(yield* obj.GetOwnProperty(key)); + // 4. Return FromPropertyDescriptor(desc). + return FromPropertyDescriptor(desc); +} + +/** https://tc39.es/ecma262/#sec-object.getownpropertydescriptors */ +function* Object_getOwnPropertyDescriptors([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let ownKeys be ? obj.[[OwnPropertyKeys]](). + const ownKeys = Q(yield* 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(yield* 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; +} + +/** https://tc39.es/ecma262/#sec-getownpropertykeys */ +function* GetOwnPropertyKeys(O: Value, type: 'String' | 'Symbol'): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let keys be ? obj.[[OwnPropertyKeys]](). + const keys = Q(yield* obj.OwnPropertyKeys()); + // 3. Let nameList be a new empty List. + const nameList: PropertyKeyValue[] = []; + // 4. For each element nextKey of keys in List order, do + keys.forEach((nextKey) => { + // a. If nextKey is a Symbol and type is symbol, or if nextKey is a String and type is string, then + if ((type === 'Symbol' && nextKey instanceof SymbolValue) || (type === 'String' && nextKey instanceof JSStringValue)) { + // i. Append nextKey as the last element of nameList. + nameList.push(nextKey); + } + }); + return CreateArrayFromList(nameList); +} + +/** https://tc39.es/ecma262/#sec-object.getownpropertynames */ +function* Object_getOwnPropertyNames([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Return ? GetOwnPropertyKeys(O, string). + return Q(yield* GetOwnPropertyKeys(O, 'String')); +} + +/** https://tc39.es/ecma262/#sec-object.getownpropertysymbols */ +function* Object_getOwnPropertySymbols([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Return ? GetOwnPropertyKeys(O, symbol). + return Q(yield* GetOwnPropertyKeys(O, 'Symbol')); +} + +/** https://tc39.es/ecma262/#sec-object.getprototypeof */ +function* Object_getPrototypeOf([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Return ? obj.[[GetPrototypeOf]](). + return Q(yield* obj.GetPrototypeOf()); +} + +/** https://tc39.es/ecma262/#sec-object.groupby */ +function* Object_groupBy([items = Value.undefined, callback = Value.undefined]: Arguments): ValueEvaluator { + /* + 1. Let groups be ? GroupBy(items, callback, property). + 2. Let obj be OrdinaryObjectCreate(null). + 3. For each Record { [[Key]], [[Elements]] } g of groups, do + a. Let elements be CreateArrayFromList(g.[[Elements]]). + b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements). + 4. Return obj. + */ + const groups: KeyedGroupRecord[] = Q(yield* GroupBy(items, callback, 'property')); + const obj = OrdinaryObjectCreate(Value.null); + for (const g of groups) { + const elements = CreateArrayFromList(g.Elements); + X(CreateDataPropertyOrThrow(obj, g.Key, elements)); + } + return obj; +} + +/** https://tc39.es/ecma262/#sec-object.hasown */ +function* Object_hasOwn([O = Value.undefined, P = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let O be ? ToObject(this value). + const key = Q(yield* ToPropertyKey(P)); + // 3. Return ? HasOwnProperty(obj, key). + return yield* HasOwnProperty(obj, key); +} + +/** https://tc39.es/ecma262/#sec-object.is */ +function Object_is([value1 = Value.undefined, value2 = Value.undefined]: Arguments) { + // 1. Return SameValue(value1, value2). + return SameValue(value1, value2); +} + +/** https://tc39.es/ecma262/#sec-object.isextensible */ +function* Object_isExtensible([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. If Type(O) is not Object, return false. + if (!(O instanceof ObjectValue)) { + return Value.false; + } + // 2. Return ? IsExtensible(O). + return Q(yield* IsExtensible(O)); +} + +/** https://tc39.es/ecma262/#sec-object.isfrozen */ +function* Object_isFrozen([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. If Type(O) is not Object, return true. + if (!(O instanceof ObjectValue)) { + return Value.true; + } + // 2. Return ? TestIntegrityLevel(O, frozen). + return Q(yield* TestIntegrityLevel(O, 'frozen')); +} + +/** https://tc39.es/ecma262/#sec-object.issealed */ +function* Object_isSealed([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. If Type(O) is not Object, return true. + if (!(O instanceof ObjectValue)) { + return Value.true; + } + // 2. Return ? TestIntegrityLevel(O, sealed). + return Q(yield* TestIntegrityLevel(O, 'sealed')); +} + +/** https://tc39.es/ecma262/#sec-object.keys */ +function* Object_keys([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key). + const nameList = Q(yield* EnumerableOwnProperties(obj, 'key')); + // 3. Return CreateArrayFromList(nameList). + return CreateArrayFromList(nameList); +} + +/** https://tc39.es/ecma262/#sec-object.preventextensions */ +function* Object_preventExtensions([O = Value.undefined]: Arguments) { + // 1. If Type(O) is not Object, return O. + if (!(O instanceof ObjectValue)) { + return O; + } + // 2. Let status be ? O.[[PreventExtensions]](). + const status = Q(yield* 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; +} + +/** https://tc39.es/ecma262/#sec-object.seal */ +function* Object_seal([O = Value.undefined]: Arguments) { + // 1. If Type(O) is not Object, return O. + if (!(O instanceof ObjectValue)) { + return O; + } + // 2. Let status be ? SetIntegrityLevel(O, sealed). + const status = Q(yield* 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; +} + +/** https://tc39.es/ecma262/#sec-object.setprototypeof */ +function* Object_setPrototypeOf([O = Value.undefined, proto = Value.undefined]: Arguments) { + // 1. Perform ? RequireObjectCoercible(O). + Q(RequireObjectCoercible(O)); + // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. + if (!(proto instanceof ObjectValue) && !(proto instanceof NullValue)) { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // 3. If Type(O) is not Object, return O. + if (!(O instanceof ObjectValue)) { + return O; + } + // 4. Let status be ? O.[[SetPrototypeOf]](proto). + const status = Q(yield* 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; +} + +/** https://tc39.es/ecma262/#sec-object.values */ +function* Object_values([O = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let obj be ? ToObject(O). + const obj = Q(ToObject(O)); + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, value). + const nameList = Q(yield* EnumerableOwnProperties(obj, 'value')); + // 3. Return CreateArrayFromList(nameList). + return CreateArrayFromList(nameList); +} + +export function bootstrapObject(realmRec: Realm) { + 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], + ['groupBy', Object_groupBy, 2], + ['hasOwn', Object_hasOwn, 2], + ['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/src/intrinsics/ObjectPrototype.mts b/src/intrinsics/ObjectPrototype.mts new file mode 100644 index 0000000..86f1a56 --- /dev/null +++ b/src/intrinsics/ObjectPrototype.mts @@ -0,0 +1,333 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + NullValue, + JSStringValue, + UndefinedValue, + ObjectValue, + Value, + Descriptor, + wellKnownSymbols, + type FunctionCallContext, + type Arguments, + type ObjectInternalMethods, +} from '../value.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { assignProps } from './bootstrap.mts'; +import { + DefinePropertyOrThrow, + Get, + HasOwnProperty, + Invoke, + IsAccessorDescriptor, + IsArray, + IsCallable, + MakeBasicObject, + Realm, + RequireObjectCoercible, + SameValue, + SetImmutablePrototype, + ToObject, + ToPropertyKey, + type BuiltinFunctionObject, + type FunctionObject, + type ImmutablePrototypeObject, + type OrdinaryObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-object.prototype.hasownproperty */ +function* ObjectProto_hasOwnProperty([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let P be ? ToPropertyKey(V). + const P = Q(yield* ToPropertyKey(V)); + // 2. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 3. Return ? HasOwnProperty(O, P). + return yield* HasOwnProperty(O, P); +} + +/** https://tc39.es/ecma262/#sec-object.prototype.isprototypeof */ +function* ObjectProto_isPrototypeOf([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. If Type(V) is not Object, return false. + if (!(V instanceof ObjectValue)) { + 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(yield* (V as ObjectValue).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; + } + } +} + +/** https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable */ +function* ObjectProto_propertyIsEnumerable([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let P be ? ToPropertyKey(V). + const P = Q(yield* ToPropertyKey(V)); + // 2. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 3. Let desc be ? O.[[GetOwnProperty]](P). + const desc = Q(yield* O.GetOwnProperty(P)); + // 4. If desc is undefined, return false. + if (desc instanceof UndefinedValue) { + return Value.false; + } + // 5. Return desc.[[Enumerable]]. + return desc.Enumerable!; +} + +/** https://tc39.es/ecma262/#sec-object.prototype.tolocalestring */ +function* ObjectProto_toLocaleString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + // 2. Return ? Invoke(O, "toString"). + return Q(yield* Invoke(O, Value('toString'))); +} + +/** https://tc39.es/ecma262/#sec-object.prototype.tostring */ +function* ObjectProto_toString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. If the this value is undefined, return "[object Undefined]". + if (thisValue === Value.undefined) { + return Value('[object Undefined]'); + } + // 2. If the this value is null, return "[object Null]". + if (thisValue === Value.null) { + return 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). + const tag = Q(yield* Get(O, wellKnownSymbols.toStringTag)); + let tagStr; + // 16. If Type(tag) is not String, set tag to builtinTag. + if (!(tag instanceof JSStringValue)) { + tagStr = builtinTag; + } else { + tagStr = tag.stringValue(); + } + // 17. Return the string-concatenation of "[object ", tag, and "]". + return Value(`[object ${tagStr}]`); +} + +/** https://tc39.es/ecma262/#sec-object.prototype.valueof */ +function ObjectProto_valueOf(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Return ? ToObject(this value). + return Q(ToObject(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-object.prototype.__defineGetter__ */ +function* ObjectProto__defineGetter__([P = Value.undefined, getter = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 2. If IsCallable(getter) is false, throw a TypeError exception. + if (!IsCallable(getter)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', getter); + } + // 3. Let desc be PropertyDescriptor { [[Get]]: getter, [[Enumerable]]: true, [[Configurable]]: true }. + const desc = Descriptor({ + Get: getter, + Enumerable: Value.true, + Configurable: Value.true, + }); + // 4. Let key be ? ToPropertyKey(P). + const key = Q(yield* ToPropertyKey(P)); + // 5. Perform ? DefinePropertyOrThrow(O, key, desc). + Q(yield* DefinePropertyOrThrow(O, key, desc)); + // 6. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-object.prototype.__defineSetter__ */ +function* ObjectProto__defineSetter__([P = Value.undefined, setter = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 2. If IsCallable(setter) is false, throw a TypeError exception. + if (!IsCallable(setter)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', setter); + } + // 3. Let desc be PropertyDescriptor { [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: true }. + const desc = Descriptor({ + Set: setter, + Enumerable: Value.true, + Configurable: Value.true, + }); + // 4. Let key be ? ToPropertyKey(P). + const key = Q(yield* ToPropertyKey(P)); + // 5. Perform ? DefinePropertyOrThrow(O, key, desc). + Q(yield* DefinePropertyOrThrow(O, key, desc)); + // 6. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-object.prototype.__lookupGetter__ */ +function* ObjectProto__lookupGetter__([P = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be ? ToObject(this value). + let O: NullValue | ObjectValue = Q(ToObject(thisValue)); + // 2. Let key be ? ToPropertyKey(P). + const key = Q(yield* ToPropertyKey(P)); + // 3. Repeat, + while (true) { + __ts_cast__(O); + // a. Let desc be ? O.[[GetOwnProperty]](key). + const desc = Q(yield* O.GetOwnProperty(key)); + // b. If desc is not undefined, then + if (!(desc instanceof UndefinedValue)) { + // i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]]. + if (IsAccessorDescriptor(desc)) { + return desc.Get; + } + // ii. Return undefined. + return Value.undefined; + } + // c. Set O to ? O.[[GetPrototypeOf]](). + O = Q(yield* O.GetPrototypeOf()); + // d. If O is null, return undefined. + if (O === Value.null) { + return Value.undefined; + } + } +} + +/** https://tc39.es/ecma262/#sec-object.prototype.__lookupSetter__ */ +function* ObjectProto__lookupSetter__([P = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be ? ToObject(this value). + let O: NullValue | ObjectValue = Q(ToObject(thisValue)); + // 2. Let key be ? ToPropertyKey(P). + const key = Q(yield* ToPropertyKey(P)); + // 3. Repeat, + while (true) { + __ts_cast__(O); + // a. Let desc be ? O.[[GetOwnProperty]](key). + const desc = Q(yield* O.GetOwnProperty(key)); + // b. If desc is not undefined, then + if (!(desc instanceof UndefinedValue)) { + // i. If IsAccessorDescriptor(desc) is true, return desc.[[Set]]. + if (IsAccessorDescriptor(desc)) { + return desc.Set; + } + // ii. Return undefined. + return Value.undefined; + } + // c. Set O to ? O.[[GetPrototypeOf]](). + O = Q(yield* O.GetPrototypeOf()); + // d. If O is null, return undefined. + if (O === Value.null) { + return Value.undefined; + } + } +} + +/** https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ */ +function* ObjectProto__proto__Get(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be ? ToObject(this value). + const O = Q(ToObject(thisValue)); + // 2. Return ? O.[[GetPrototypeOf]](). + return Q(yield* O.GetPrototypeOf()); +} + +/** https://tc39.es/ecma262/#sec-set-object.prototype.__proto__ */ +function* ObjectProto__proto__Set([proto = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the *this* value. + // 2. Perform ? RequireObjectCoercible(this value). + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. If Type(proto) is neither Object nor Null, return undefined. + if (!(proto instanceof ObjectValue) && !(proto instanceof NullValue)) { + return Value.undefined; + } + // 3. If Type(O) is not Object, return undefined. + if (!(O instanceof ObjectValue)) { + return Value.undefined; + } + // 4. Let status be ? O.[[SetPrototypeOf]](proto). + const status = Q(yield* O.SetPrototypeOf(proto)); + // 5. If status is false, throw a TypeError exception. + if (status === Value.false) { + return surroundingAgent.Throw('TypeError', 'ObjectSetPrototype'); + } + // 6. Return undefined. + return Value.undefined; +} + +const InternalMethods = { + /** https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-immutable-prototype-exotic-objects-setprototypeof-v */ + * SetPrototypeOf(V) { + // 1. Return ? SetImmutablePrototype(O, V). + return Q(yield* SetImmutablePrototype(this, V)); + }, +} satisfies Partial>; + +/** https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-properties-of-the-object-prototype-object */ +export function makeObjectPrototype(realmRec: Realm) { + // The Object prototype object: + const proto = MakeBasicObject(['Prototype', 'Extensible']) as Mutable; + + // * has an [[Extensible]] internal slot whose value is true. + proto.Extensible = Value.true; + + // * has a [[Prototype]] internal slot whose value is null. + proto.Prototype = Value.null; + + // * has the internal methods defined for ordinary objects, except for the [[SetPrototypeOf]] method, which is as defined in 10.4.7.1. + // (Thus, it is an immutable prototype exotic object.) + proto.SetPrototypeOf = InternalMethods.SetPrototypeOf; + + // * is %Object.prototype%. + realmRec.Intrinsics['%Object.prototype%'] = proto; +} + +export function bootstrapObjectPrototype(realmRec: Realm) { + 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], + ['__defineGetter__', ObjectProto__defineGetter__, 2], + ['__defineSetter__', ObjectProto__defineSetter__, 2], + ['__lookupGetter__', ObjectProto__lookupGetter__, 1], + ['__lookupSetter__', ObjectProto__lookupSetter__, 1], + ['__proto__', [ObjectProto__proto__Get, ObjectProto__proto__Set]], + ]); + + realmRec.Intrinsics['%Object.prototype.toString%'] = X(Get(proto, Value('toString'))) as BuiltinFunctionObject; + realmRec.Intrinsics['%Object.prototype.valueOf%'] = X(Get(proto, Value('valueOf'))) as FunctionObject; +} diff --git a/src/intrinsics/Promise.mts b/src/intrinsics/Promise.mts new file mode 100644 index 0000000..edbc03c --- /dev/null +++ b/src/intrinsics/Promise.mts @@ -0,0 +1,604 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + BooleanValue, + Descriptor, + ObjectValue, + UndefinedValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + AbruptCompletion, + IfAbruptRejectPromise, + EnsureCompletion, + Q, X, + type ValueEvaluator, + type ValueCompletion, +} from '../completion.mts'; +import { __ts_cast__, type Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Assert, + Call, + CreateArrayFromList, + CreateBuiltinFunction, + CreateDataProperty, + CreateDataPropertyOrThrow, + CreateResolvingFunctions, + DefinePropertyOrThrow, + Get, + GetIterator, + Invoke, + IsCallable, + IsConstructor, + IteratorClose, + NewPromiseCapability, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + PromiseCapabilityRecord, + PromiseResolve, + PromiseReactionRecord, + type FunctionObject, + Realm, + type IteratorRecord, + type OrdinaryObject, + type PromiseAllResolveElementFunctionObject, + type PromiseAllRejectElementFunctionObject, + IteratorStepValue, +} from '#self'; + +/** https://tc39.es/ecma262/#table-internal-slots-of-promise-instances */ +export interface PromiseObject extends OrdinaryObject { + PromiseState: 'pending' | 'fulfilled' | 'rejected'; + PromiseResult: Value | undefined; + PromiseFulfillReactions: undefined | PromiseReactionRecord[]; + PromiseRejectReactions: undefined | PromiseReactionRecord[]; + PromiseIsHandled: BooleanValue; +} + +export function isPromiseObject(value: Value): value is PromiseObject { + return 'PromiseState' in value; +} + +/** https://tc39.es/ecma262/#sec-promise-executor */ +function* PromiseConstructor(this: FunctionObject, [executor = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. If IsCallable(executor) is false, throw a TypeError exception. + if (!IsCallable(executor)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', executor); + } + // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »). + const promise = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Promise.prototype%', [ + 'PromiseState', + 'PromiseResult', + 'PromiseFulfillReactions', + 'PromiseRejectReactions', + 'PromiseIsHandled', + ])) as Mutable; + // 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 = yield* 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(yield* Call(resolvingFunctions.Reject, Value.undefined, [completion.Value])); + } + // 11. Return promise. + return promise; +} + +/** https://tc39.es/ecma262/#sec-getpromiseresolve */ +function* GetPromiseResolve(promiseConstructor: FunctionObject) { + // 1. Assert: IsConstructor(promiseConstructor) is true. + Assert(IsConstructor(promiseConstructor)); + // 2. Let promiseResolve be ? Get(promiseConstructor, "resolve"). + const promiseResolve = Q(yield* Get(promiseConstructor, Value('resolve'))); + // 3. If IsCallable(promiseResolve) is false, throw a TypeError exception. + if (!IsCallable(promiseResolve)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', promiseResolve); + } + // 4. Return promiseResolve. + return promiseResolve; +} + +/** https://tc39.es/ecma262/#sec-performpromiseall */ +export function* PerformPromiseAll(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator { + // 1. Assert: IsConstructor(constructor) is true. + Assert(IsConstructor(constructor)); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve)); + // 4. Let values be a new empty List. + const values: Value[] = []; + // 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 ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is done, then + if (next === 'done') { + // 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(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + // iv. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // h. Append undefined to values. + values.push(Value.undefined); + // i. Let nextPromise be ? Call(promiseResolve, constructor, « next »). + const nextPromise = Q(yield* Call(promiseResolve, constructor, [next])); + const fulfilledSteps = function* PromiseAllResolveElementFunctions([x = Value.undefined]: Arguments): ValueEvaluator { + const F = surroundingAgent.activeFunctionObject as PromiseAllResolveElementFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value === true) { + return Value.undefined; + } + alreadyCalled.Value = true; + const thisIndex = F.Index; + values[thisIndex] = x; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const valuesArray = CreateArrayFromList(values); + return Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + return Value.undefined; + }; + const onFulfilled = X(CreateBuiltinFunction(fulfilledSteps, 1, Value(''), ['AlreadyCalled', 'Index'])) as Mutable; + onFulfilled.AlreadyCalled = { Value: false }; + onFulfilled.Index = index; + index += 1; + remainingElementsCount.Value += 1; + Q(yield* Invoke(nextPromise, Value('then'), [onFulfilled, resultCapability.Reject])); + } +} + +/** https://tc39.es/ecma262/#sec-promise.all */ +function* Promise_all([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(yield* NewPromiseCapability(C)); + __ts_cast__(C); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = yield* GetPromiseResolve(C); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + __ts_cast__(promiseResolve); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = yield* GetIterator(iterable, 'sync'); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + __ts_cast__(iteratorRecord); + // 7. Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve). + let result: ValueCompletion = yield* 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 = yield* IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return ? result. + return result; +} + +/** https://tc39.es/ecma262/#sec-performpromiseallsettled */ +function* PerformPromiseAllSettled(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator { + // 1. Assert: ! IsConstructor(constructor) is true. + Assert(IsConstructor(constructor)); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve)); + // 4. Let values be a new empty List. + const values: Value[] = []; + // 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 ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is done, + if (next === 'done') { + // 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(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + // iv. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // h. Append undefined to values. + values.push(Value.undefined); + // i. Let nextPromise be ? Call(promiseResolve, constructor, « next »). + const nextPromise = Q(yield* Call(promiseResolve, constructor, [next])); + // j. Let fulfilledSteps be the algorithm steps defined in Promise.allSettled Resolve Element Functions. + const fulfilledSteps = function* PromiseAllSettledResolveElementFunctions([value = Value.undefined]: Arguments): ValueEvaluator { + const F = surroundingAgent.activeFunctionObject as PromiseAllResolveElementFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value === true) { + return Value.undefined; + } + alreadyCalled.Value = true; + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataProperty(obj, Value('status'), Value('fulfilled'))); + X(CreateDataProperty(obj, Value('value'), value)); + const thisIndex = F.Index; + values[thisIndex] = obj; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const valuesArray = CreateArrayFromList(values); + return Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + return Value.undefined; + }; + // l. Let onFulfilled be ! CreateBuiltinFunction(fulfilledSteps, 1, "", « [[AlreadyCalled]], [[Index]] »). + const onFulfilled = X(CreateBuiltinFunction(fulfilledSteps, 1, Value(''), [ + 'AlreadyCalled', + 'Index', + 'Values', + 'Capability', + 'RemainingElements', + ])) as Mutable; + // m. Let alreadyCalled be the Record { [[Value]]: false }. + const alreadyCalled = { Value: false }; + // n. Set onFulfilled.[[AlreadyCalled]] to alreadyCalled. + onFulfilled.AlreadyCalled = alreadyCalled; + // o. Set onFulfilled.[[Index]] to index. + onFulfilled.Index = index; + // s. Let rejectedSteps be the algorithm steps defined in Promise.allSettled Reject Element Functions. + const rejectedSteps = function* PromiseAllSettledRejectElementFunctions([error = Value.undefined]: Arguments): ValueEvaluator { + const F = surroundingAgent.activeFunctionObject as PromiseAllResolveElementFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value === true) { + return Value.undefined; + } + alreadyCalled.Value = true; + const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataProperty(obj, Value('status'), Value('rejected'))); + X(CreateDataProperty(obj, Value('reason'), error)); + const thisIndex = F.Index; + values[thisIndex] = obj; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const valuesArray = X(CreateArrayFromList(values)); + return Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray])); + } + return Value.undefined; + }; + // u. Let onRejected be ! CreateBuiltinFunction(rejectedSteps, 1, "", « [[AlreadyCalled]], [[Index]] »). + const onRejected = X(CreateBuiltinFunction(rejectedSteps, 1, Value(''), ['AlreadyCalled', 'Index'])) as Mutable; + onRejected.AlreadyCalled = alreadyCalled; + onRejected.Index = index; + index += 1; + remainingElementsCount.Value += 1; + Q(yield* Invoke(nextPromise, Value('then'), [onFulfilled, onRejected])); + } +} + +/** https://tc39.es/ecma262/#sec-promise.allsettled */ +function* Promise_allSettled([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(yield* NewPromiseCapability(C)); + __ts_cast__(C); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = yield* GetPromiseResolve(C); + __ts_cast__(promiseResolve); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = yield* GetIterator(iterable, 'sync'); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + __ts_cast__(iteratorRecord); + // 7. Let result be PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve). + let result: ValueCompletion = yield* 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 = yield* IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return ? result. + return result; +} + +/** https://tc39.es/ecma262/#sec-performpromiseany */ +function* PerformPromiseAny(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator { + // 1. Assert: ! IsConstructor(constructor) is true. + Assert(IsConstructor(constructor)); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: ! IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve)); + // 4. Let errors be a new empty List. + const errors: Value[] = []; + // 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 ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is done, then + if (next === 'done') { + // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remainingElementsCount.Value -= 1; + // iii. If remainingElementsCount.[[Value]] is 0, then + if (remainingElementsCount.Value === 0) { + // 1. Let aggregateError be a newly created AggregateError object. + const aggregateError = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value as ObjectValue; + // 2. Perform ! DefinePropertyOrThrow(aggregateError, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: errors }). + X(DefinePropertyOrThrow(aggregateError, Value('errors'), Descriptor({ + Configurable: Value.true, + Enumerable: Value.false, + Writable: Value.true, + Value: X(CreateArrayFromList(errors)), + }))); + // 3. Perform ? Call(resultCapability.[[Reject]], *undefined*, « _aggregateError_ »). + Q(yield* Call(resultCapability.Reject, Value.undefined, [aggregateError])); + } + // iv. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // h. Append undefined to errors. + errors.push(Value.undefined); + // i. Let nextPromise be ? Call(promiseResolve, constructor, « next »). + const nextPromise = Q(yield* Call(promiseResolve, constructor, [next])); + const rejectedSteps = function* PromiseAnyRejectElementFunctions([error = Value.undefined]: Arguments): ValueEvaluator { + const F = surroundingAgent.activeFunctionObject as PromiseAllRejectElementFunctionObject; + const alreadyCalled = F.AlreadyCalled; + if (alreadyCalled.Value) { + return Value.undefined; + } + alreadyCalled.Value = true; + const thisIndex = F.Index; + errors[thisIndex] = error; + remainingElementsCount.Value -= 1; + if (remainingElementsCount.Value === 0) { + const aggregateError = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value as ObjectValue; + X(DefinePropertyOrThrow(aggregateError, Value('errors'), Descriptor({ + Configurable: Value.true, + Enumerable: Value.false, + Writable: Value.true, + Value: X(CreateArrayFromList(errors)), + }))); + return Q(yield* Call(resultCapability.Reject, Value.undefined, [aggregateError])); + } + return Value.undefined; + }; + // l. Let onRejected be ! CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »). + const onRejected = X(CreateBuiltinFunction(rejectedSteps, 1, Value(''), ['AlreadyCalled', 'Index'])) as Mutable; + onRejected.AlreadyCalled = { Value: false }; + onRejected.Index = index; + index += 1; + remainingElementsCount.Value += 1; + Q(yield* Invoke(nextPromise, Value('then'), [resultCapability.Resolve, onRejected])); + } +} + +/** https://tc39.es/ecma262/#sec-promise.any */ +function* Promise_any([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(yield* NewPromiseCapability(C)); + __ts_cast__(C); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = yield* GetPromiseResolve(C); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + __ts_cast__(promiseResolve); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = yield* GetIterator(iterable, 'sync'); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + __ts_cast__(iteratorRecord); + // 7. Let result be PerformPromiseAny(iteratorRecord, C, promiseCapability). + let result: ValueCompletion = yield* 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 = yield* IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return ? result. + return result; +} + +function* PerformPromiseRace(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator { + // 1. Assert: IsConstructor(constructor) is true. + Assert(IsConstructor(constructor)); + // 2. Assert: resultCapability is a PromiseCapability Record. + Assert(resultCapability instanceof PromiseCapabilityRecord); + // 3. Assert: IsCallable(promiseResolve) is true. + Assert(IsCallable(promiseResolve)); + // 4. Repeat, + while (true) { + // a. Let next be ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is done, then + if (next === 'done') { + // ii. Return resultCapability.[[Promise]]. + return resultCapability.Promise; + } + // h. Let nextPromise be ? Call(promiseResolve, constructor, « next »). + const nextPromise = Q(yield* Call(promiseResolve, constructor, [next])); + // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). + Q(yield* Invoke(nextPromise, Value('then'), [resultCapability.Resolve, resultCapability.Reject])); + } +} + +/** https://tc39.es/ecma262/#sec-promise.race */ +function* Promise_race([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(yield* NewPromiseCapability(C)); + __ts_cast__(C); + // 3. Let promiseResolve be GetPromiseResolve(C). + const promiseResolve = yield* GetPromiseResolve(C); + __ts_cast__(promiseResolve); + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + IfAbruptRejectPromise(promiseResolve, promiseCapability); + // 5. Let iteratorRecord be GetIterator(iterable). + const iteratorRecord = yield* GetIterator(iterable, 'sync'); + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + IfAbruptRejectPromise(iteratorRecord, promiseCapability); + __ts_cast__(iteratorRecord); + // 7. Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve). + let result: ValueCompletion = yield* 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 = yield* IteratorClose(iteratorRecord, result); + } + // b. IfAbruptRejectPromise(result, promiseCapability). + IfAbruptRejectPromise(result, promiseCapability); + } + // 9. Return ? result. + return result; +} + +/** https://tc39.es/ecma262/#sec-promise.reject */ +function* Promise_reject([r = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability = Q(yield* NewPromiseCapability(C)); + // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). + Q(yield* Call(promiseCapability.Reject, Value.undefined, [r])); + // 4. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} + +/** https://tc39.es/ecma262/#sec-promise.resolve */ +function* Promise_resolve([x = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. If Type(C) is not Object, throw a TypeError exception. + if (!(C instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Promise.resolve', C); + } + // 3. Return ? PromiseResolve(C, x). + return Q(yield* PromiseResolve(C, x)); +} + +/** https://tc39.es/ecma262/#sec-get-promise-@@species */ +function Promise_symbolSpecies(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Return the this value. + return thisValue; +} + +/** https://tc39.es/ecma262/#sec-promise.try */ +function* Promise_try([callback = Value.undefined, ...args]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. If C is not an Object, throw a TypeError exception. + if (!(C instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Promise.try', C); + } + // 3. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability: PromiseCapabilityRecord = Q(yield* NewPromiseCapability(C)); + // 4. Let status be Completion(Call(callback, undefined, args)). + const status = EnsureCompletion(yield* Call(callback, Value.undefined, args as Arguments)); + + if (status instanceof AbruptCompletion) { + // 5. If status is an abrupt completion, then + // a. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »). + Q(yield* Call(promiseCapability.Reject, Value.undefined, [status.Value])); + } else { + // 6. Else, + // a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »). + Q(yield* Call(promiseCapability.Resolve, Value.undefined, [status.Value])); + } + // 7. Return promiseCapability.[[Promise]]. + return EnsureCompletion(promiseCapability.Promise); +} + +/** https://tc39.es/ecma262/#sec-promise.withResolvers */ +function* Promise_withResolvers(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let C be the this value. + const C = thisValue; + // 2. Let promiseCapability be ? NewPromiseCapability(C). + const promiseCapability: PromiseCapabilityRecord = Q(yield* NewPromiseCapability(C)); + // 3. Let obj be OrdinaryObjectCreate(%Object.prototype%). + const obj = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'))); + // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]). + X(CreateDataPropertyOrThrow(obj, Value('promise'), promiseCapability.Promise)); + // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). + X(CreateDataPropertyOrThrow(obj, Value('resolve'), promiseCapability.Resolve)); + // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). + X(CreateDataPropertyOrThrow(obj, Value('reject'), promiseCapability.Reject)); + // 7. Return obj. + return EnsureCompletion(obj); +} + +export function bootstrapPromise(realmRec: Realm) { + 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], + ['try', Promise_try, 1], + ['withResolvers', Promise_withResolvers, 0], + [wellKnownSymbols.species, [Promise_symbolSpecies]], + ]); + + X(promiseConstructor.DefineOwnProperty(Value('prototype'), Descriptor({ + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + realmRec.Intrinsics['%Promise%'] = promiseConstructor; + realmRec.Intrinsics['%Promise.resolve%'] = X(Get(promiseConstructor, Value('resolve'))) as FunctionObject; +} diff --git a/src/intrinsics/PromisePrototype.mts b/src/intrinsics/PromisePrototype.mts new file mode 100644 index 0000000..af98831 --- /dev/null +++ b/src/intrinsics/PromisePrototype.mts @@ -0,0 +1,125 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { + Q, ThrowCompletion, X, +} from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { PromiseObject } from './Promise.mts'; +import { + Assert, + Call, + CreateBuiltinFunction, + Get, + Invoke, + IsCallable, + IsConstructor, + IsPromise, + NewPromiseCapability, + PerformPromiseThen, + PromiseResolve, + Realm, + SpeciesConstructor, + type FunctionObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-promise.prototype.catch */ +function* PromiseProto_catch([onRejected = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let promise be the this value. + const promise = thisValue; + // 2. Return ? Invoke(promise, "then", « undefined, onRejected »). + return Q(yield* Invoke(promise, Value('then'), [Value.undefined, onRejected])); +} + +/** https://tc39.es/ecma262/#sec-promise.prototype.finally */ +function* PromiseProto_finally([onFinally = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let promise be the this value. + const promise = thisValue; + // 2. If Type(promise) is not Object, throw a TypeError exception. + if (!(promise instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Promise', promise); + } + // 3. Let C be ? SpeciesConstructor(promise, %Promise%). + const C = Q(yield* SpeciesConstructor(promise, surroundingAgent.intrinsic('%Promise%'))); + // 4. Assert: IsConstructor(C) is true. + Assert(IsConstructor(C)); + let thenFinally; + let catchFinally; + // 5. If IsCallable(onFinally) is false, then + if (!IsCallable(onFinally)) { + // a. Let thenFinally be onFinally. + thenFinally = onFinally; + // b. Let catchFinally be onFinally. + catchFinally = onFinally; + } else { // 6. Else, + // a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called: + const thenFinallyClosure = function* thenFinallyClosure([value = Value.undefined]: Arguments): ValueEvaluator { + // i. Let result be ? Call(onFinally, undefined). + const result = Q(yield* Call(onFinally, Value.undefined)); + // ii. Let promise be ? PromiseResolve(C, result). + const promiseInner = Q(yield* PromiseResolve(C, result)); + // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called: + // 1. Return value. + const returnValue = () => value; + // iv. Let valueThunk be ! CreateBuiltinFunction(returnValue, 0, "", « »). + const valueThunk = X(CreateBuiltinFunction(returnValue, 0, Value(''), [])); + // v. Return ? Invoke(promise, "then", « valueThunk »). + return Q(yield* Invoke(promiseInner, Value('then'), [valueThunk])); + }; + // b. Let thenFinally be ! CreateBuiltinFunction(thenFinallyClosure, 1, "", « »). + thenFinally = X(CreateBuiltinFunction(thenFinallyClosure, 1, Value(''), ['HostCapturedValues'])); + // NON-SPEC + thenFinally.HostCapturedValues = [onFinally]; + // c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called: + const catchFinallyClosure = function* catchFinallyClosure([reason = Value.undefined]: Arguments): ValueEvaluator { + // i. Let result be ? Call(onFinally, undefined). + const result = Q(yield* Call(onFinally, Value.undefined)); + // ii. Let promise be ? PromiseResolve(C, result). + const promiseInner = Q(yield* PromiseResolve(C, result)); + // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called: + // 1. Return ThrowCompletion(reason). + const throwReason = () => ThrowCompletion(reason); + // iv. Let thrower be ! CreateBuiltinFunction(throwReason, 0, "", « »). + const thrower = X(CreateBuiltinFunction(throwReason, 0, Value(''), [])); + // v. Return ? Invoke(promise, "then", « thrower »). + return Q(yield* Invoke(promiseInner, Value('then'), [thrower])); + }; + // d. Let catchFinally be ! CreateBuiltinFunction(catchFinallyClosure, 1, "", « »). + catchFinally = X(CreateBuiltinFunction(catchFinallyClosure, 1, Value(''), ['HostCapturedValues'])); + // NON-SPEC + catchFinally.HostCapturedValues = [onFinally]; + } + // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). + return Q(yield* Invoke(promise, Value('then'), [thenFinally, catchFinally])); +} + +/** https://tc39.es/ecma262/#sec-promise.prototype.then */ +function* PromiseProto_then([onFulfilled = Value.undefined, onRejected = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let promise be the this value. + const promise = thisValue as PromiseObject; + // 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(yield* SpeciesConstructor(promise, surroundingAgent.intrinsic('%Promise%'))); + // 4. Let resultCapability be ? NewPromiseCapability(C). + const resultCapability = Q(yield* NewPromiseCapability(C)); + // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability). + Q(surroundingAgent.debugger_tryTouchDuringPreview(promise)); + return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability); +} + +export function bootstrapPromisePrototype(realmRec: Realm) { + 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, Value('then'))) as FunctionObject; + + realmRec.Intrinsics['%Promise.prototype%'] = proto; +} diff --git a/src/intrinsics/Proxy.mts b/src/intrinsics/Proxy.mts new file mode 100644 index 0000000..e9765ab --- /dev/null +++ b/src/intrinsics/Proxy.mts @@ -0,0 +1,100 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + NullValue, ObjectValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q, X, type ValueCompletion } from '../completion.mts'; +import { assignProps } from './bootstrap.mts'; +import { + Assert, + CreateBuiltinFunction, + CreateDataProperty, + markBuiltinFunctionAsConstructor, + OrdinaryObjectCreate, + ProxyCreate, + Realm, + type BuiltinFunctionObject, + type ExoticObject, + type FunctionObject, +} from '#self'; + +export interface ProxyObject extends ExoticObject, BuiltinFunctionObject { + ProxyHandler: Value | NullValue; + ProxyTarget: ObjectValue | NullValue; +} +export function isProxyExoticObject(O: Value): O is ProxyObject { + return 'ProxyHandler' in O; +} +export interface RevocableProxyRevokeFunctionObject extends BuiltinFunctionObject { + RevocableProxy: ProxyObject | NullValue; +} +/** https://tc39.es/ecma262/#sec-proxy-target-handler */ +function ProxyConstructor(this: FunctionObject, [target = Value.undefined, handler = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 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 ProxyCreate(target, handler); +} + +/** https://tc39.es/ecma262/#sec-proxy-revocation-functions */ +function ProxyRevocationFunctions() { + // 1. Let F be the active function object. + const F = surroundingAgent.activeFunctionObject as RevocableProxyRevokeFunctionObject; + // 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; +} + +/** https://tc39.es/ecma262/#sec-proxy.revocable */ +function Proxy_revocable([target = Value.undefined, handler = Value.undefined]: Arguments): ValueCompletion { + // 1. Let p be ? ProxyCreate(target, handler). + const p = Q(ProxyCreate(target, handler)); + /** https://tc39.es/ecma262/#sec-proxy-revocation-functions. */ + const steps = ProxyRevocationFunctions; + // 3. Let length be the number of non-optional parameters of the function definition in Proxy Revocation Functions. + const length = 0; + // 4. Let revoker be ! CreateBuiltinFunction(steps, length, "", « [[RevocableProxy]] »). + const revoker = X(CreateBuiltinFunction(steps, length, Value(''), ['RevocableProxy'])) as RevocableProxyRevokeFunctionObject; + // 5. Set revoker.[[RevocableProxy]] to p. + revoker.RevocableProxy = p; + // 6. Let result be OrdinaryObjectCreate(%Object.prototype%). + const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // 7. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). + X(CreateDataProperty(result, Value('proxy'), p)); + // 8. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker). + X(CreateDataProperty(result, Value('revoke'), revoker)); + // 9. Return result. + return result; +} + +export function bootstrapProxy(realmRec: Realm) { + const proxyConstructor = CreateBuiltinFunction( + markBuiltinFunctionAsConstructor(ProxyConstructor), + 2, + Value('Proxy'), + [], + realmRec, + ); + + assignProps(realmRec, proxyConstructor, [ + ['revocable', Proxy_revocable, 2], + ]); + + realmRec.Intrinsics['%Proxy%'] = proxyConstructor; +} diff --git a/src/intrinsics/Reflect.mts b/src/intrinsics/Reflect.mts new file mode 100644 index 0000000..47368f2 --- /dev/null +++ b/src/intrinsics/Reflect.mts @@ -0,0 +1,211 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { ObjectValue, Value, type Arguments } from '../value.mts'; +import { Q } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Call, + Construct, + CreateArrayFromList, + CreateListFromArrayLike, + FromPropertyDescriptor, + IsCallable, + IsConstructor, + PrepareForTailCall, + Realm, + ToPropertyDescriptor, + ToPropertyKey, + type FunctionObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-reflect.apply */ +function* Reflect_apply([target = Value.undefined, thisArgument = Value.undefined, argumentsList = Value.undefined]: Arguments) { + // 1. If IsCallable(target) is false, throw a TypeError exception. + if (!IsCallable(target)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', target); + } + // 2. Let args be ? CreateListFromArrayLike(argumentsList). + const args = Q(yield* CreateListFromArrayLike(argumentsList)); + // 3. Perform PrepareForTailCall(). + PrepareForTailCall(); + // 4. Return ? Call(target, thisArgument, args). + return Q(yield* Call(target, thisArgument, args)); +} + +/** https://tc39.es/ecma262/#sec-reflect.construct */ +function* Reflect_construct([target = Value.undefined, argumentsList = Value.undefined, newTarget]: Arguments) { + // 1. If IsConstructor(target) is false, throw a TypeError exception. + if (!IsConstructor(target)) { + 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)) { // 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(yield* CreateListFromArrayLike(argumentsList)); + // 5. Return ? Construct(target, args, newTarget). + return Q(yield* Construct(target, args, newTarget as FunctionObject)); +} + +/** https://tc39.es/ecma262/#sec-reflect.defineproperty */ +function* Reflect_defineProperty([target = Value.undefined, propertyKey = Value.undefined, attributes = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(yield* ToPropertyKey(propertyKey)); + // 3. Let desc be ? ToPropertyDescriptor(attributes). + const desc = Q(yield* ToPropertyDescriptor(attributes)); + // 4. Return ? target.[[DefineOwnProperty]](key, desc). + return Q(yield* target.DefineOwnProperty(key, desc)); +} + +/** https://tc39.es/ecma262/#sec-reflect.deleteproperty */ +function* Reflect_deleteProperty([target = Value.undefined, propertyKey = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(yield* ToPropertyKey(propertyKey)); + // 3. Return ? target.[[Delete]](key). + return Q(yield* target.Delete(key)); +} + +/** https://tc39.es/ecma262/#sec-reflect.get */ +function* Reflect_get([target = Value.undefined, propertyKey = Value.undefined, receiver]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(yield* 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(yield* target.Get(key, receiver)); +} + +/** https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor */ +function* Reflect_getOwnPropertyDescriptor([target = Value.undefined, propertyKey = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(yield* ToPropertyKey(propertyKey)); + // 3. Let desc be ? target.[[GetOwnProperty]](key). + const desc = Q(yield* target.GetOwnProperty(key)); + // 4. Return FromPropertyDescriptor(desc). + return FromPropertyDescriptor(desc); +} + +/** https://tc39.es/ecma262/#sec-reflect.getprototypeof */ +function* Reflect_getPrototypeOf([target = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Return ? target.[[GetPrototypeOf]](). + return Q(yield* target.GetPrototypeOf()); +} + +/** https://tc39.es/ecma262/#sec-reflect.has */ +function* Reflect_has([target = Value.undefined, propertyKey = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(yield* ToPropertyKey(propertyKey)); + // 3. Return ? target.[[HasProperty]](key). + return Q(yield* target.HasProperty(key)); +} + +/** https://tc39.es/ecma262/#sec-reflect.isextensible */ +function* Reflect_isExtensible([target = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Return ? target.[[IsExtensible]](). + return Q(yield* target.IsExtensible()); +} + +/** https://tc39.es/ecma262/#sec-reflect.ownkeys */ +function* Reflect_ownKeys([target = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let keys be ? target.[[OwnPropertyKeys]](). + const keys = Q(yield* target.OwnPropertyKeys()); + // 3. Return CreateArrayFromList(keys). + return CreateArrayFromList(keys); +} + +/** https://tc39.es/ecma262/#sec-reflect.preventextensions */ +function* Reflect_preventExtensions([target = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Return ? target.[[PreventExtensions]](). + return Q(yield* target.PreventExtensions()); +} + +/** https://tc39.es/ecma262/#sec-reflect.set */ +function* Reflect_set([target = Value.undefined, propertyKey = Value.undefined, V = Value.undefined, receiver]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let key be ? ToPropertyKey(propertyKey). + const key = Q(yield* ToPropertyKey(propertyKey)); + // 3. If receiver is not present, then + if (receiver === undefined) { + receiver = target; + } + // 4. Return ? target.[[Set]](key, V, receiver). + return Q(yield* target.Set(key, V, receiver)); +} + +/** https://tc39.es/ecma262/#sec-reflect.setprototypeof */ +function* Reflect_setPrototypeOf([target = Value.undefined, proto = Value.undefined]: Arguments) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. If Type(proto) is not Object and proto is not null, throw a TypeError exception. + if (!(proto instanceof ObjectValue) && proto !== Value.null) { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // 3. Return ? target.[[SetPrototypeOf]](proto). + return Q(yield* target.SetPrototypeOf(proto)); +} + +export function bootstrapReflect(realmRec: Realm) { + 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/src/intrinsics/RegExp.mts b/src/intrinsics/RegExp.mts new file mode 100644 index 0000000..a4c9c3c --- /dev/null +++ b/src/intrinsics/RegExp.mts @@ -0,0 +1,165 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { + JSStringValue, + ObjectValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { + isDecimalDigit, isLineTerminator, isWhitespace, +} from '../parser/Lexer.mts'; +import { isAsciiLetter, isControlEscape, isSyntaxCharacter } from '../parser/RegExpParser.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { UnicodeEscape } from './JSON.mts'; +import { + Get, + IsRegExp, + Realm, + RegExpAlloc, + RegExpInitialize, + SameValue, + type FunctionObject, + type OrdinaryObject, + + Assert, isLeadingSurrogate, isTrailingSurrogate, StringToCodePoints, UTF16EncodeCodePoint, type CodePoint, type RegExpMatcher, type RegExpRecord, +} from '#self'; + +export interface RegExpObject extends OrdinaryObject { + readonly OriginalSource: JSStringValue; + readonly OriginalFlags: JSStringValue; + readonly RegExpMatcher: RegExpMatcher; + readonly RegExpRecord: RegExpRecord; + readonly parsedPattern: ParseNode.RegExp.Pattern; +} +export function isRegExpObject(o: Value): o is RegExpObject { + return 'RegExpMatcher' in o; +} +/** https://tc39.es/ecma262/#sec-regexp-constructor */ +function* RegExpConstructor([pattern = Value.undefined, flags = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 1. Let patternIsRegExp be ? IsRegExp(pattern). + const patternIsRegExp = Q(yield* 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(yield* Get(pattern as ObjectValue, 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 (isRegExpObject(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(yield* Get(pattern as ObjectValue, Value('source'))); + // b. If flags is undefined, then + if (flags === Value.undefined) { + // i. Let F be ? Get(pattern, "flags"). + F = Q(yield* Get(pattern as ObjectValue, 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(yield* RegExpAlloc(newTarget as FunctionObject)); + // 8. Return ? RegExpInitialize(O, P, F). + return Q(yield* RegExpInitialize(O, P, F)); +} + +/** https://tc39.es/ecma262/#sec-regexp.escape */ +function* RegExp_escape([S = Value.undefined]: Arguments) { + if (!(S instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', S); + } + let escaped = ''; + const cpList = StringToCodePoints(S.stringValue()); + for (const cp of cpList) { + if (escaped === '' && (isDecimalDigit(String.fromCodePoint(cp)) || isAsciiLetter(cp))) { + const numericValue = cp; + const hex = numericValue.toString(16); + Assert(hex.length === 2); + escaped += `\u{005C}x${hex}`; + } else { + escaped += EncodeForRegExpEscape(cp); + } + } + return Value(escaped); +} + +const table67: Record = { + 9: 't', + 10: 'n', + 11: 'v', + 12: 'f', + 13: 'r', +}; +function EncodeForRegExpEscape(cp: CodePoint) { + const ch = String.fromCharCode(cp); + if (cp === 0x002F || isSyntaxCharacter(ch)) { + return `\u{005C}${UTF16EncodeCodePoint(cp)}`; + } else if (isControlEscape(cp)) { + return `\u{005C}${table67[cp]!}`; + } + const otherPunctuators = ",-=<>#&!%:;@~'`\u{0022}"; + const toEscape = StringToCodePoints(otherPunctuators); + if (toEscape.includes(cp) || isWhitespace(ch) || isLineTerminator(ch) || isLeadingSurrogate(cp) || isTrailingSurrogate(cp)) { + const cpNum = cp; + if (cpNum <= 0xFF) { + const hex = cpNum.toString(16); + return `\u{005C}x${hex.padStart(2, '0')}`; + } + let escaped = ''; + const codeUnits = UTF16EncodeCodePoint(cp); + for (const cu of codeUnits) { + escaped += UnicodeEscape(cu); + } + return escaped; + } + return UTF16EncodeCodePoint(cp); +} + +/** https://tc39.es/ecma262/#sec-get-regexp-@@species */ +function RegExp_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) { + return thisValue; +} + +export function bootstrapRegExp(realmRec: Realm) { + const proto = realmRec.Intrinsics['%RegExp.prototype%']; + + const cons = bootstrapConstructor(realmRec, RegExpConstructor, 'RegExp', 2, proto, [ + [wellKnownSymbols.species, [RegExp_speciesGetter]], + ['escape', RegExp_escape, 1], + ]); + + realmRec.Intrinsics['%RegExp%'] = cons; +} diff --git a/src/intrinsics/RegExpPrototype.mts b/src/intrinsics/RegExpPrototype.mts new file mode 100644 index 0000000..357dfb2 --- /dev/null +++ b/src/intrinsics/RegExpPrototype.mts @@ -0,0 +1,761 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + NullValue, + JSStringValue, + ObjectValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, + UndefinedValue, + NumberValue, +} from '../value.mts'; +import { RegExpState, GetSubstitution } from '../runtime-semantics/all.mts'; +import { CodePointAt } from '../static-semantics/all.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { CreateRegExpStringIterator } from './RegExpStringIteratorPrototype.mts'; +import { isRegExpObject, type RegExpObject } from './RegExp.mts'; +import { + ArrayCreate, + Assert, + Call, + Construct, + CreateDataProperty, + CreateDataPropertyOrThrow, + EscapeRegExpPattern, + Get, + GetMatchString, + GetStringIndex, + IsCallable, + LengthOfArrayLike, + MakeMatchIndicesIndexPairArray, + OrdinaryObjectCreate, + RequireInternalSlot, + SameValue, + Set, + SpeciesConstructor, + ToBoolean, + ToIntegerOrInfinity, + ToLength, + ToObject, + ToString, + ToUint32, + RegExpHasFlag, + F, R, R as MathematicalValue, + Realm, + type MatchRecord, + type OrdinaryObject, +} from '#self'; + + +/** https://tc39.es/ecma262/#sec-regexp.prototype.exec */ +function* RegExpProto_exec([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const R = thisValue as RegExpObject; + Q(RequireInternalSlot(R, 'RegExpMatcher')); + const S = Q(yield* ToString(string)); + return Q(yield* RegExpBuiltinExec(R, S)); +} + +/** https://tc39.es/ecma262/#sec-regexpexec */ +export function* RegExpExec(R: ObjectValue, S: JSStringValue) { + Assert(R instanceof ObjectValue); + Assert(S instanceof JSStringValue); + + const exec = Q(yield* Get(R, Value('exec'))); + if (IsCallable(exec)) { + const result = Q(yield* Call(exec, R, [S])); + if (!(result instanceof ObjectValue) && !(result instanceof NullValue)) { + return surroundingAgent.Throw('TypeError', 'RegExpExecNotObject', result); + } + return result; + } + Q(RequireInternalSlot(R, 'RegExpMatcher')); + return Q(yield* RegExpBuiltinExec(R as RegExpObject, S)); +} + +/** https://tc39.es/ecma262/#sec-regexpbuiltinexec */ +export function* RegExpBuiltinExec(R: RegExpObject, S: JSStringValue): ValueEvaluator { + // Let length be the number of code units in S. + const length = S.stringValue().length; + let lastIndex = MathematicalValue(Q(yield* ToLength(X(Get(R, Value('lastIndex')))))); + const flags = R.OriginalFlags.stringValue(); + const global = flags.includes('g'); + const sticky = flags.includes('y'); + const hasIndices = flags.includes('d'); + if (!global && !sticky) { + lastIndex = 0; + } + const matcher = R.RegExpMatcher; + const fullUnicode = flags.includes('u') || flags.includes('v'); + let matchSucceeded = false; + // If fullUnicode is true, let input be StringToCodePoints(S). Otherwise, let input be a List whose elements are the code units that are the elements of S. + const input = RegExpState.createRegExpMatchingSource(fullUnicode ? Array.from(S.stringValue()) : S.stringValue().split(''), S.stringValue()); + + // used to calculate inputIndex below + const accumulatedInputLength: number[] = []; + if (fullUnicode) { + for (let index = 0; index < input.length; index += 1) { + const codePoint = input[index]; + accumulatedInputLength[index] = (accumulatedInputLength[index - 1] ?? 0) + codePoint.length; + } + } + let r; + while (matchSucceeded === false) { + if (lastIndex > length) { + if (global || sticky) { + Q(yield* Set(R, Value('lastIndex'), F(+0), Value.true)); + } + return Value.null; + } + // Let inputIndex be the index into input of the character that was obtained from element lastIndex of S. + let inputIndex; + if (fullUnicode) { + inputIndex = accumulatedInputLength.findIndex((x) => lastIndex < x); + if (inputIndex === -1) { + // lastIndex is greater than all code points + inputIndex = accumulatedInputLength.length; + } + } else { + inputIndex = lastIndex; + } + + r = matcher(input, inputIndex); + if (r === 'failure') { + if (sticky) { + Q(yield* Set(R, Value('lastIndex'), F(+0), Value.true)); + return Value.null; + } + lastIndex = AdvanceStringIndex(S, lastIndex, fullUnicode); + } else { + Assert(r instanceof RegExpState); + matchSucceeded = true; + } + } + __ts_cast__(r); + let e = r.endIndex; + if (fullUnicode) { + e = GetStringIndex(S, input, e); + } + if (global || sticky) { + Q(yield* Set(R, Value('lastIndex'), F(e), Value.true)); + } + // Let n be the number of elements in r's captures List. + // Note: this list is used as 1-indexed, so the 0th element is a hole and do not count as "the number of elements" + const n = Math.max(0, r.captures.length - 1); + Assert(r.captures[0] === undefined); + Assert(n === R.RegExpRecord.CapturingGroupsCount); + Assert(n < (2 ** 32) - 1); + const A = X(ArrayCreate(n + 1)); + Assert(MathematicalValue(X(Get(A, Value('length'))) as NumberValue) === n + 1); + X(CreateDataPropertyOrThrow(A, Value('index'), F(lastIndex))); + X(CreateDataPropertyOrThrow(A, Value('input'), S)); + const match: MatchRecord = { StartIndex: lastIndex, EndIndex: e }; + const indices: (MatchRecord | UndefinedValue)[] = []; + const groupNames = []; + indices.push(match); + const matchedSubStr = GetMatchString(S, match); + X(CreateDataPropertyOrThrow(A, Value('0'), matchedSubStr)); + let groups; + let hasGroups; + if (R.parsedPattern.capturingGroups.filter((x) => x.GroupName).length > 0) { + groups = OrdinaryObjectCreate(Value.null); + hasGroups = Value.true; + } else { + groups = Value.undefined; + hasGroups = Value.false; + } + X(CreateDataPropertyOrThrow(A, Value('groups'), groups)); + const matchedGroupNames: string[] = []; + for (let i = 1; i <= n; i += 1) { + const captureI = r.captures[i]; + let capturedValue; + if (!captureI) { + capturedValue = Value.undefined; + indices.push(Value.undefined); + } else { + let captureStart = captureI.startIndex; + let captureEnd = captureI.endIndex; + if (fullUnicode) { + captureStart = GetStringIndex(S, input, captureStart); + captureEnd = GetStringIndex(S, input, captureEnd); + } + const capture: MatchRecord = { StartIndex: captureStart, EndIndex: captureEnd }; + capturedValue = GetMatchString(S, capture); + indices.push(capture); + } + X(CreateDataPropertyOrThrow(A, X(ToString(F(i))), capturedValue)); + const i_th = i - 1; + if (R.parsedPattern.capturingGroups[i_th].GroupName) { + const s = Value(R.parsedPattern.capturingGroups[i_th].GroupName); + if (matchedGroupNames.includes(s.stringValue())) { + Assert(capturedValue === Value.undefined); + groupNames.push(Value.undefined); + } else { + if (capturedValue !== Value.undefined) { + matchedGroupNames.push(s.stringValue()); + } + X(CreateDataPropertyOrThrow(groups as ObjectValue, s, capturedValue)); + groupNames.push(s); + } + } else { + groupNames.push(Value.undefined); + } + } + if (hasIndices) { + const indicesArray = MakeMatchIndicesIndexPairArray(S, indices, groupNames, hasGroups); + X(CreateDataPropertyOrThrow(A, Value('indices'), indicesArray)); + } + return A; +} + +/** https://tc39.es/ecma262/#sec-advancestringindex */ +export function AdvanceStringIndex(S: JSStringValue, index: number, unicode: boolean) { + Assert(index <= (2 ** 53) - 1); + if (!unicode) { + return index + 1; + } + const length = S.stringValue().length; + if (index + 1 >= length) { + return index + 1; + } + const cp = CodePointAt(S.stringValue(), index); + return index + cp.CodeUnitCount; +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll */ +function RegExpProto_dotAllGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x0073 (LATIN SMALL LETTER S). + const cu = 's'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.flags */ +function* RegExpProto_flagsGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const R = thisValue; + if (!(R instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + let result = ''; + const hasIndices = ToBoolean(Q(yield* Get(R, Value('hasIndices')))); + if (hasIndices === Value.true) { + result += 'd'; + } + const global = ToBoolean(Q(yield* Get(R, Value('global')))); + if (global === Value.true) { + result += 'g'; + } + const ignoreCase = ToBoolean(Q(yield* Get(R, Value('ignoreCase')))); + if (ignoreCase === Value.true) { + result += 'i'; + } + const multiline = ToBoolean(Q(yield* Get(R, Value('multiline')))); + if (multiline === Value.true) { + result += 'm'; + } + const dotAll = ToBoolean(Q(yield* Get(R, Value('dotAll')))); + if (dotAll === Value.true) { + result += 's'; + } + const unicode = ToBoolean(Q(yield* Get(R, Value('unicode')))); + if (unicode === Value.true) { + result += 'u'; + } + const unicodeSet = ToBoolean(Q(yield* Get(R, Value('unicodeSets')))); + if (unicodeSet === Value.true) { + result += 'v'; + } + const sticky = ToBoolean(Q(yield* Get(R, Value('sticky')))); + if (sticky === Value.true) { + result += 'y'; + } + return Value(result); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.global */ +function RegExpProto_globalGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const R = thisValue as RegExpObject; + if (!(R instanceof ObjectValue)) { + 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; +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.hasIndices */ +function RegExpProto_hasIndicesGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x0073 (LATIN SMALL LETTER D). + const cu = 'd'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase */ +function RegExpProto_ignoreCaseGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x0069 (LATIN SMALL LETTER I). + const cu = 'i'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +/** https://tc39.es/ecma262/#sec-regexp.prototype-@@match */ +function* RegExpProto_match([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let rx be the this value. + const rx = thisValue; + // 2. If Type(rx) is not Object, throw a TypeError exception. + if (!(rx instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + // 3. Let S be ? ToString(string). + const S = Q(yield* ToString(string)); + // 4. Let flags be ? ToString(? Get(rx, "flags")). + const flags = Q(yield* ToString(Q(yield* Get(rx, Value('flags'))))); + // 5. If flags does not contain "g", then + if (!flags.stringValue().includes('g')) { + // a. Return ? RegExpExec(rx, S). + return Q(yield* RegExpExec(rx, S)); + } else { // 6. Else, + // a. If flags contains "u", let fullUnicode be true. Otherwise, let fullUnicode be false. + const fullUnicode = flags.stringValue().includes('u'); + // b. Perform ? Set(rx, "lastIndex", +0𝔽, true). + Q(yield* Set(rx, Value('lastIndex'), F(+0), Value.true)); + // c. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(0)); + // d. Let n be 0. + let n = 0; + // e. Repeat, + while (true) { + // i. Let result be ? RegExpExec(rx, S). + const result = Q(yield* RegExpExec(rx, S)); + // ii. If result is null, then + if (result instanceof NullValue) { + // 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(yield* ToString(Q(yield* Get(result, Value('0'))))); + // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), matchStr). + X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), matchStr)); + // 3. If matchStr is the empty String, then + if (matchStr.stringValue() === '') { + // a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))). + const thisIndex = R(Q(yield* ToLength(Q(yield* Get(rx, Value('lastIndex')))))); + // b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). + const nextIndex = AdvanceStringIndex(S, thisIndex, fullUnicode); + // c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true). + Q(yield* Set(rx, Value('lastIndex'), F(nextIndex), Value.true)); + } + // 4. Set n to n + 1. + n += 1; + } + } + } +} + +/** https://tc39.es/ecma262/#sec-regexp-prototype-matchall */ +function* RegExpProto_matchAll([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const R = thisValue; + if (!(R instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const S = Q(yield* ToString(string)); + const C = Q(yield* SpeciesConstructor(R, surroundingAgent.intrinsic('%RegExp%'))); + const flags = Q(yield* ToString(Q(yield* Get(R, Value('flags'))))); + const matcher = Q(yield* Construct(C, [R, flags])); + const lastIndex = Q(yield* ToLength(Q(yield* Get(R, Value('lastIndex'))))); + Q(yield* Set(matcher, Value('lastIndex'), lastIndex, Value.true)); + const global = flags.stringValue().includes('g'); + const fullUnicode = flags.stringValue().includes('u') || flags.stringValue().includes('v'); + return CreateRegExpStringIterator(matcher, S, global, fullUnicode); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline */ +function RegExpProto_multilineGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x006D (LATIN SMALL LETTER M). + const cu = 'm'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +/** https://tc39.es/ecma262/#sec-regexp.prototype-@@replace */ +function* RegExpProto_replace([string = Value.undefined, replaceValue = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let rx be the this value. + const rx = thisValue; + // 2. If rx is not an Object, throw a TypeError exception. + if (!(rx instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + // 3. Let S be ? ToString(string). + const S = Q(yield* ToString(string)); + // 4. Let lengthS be the length of S. + const lengthS = S.stringValue().length; + // 5. Let functionalReplace be IsCallable(replaceValue). + const functionalReplace = IsCallable(replaceValue); + // 6. If functionalReplace is false, then + if (!functionalReplace) { + // a. Set replaceValue to ? ToString(replaceValue). + replaceValue = Q(yield* ToString(replaceValue)); + } + // 7. Let flags be ? ToString(? Get(rx, "flags")). + const flags = Q(yield* ToString(Q(yield* Get(rx, Value('flags'))))); + // 8. If flags contains "g", let global be true. Otherwise, let global be false. + const global = flags.stringValue().includes('g') ? Value.true : Value.false; + let fullUnicode; + // 9. If global is true, then + if (global === Value.true) { + // a. If flags contains "u", let fullUnicode be true. Otherwise, let fullUnicode be false. + fullUnicode = flags.stringValue().includes('u'); + // b. Perform ? Set(rx, "lastIndex", +0𝔽, true). + Q(yield* Set(rx, Value('lastIndex'), F(+0), Value.true)); + } + // 10. Let results be a new empty List. + const results = []; + // 11. Let done be false. + let done = false; + // 12. Repeat, while done is false, + while (!done) { + // a. Let result be ? RegExpExec(rx, S). + const result = Q(yield* RegExpExec(rx, S)); + // b. If result is null, set done to true. + if (result instanceof NullValue) { + done = true; + } else { // c. Else, + // i. Append result to results. + results.push(result); + // ii. If global is false, set done to true. + if (global === Value.false) { + done = true; + } else { // iii. Else, + // 1. Let matchStr be ? ToString(? Get(result, "0")). + const matchStr = Q(yield* ToString(Q(yield* Get(result, Value('0'))))); + // 2. If matchStr is the empty String, then + if (matchStr.stringValue() === '') { + // a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))). + const thisIndex = R(Q(yield* ToLength(Q(yield* Get(rx, Value('lastIndex')))))); + // b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). + const nextIndex = AdvanceStringIndex(S, thisIndex, fullUnicode!); + // c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true). + Q(yield* Set(rx, Value('lastIndex'), F(nextIndex), Value.true)); + } + } + } + } + // 13. Let accumulatedResult be the empty String. + let accumulatedResult = ''; + // 14. Let nextSourcePosition be 0. + let nextSourcePosition = 0; + // 15. For each element result of results, do + for (const result of results) { + // a. Let resultLength be ? LengthOfArrayLike(result). + let nCaptures = Q(yield* LengthOfArrayLike(result)); + // b. Let nCaptures be max(resultLength - 1, 0). + nCaptures = Math.max(nCaptures - 1, 0); + // c. Let matched be ? ToString(? Get(result, "0")). + const matched = Q(yield* ToString(Q(yield* Get(result, Value('0'))))); + // d. Let matchLength be the length of matched. + const matchLength = matched.stringValue().length; + // e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")). + let position = Q(yield* ToIntegerOrInfinity(Q(yield* Get(result, Value('index'))))); + // f. Set position to the result of clamping position between 0 and lengthS. + position = Math.max(Math.min(position, lengthS), 0); + // g. Let captures be a new empty List. + const captures = []; + // h. Let n be 1. + let n = 1; + // i. Repeat, while n ≤ nCaptures, + while (n <= nCaptures) { + // i. Let capN be ? Get(result, ! ToString(𝔽(n))). + let capN = Q(yield* Get(result, X(ToString(F(n))))); + // ii. If capN is not undefined, then + if (capN !== Value.undefined) { + // 1. Set capN to ? ToString(capN). + capN = Q(yield* ToString(capN)); + } + // iii. Append capN to captures. + captures.push(capN); + // iv. NOTE: When n = 1, the preceding step puts the first element into captures + // (at index 0). More generally, the nth capture (the characters captured by + // the nth set of capturing parentheses) is at captures[n - 1]. + // v. Set n to n + 1. + n += 1; + } + // j. Let namedCaptures be ? Get(result, "groups"). + let namedCaptures = Q(yield* Get(result, Value('groups'))); + let replacement; + // k. If functionalReplace is true, then + if (functionalReplace) { + // i. Let replacerArgs be the list-concatenation of « matched », captures, and « 𝔽(position), S ». + const replacerArgs: Value[] = [matched, ...captures, F(position), S]; + // ii. If namedCaptures is not undefined, then + if (namedCaptures !== Value.undefined) { + // 1. Append namedCaptures to replacerArgs. + replacerArgs.push(namedCaptures); + } + // iii. Let replValue be ? Call(replaceValue, undefined, replacerArgs). + const replValue = Q(yield* Call(replaceValue, Value.undefined, replacerArgs)); + // iv. Let replacement be ? ToString(replValue). + replacement = Q(yield* ToString(replValue)); + } else { // l. Else, + // i. If namedCaptures is not undefined, then + if (namedCaptures !== Value.undefined) { + // 1. Set namedCaptures to ? ToObject(namedCaptures). + namedCaptures = Q(ToObject(namedCaptures)); + } + // ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue). + replacement = Q(yield* GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue as JSStringValue)); + } + // m. If position ≥ nextSourcePosition, then + if (position >= nextSourcePosition) { + // i. NOTE: position should not normally move backwards. If it does, it is an indication of an + // ill-behaving RegExp subclass or use of an access triggered side-effect to change the + // global flag or other characteristics of rx. In such cases, the corresponding substitution is ignored. + // ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement. + accumulatedResult = accumulatedResult + S.stringValue().substring(nextSourcePosition, position) + replacement.stringValue(); + // iii. Set nextSourcePosition to position + matchLength. + nextSourcePosition = position + matchLength; + } + } + // 16. If nextSourcePosition ≥ lengthS, return accumulatedResult. + if (nextSourcePosition >= lengthS) { + return Value(accumulatedResult); + } + // 17. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition. + return Value(accumulatedResult + S.stringValue().substring(nextSourcePosition)); +} + +/** https://tc39.es/ecma262/#sec-regexp.prototype-@@search */ +function* RegExpProto_search([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const rx = thisValue; + if (!(rx instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + const S = Q(yield* ToString(string)); + + const previousLastIndex = Q(yield* Get(rx, Value('lastIndex'))); + if (SameValue(previousLastIndex, F(+0)) === Value.false) { + Q(yield* Set(rx, Value('lastIndex'), F(+0), Value.true)); + } + + const result = Q(yield* RegExpExec(rx, S)); + const currentLastIndex = Q(yield* Get(rx, Value('lastIndex'))); + if (SameValue(currentLastIndex, previousLastIndex) === Value.false) { + Q(yield* Set(rx, Value('lastIndex'), previousLastIndex, Value.true)); + } + + if (result instanceof NullValue) { + return F(-1); + } + + return Q(yield* Get(result, Value('index'))); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.source */ +function RegExpProto_sourceGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const R = thisValue; + if (!(R instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + if (!('OriginalSource' in R)) { + if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) { + return Value('(?:)'); + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + Assert(isRegExpObject(R)); + const src = R.OriginalSource; + const flags = R.OriginalFlags; + return EscapeRegExpPattern(src, flags); +} + +/** https://tc39.es/ecma262/#sec-regexp.prototype-@@split */ +function* RegExpProto_split([string = Value.undefined, limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const rx = thisValue; + if (!(rx instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx); + } + const S = Q(yield* ToString(string)); + + const C = Q(yield* SpeciesConstructor(rx, surroundingAgent.intrinsic('%RegExp%'))); + const flagsValue = Q(yield* Get(rx, Value('flags'))); + const flags = Q(yield* ToString(flagsValue)).stringValue(); + const unicodeMatching = flags.includes('u'); + const newFlags = flags.includes('y') ? Value(flags) : Value(`${flags}y`); + const splitter = Q(yield* Construct(C, [rx, newFlags])); + + const A = X(ArrayCreate(0)); + let lengthA = 0; + + let lim; + if (limit === Value.undefined) { + lim = (2 ** 32) - 1; + } else { + lim = R(Q(yield* ToUint32(limit))); + } + + const size = S.stringValue().length; + let p = 0; + + if (lim === 0) { + return A; + } + + if (size === 0) { + const z = Q(yield* RegExpExec(splitter, S)); + if (z !== Value.null) { + return A; + } + X(CreateDataProperty(A, Value('0'), S)); + return A; + } + + let q = p; + while (q < size) { + Q(yield* Set(splitter, Value('lastIndex'), F(q), Value.true)); + const z = Q(yield* RegExpExec(splitter, S)); + if (z instanceof NullValue) { + q = AdvanceStringIndex(S, q, unicodeMatching); + } else { + const lastIndex = Q(yield* Get(splitter, Value('lastIndex'))); + let e = R(Q(yield* ToLength(lastIndex))); + e = Math.min(e, size); + if (e === p) { + q = AdvanceStringIndex(S, q, unicodeMatching); + } else { + const T = Value(S.stringValue().substring(p, q)); + X(CreateDataProperty(A, X(ToString(F(lengthA))), T)); + lengthA += 1; + if (lengthA === lim) { + return A; + } + p = e; + let numberOfCaptures = Q(yield* LengthOfArrayLike(z)); + numberOfCaptures = Math.max(numberOfCaptures - 1, 0); + let i = 1; + while (i <= numberOfCaptures) { + const nextCapture = Q(yield* Get(z, X(ToString(F(i))))); + X(CreateDataProperty(A, X(ToString(F(lengthA))), nextCapture)); + i += 1; + lengthA += 1; + if (lengthA === lim) { + return A; + } + } + q = p; + } + } + } + + const T = Value(S.stringValue().substring(p, size)); + X(CreateDataProperty(A, X(ToString(F(lengthA))), T)); + return A; +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky */ +function RegExpProto_stickyGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x0097 (LATIN SMALL LETTER Y). + const cu = 'y'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +/** https://tc39.es/ecma262/#sec-regexp.prototype.test */ +function* RegExpProto_test([S = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const R = thisValue; + if (!(R instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const string = Q(yield* ToString(S)); + const match = Q(yield* RegExpExec(R, string)); + if (match !== Value.null) { + return Value.true; + } + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-regexp.prototype.tostring */ +function* RegExpProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const R = thisValue; + if (!(R instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R); + } + const pattern = Q(yield* ToString(Q(yield* Get(R, Value('source'))))); + const flags = Q(yield* ToString(Q(yield* Get(R, Value('flags'))))); + const result = `/${pattern.stringValue()}/${flags.stringValue()}`; + return Value(result); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode */ +function RegExpProto_unicodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x0075 (LATIN SMALL LETTER U). + const cu = 'u'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +/** https://tc39.es/ecma262/#sec-get-regexp.prototype.unicodeSets */ +function RegExpProto_unicodeSetsGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let R be the this value. + const R = thisValue; + // 2. Let cu be the code unit 0x0076 (LATIN SMALL LETTER V). + const cu = 'v'; + // 3. Return ? RegExpHasFlag(R, cu). + return Q(RegExpHasFlag(R, cu)); +} + +export function bootstrapRegExpPrototype(realmRec: Realm) { + const proto = bootstrapPrototype( + realmRec, + [ + ['exec', RegExpProto_exec, 1], + ['dotAll', [RegExpProto_dotAllGetter]], + ['flags', [RegExpProto_flagsGetter]], + ['global', [RegExpProto_globalGetter]], + ['hasIndices', [RegExpProto_hasIndicesGetter]], + ['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]], + ['unicodeSets', [RegExpProto_unicodeSetsGetter]], + ], + realmRec.Intrinsics['%Object.prototype%'], + ); + + realmRec.Intrinsics['%RegExp.prototype%'] = proto; +} diff --git a/src/intrinsics/RegExpStringIteratorPrototype.mts b/src/intrinsics/RegExpStringIteratorPrototype.mts new file mode 100644 index 0000000..ac1daee --- /dev/null +++ b/src/intrinsics/RegExpStringIteratorPrototype.mts @@ -0,0 +1,73 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + JSStringValue, NullValue, ObjectValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { + Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { RegExpExec, AdvanceStringIndex } from './RegExpPrototype.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + CreateIteratorFromClosure, + GeneratorResume, + ToString, + ToLength, + Get, + Set, + Yield, + F, R as MathematicalValue, + Realm, + type GeneratorObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-createregexpstringiterator */ +export function CreateRegExpStringIterator(R: ObjectValue, S: JSStringValue, global: boolean, fullUnicode: boolean): ValueCompletion { + // 4. Let closure be a new Abstract Closure with no parameters that captures R, S, global, and fullUnicode and performs the following steps when called: + const closure = function* closure(): ValueEvaluator { + // a. Repeat, + while (true) { + // i. Let match be ? RegExpExec(R, S). + const match = Q(yield* RegExpExec(R, S)); + // ii. If match is null, return undefined. + if (match instanceof NullValue) { + return Value.undefined; + } + // iii. If global is false, then + if (!global) { + // 1. Perform ? Yield(match). + Q(yield* Yield(match)); + // 2. Return undefined. + return Value.undefined; + } + // iv. Let matchStr be ? ToString(? Get(match, "0")). + const matchStr = Q(yield* ToString(Q(yield* Get(match, Value('0'))))); + // v. If matchStr is the empty String, then + if (matchStr.stringValue() === '') { + // i. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). + const thisIndex = MathematicalValue(Q(yield* ToLength(Q(yield* Get(R, Value('lastIndex')))))); + // ii. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). + const nextIndex = X(AdvanceStringIndex(S, thisIndex, fullUnicode)); + // iii. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true). + Q(yield* Set(R, Value('lastIndex'), F(nextIndex), Value.true)); + } + // vi. Perform ? Yield(match). + Q(yield* Yield(match)); + } + }; + // 4. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%). + return X(CreateIteratorFromClosure(closure, Value('%RegExpStringIteratorPrototype%'), surroundingAgent.intrinsic('%RegExpStringIteratorPrototype%'))); +} + +/** https://tc39.es/ecma262/#sec-%regexpstringiteratorprototype%.next */ +function* RegExpStringIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Return ? GeneratorResume(this value, empty, "%RegExpStringIteratorPrototype%"). + return Q(yield* GeneratorResume(thisValue, undefined, Value('%RegExpStringIteratorPrototype%'))); +} + +export function bootstrapRegExpStringIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', RegExpStringIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'RegExp String Iterator'); + + realmRec.Intrinsics['%RegExpStringIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/Set.mts b/src/intrinsics/Set.mts new file mode 100644 index 0000000..9a265a1 --- /dev/null +++ b/src/intrinsics/Set.mts @@ -0,0 +1,75 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + UndefinedValue, Value, wellKnownSymbols, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { IfAbruptCloseIterator, Q } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Call, + Get, + GetIterator, + IsCallable, + IteratorStepValue, + OrdinaryCreateFromConstructor, + Realm, + type FunctionObject, + type OrdinaryObject, +} from '#self'; + +export interface SetObject extends OrdinaryObject { + readonly SetData: (Value | undefined)[]; +} +export function isSetObject(value: Value): value is SetObject { + return 'SetData' in value; +} +/** https://tc39.es/ecma262/#sec-set-iterable */ +function* SetConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »). + const set = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Set.prototype%', ['SetData'])) as Mutable; + // 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(yield* Get(set, Value('add'))); + // 6. If IsCallable(adder) is false, throw a TypeError exception. + if (!IsCallable(adder)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + // 7. Let iteratorRecord be ? GetIterator(iterable). + const iteratorRecord = Q(yield* GetIterator(iterable, 'sync')); + // 8. Repeat, + while (true) { + // a. Let next be ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // b. If next is false, return set. + if (next === 'done') { + return set; + } + // d. Let status be Call(adder, set, « next »). + const status = yield* Call(adder, set, [next]); + // e. IfAbruptCloseIterator(status, iteratorRecord). + IfAbruptCloseIterator(status, iteratorRecord); + } +} + +/** https://tc39.es/ecma262/#sec-get-set-@@species */ +function Set_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) { + // Return the this value. + return thisValue; +} + +export function bootstrapSet(realmRec: Realm) { + const setConstructor = bootstrapConstructor(realmRec, SetConstructor, 'Set', 0, realmRec.Intrinsics['%Set.prototype%'], [ + [wellKnownSymbols.species, [Set_speciesGetter]], + ]); + + realmRec.Intrinsics['%Set%'] = setConstructor; +} diff --git a/src/intrinsics/SetIteratorPrototype.mts b/src/intrinsics/SetIteratorPrototype.mts new file mode 100644 index 0000000..7a3f893 --- /dev/null +++ b/src/intrinsics/SetIteratorPrototype.mts @@ -0,0 +1,78 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q, X, type ValueCompletion } from '../completion.mts'; +import { + Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import type { ValueEvaluator, YieldEvaluator } from '../evaluator.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { SetObject } from './Set.mts'; +import { + Assert, + CreateArrayFromList, + CreateIteratorFromClosure, + GeneratorResume, + Realm, + RequireInternalSlot, + Yield, + type GeneratorObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-createsetiterator */ +export function CreateSetIterator(set: Value, kind: 'key+value' | 'value'): ValueCompletion { + // 1. Assert: kind is key+value or value. + Assert(kind === 'key+value' || kind === 'value'); + // 2. Perform ? RequireInternalSlot(set, [[SetData]]). + Q(RequireInternalSlot(set, 'SetData')); + // 3. Let closure be a new Abstract Closure with no parameters that captures set and kind and performs the following steps when called: + const closure = function* closure(): YieldEvaluator { + // a. Let index be 0. + let index = 0; + // b. Let entries be the List that is set.[[SetData]]. + const entries = (set as SetObject).SetData; + // c. Let numEntries be the number of elements of entries. + let numEntries = entries.length; + // d. Repeat, while index < numEntries, + while (index < numEntries) { + // i. Let e be entries[index]. + const e = entries[index]; + // ii. Set index to index + 1. + index += 1; + // iii. If e is not empty, then + if (e !== undefined) { + // 1. If kind is key+value, then + if (kind === 'key+value') { + // a. Perform ? Yield(! CreateArrayFromList(« e, e »)). + Q(yield* Yield(X(CreateArrayFromList([e, e])))); + } else { // 2. Else, + // a. Assert: kind is value. + Assert(kind === 'value'); + // b. Perform ? Yield(e). + Q(yield* Yield(e)); + } + } + // iv. Set numEntries to the number of elements of entries. + numEntries = entries.length; + } + // NON-SPEC + generator.HostCapturedValues = undefined; + // e. Return undefined. + return Value.undefined; + }; + // 4. Return ! CreateIteratorFromClosure(closure, "%SetIteratorPrototype%", %SetIteratorPrototype%). + const generator = X(CreateIteratorFromClosure(closure, Value('%SetIteratorPrototype%'), surroundingAgent.intrinsic('%SetIteratorPrototype%'), ['HostCapturedValues'], [set])); + return generator; +} + +/** https://tc39.es/ecma262/#sec-%setiteratorprototype%.next */ +function* SetIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Return ? GeneratorResume(this value, empty, "%SetIteratorPrototype%"). + return Q(yield* GeneratorResume(thisValue, undefined, Value('%SetIteratorPrototype%'))); +} + +export function bootstrapSetIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', SetIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'Set Iterator'); + + realmRec.Intrinsics['%SetIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/SetPrototype.mts b/src/intrinsics/SetPrototype.mts new file mode 100644 index 0000000..1eac583 --- /dev/null +++ b/src/intrinsics/SetPrototype.mts @@ -0,0 +1,715 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Descriptor, + NumberValue, + Value, + wellKnownSymbols, + ObjectValue, + type Arguments, + type FunctionCallContext, + BooleanValue, +} from '../value.mts'; +import { + EnsureCompletion, NormalCompletion, Q, X, type ValueCompletion, type ValueEvaluator, +} from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { CreateSetIterator } from './SetIteratorPrototype.mts'; +import type { SetObject } from './Set.mts'; +import { + Call, + F, + IsCallable, + RequireInternalSlot, + Get, + ToNumber, + ToIntegerOrInfinity, + IteratorStep, + IteratorValue, + OrdinaryObjectCreate, + SameValueZero, R, + Realm, + ToBoolean, + GetIteratorFromMethod, + CanonicalizeKeyedCollectionKey, + IteratorStepValue, + IteratorClose, +} from '#self'; +import type { + FunctionObject, + Mutable, + PlainEvaluator, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-set.prototype.add */ +function SetProto_add([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue as SetObject; + // 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 (value instanceof NumberValue && Object.is(R(value), -0)) { + value = F(+0); + } + // 6. Append value as the last element of entries. + Q(surroundingAgent.debugger_tryTouchDuringPreview(S)); + entries.push(value); + // 7. Return S. + return S; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.clear */ +function SetProto_clear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue as SetObject; + // 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 + if (entries.length) { + Q(surroundingAgent.debugger_tryTouchDuringPreview(S)); + } + 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; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.delete */ +function SetProto_delete([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue as SetObject; + // 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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(S)); + entries[i] = undefined; + // ii. Return true. + return Value.true; + } + } + // 5. Return false. + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.difference */ +function* SetProto_difference([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + + // 2. Perform ? RequireInternalSlot(O, [[SetData]]). + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + // 3. Let otherRec be ? GetSetRecord(other). + const otherRec = Q(yield* GetSetRecord(other)); + + // 4. Let resultSetData be a copy of O.[[SetData]]. + const resultSetData = [...O.SetData]; + + // 5. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then + if (R(SetDataSize(O.SetData)) <= otherRec.Size) { + /* + a. Let thisSize be the number of elements in O.[[SetData]]. + b. Let index be 0. + c. Repeat, while index < thisSize, + i. Let e be resultSetData[index]. + ii. If e is not empty, then + 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)). + 2. If inOther is true, then + a. Set resultSetData[index] to empty. + iii. Set index to index + 1. + */ + const thisSize = O.SetData.length; + let index = 0; + while (index < thisSize) { + const e = resultSetData[index]; + if (e !== undefined) { + const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e]))); + if (inOther === Value.true) { + resultSetData[index] = undefined; + } + } + index += 1; + } + } else { + /* + a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]). + b. Let next be not-started. + c. Repeat, while next is not done, + i. Set next to ? IteratorStepValue(keysIter). + ii. If next is not done, then + 1. Set next to CanonicalizeKeyedCollectionKey(next). + 2. Let valueIndex be SetDataIndex(resultSetData, next). + 3. If valueIndex is not not-found, then + a. Set resultSetData[valueIndex] to empty. + */ + const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys)); + let next: Value | 'done' | 'not-started' = 'not-started'; + while (next !== 'done') { + next = Q(yield* IteratorStepValue(keysIter)); + if (next !== 'done') { + next = CanonicalizeKeyedCollectionKey(next); + const valueIndex = SetDataIndex(resultSetData, next); + if (valueIndex !== 'not-found') { + resultSetData[valueIndex] = undefined; + } + } + } + } + + /* + 7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »). + 8. Set result.[[SetData]] to resultSetData. + 9. Return result. + */ + const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable; + result.SetData = resultSetData; + return result; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.entries */ +function SetProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue; + // 2. Return ? CreateSetIterator(S, key+value). + return Q(CreateSetIterator(S, 'key+value')); +} + +/** https://tc39.es/ecma262/#sec-set.prototype.foreach */ +function* SetProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let S be the this value. + const S = thisValue as SetObject; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 4. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + // 5. For each element _e_ of _entries_, 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(yield* Call(callbackfn, thisArg, [e, e, S])); + } + } + // 6. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.has */ +function SetProto_has([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue as SetObject; + // 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; +} + +/** https://tc39.es/ecma262/#sec-get-set.prototype.size */ +function SetProto_sizeGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue as SetObject; + // 2. Perform ? RequireInternalSlot(S, [[SetData]]). + Q(RequireInternalSlot(S, 'SetData')); + // 3. Let entries be the List that is S.[[SetData]]. + const entries = S.SetData; + + return SetDataSize(entries); +} + +/** https://tc39.es/ecma262/#sec-set.prototype.intersection */ +function* SetProto_intersection([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + + // 2. Perform ? RequireInternalSlot(O, [[SetData]]). + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + // 3. Let otherRec be ? GetSetRecord(other). + const otherRec = Q(yield* GetSetRecord(other)); + + // 4. Let resultSetData be a new empty List. + const resultSetData: Value[] = []; + + if (R(SetDataSize(O.SetData)) <= otherRec.Size) { + /* + a. Let thisSize be the number of elements in O.[[SetData]]. + b. Let index be 0. + c. Repeat, while index < thisSize, + i. Let e be O.[[SetData]][index]. + ii. Set index to index + 1. + iii. If e is not empty, then + 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)). + 2. If inOther is true, then + a. NOTE: It is possible for earlier calls to otherRec.[[Has]] to remove and re-add an element of O.[[SetData]], which can cause the same element to be visited twice during this iteration. + b. If SetDataHas(resultSetData, e) is false, then + i. Append e to resultSetData. + 3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]]. + 4. Set thisSize to the number of elements in O.[[SetData]]. + */ + let thisSize = O.SetData.length; + let index = 0; + while (index < thisSize) { + const e: Value | undefined = O.SetData[index]; + index += 1; + if (e !== undefined) { + const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e]))); + if (inOther === Value.true && !SetDataHas(resultSetData, e)) { + resultSetData.push(e); + } + } + thisSize = O.SetData.length; + } + } else { + /* + a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]). + b. Let next be not-started. + c. Repeat, while next is not done, + i. Set next to ? IteratorStepValue(keysIter). + ii. If next is not done, then + 1. Set next to CanonicalizeKeyedCollectionKey(next). + 2. Let inThis be SetDataHas(O.[[SetData]], next). + 3. If inThis is true, then + a. NOTE: Because other is an arbitrary object, it is possible for its "keys" iterator to produce the same value more than once. + b. If SetDataHas(resultSetData, next) is false, then + i. Append next to resultSetData. + */ + const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys)); + let next: Value | 'done' | 'not-started' = 'not-started'; + while (next !== 'done') { + next = Q(yield* IteratorStepValue(keysIter)); + if (next !== 'done') { + next = CanonicalizeKeyedCollectionKey(next); + const inThis = SetDataHas(O.SetData, next); + if (inThis && !SetDataHas(resultSetData, next)) { + resultSetData.push(next); + } + } + } + } + + /* + 7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »). + 8. Set result.[[SetData]] to resultSetData. + 9. Return result. + */ + const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable; + result.SetData = resultSetData; + return result; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.isdisjointfrom */ +function* SetProto_isDisjointFrom([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + + // 2. Perform ? RequireInternalSlot(O, [[SetData]]). + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + // 3. Let otherRec be ? GetSetRecord(other). + const otherRec = Q(yield* GetSetRecord(other)); + + if (R(SetDataSize(O.SetData)) <= otherRec.Size) { + /* + a. Let thisSize be the number of elements in O.[[SetData]]. + b. Let index be 0. + c. Repeat, while index < thisSize, + i. Let e be O.[[SetData]][index]. + ii. Set index to index + 1. + iii. If e is not empty, then + 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)). + 2. If inOther is true, return false. + 3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]]. + 4. Set thisSize to the number of elements in O.[[SetData]]. + */ + let thisSize = O.SetData.length; + let index = 0; + while (index < thisSize) { + const e = O.SetData[index]; + index += 1; + if (e !== undefined) { + const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e]))); + if (inOther === Value.true) { + return BooleanValue.false; + } + thisSize = O.SetData.length; + } + } + } else { + /* + a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]). + b. Let next be not-started. + c. Repeat, while next is not done, + i. Set next to ? IteratorStepValue(keysIter). + ii. If next is not done, then + 1. If SetDataHas(O.[[SetData]], next) is true, then + a. Perform ? IteratorClose(keysIter, NormalCompletion(unused)). + b. Return false. + */ + const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys)); + let next: Value | 'done' | 'not-started' = 'not-started'; + while (next !== 'done') { + next = Q(yield* IteratorStepValue(keysIter)); + if (next !== 'done' && SetDataHas(O.SetData, next)) { + Q(yield* IteratorClose(keysIter, NormalCompletion(undefined))); + return Value.false; + } + } + } + + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.issubsetof */ +function* SetProto_isSubsetOf([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + const otherRec = Q(yield* GetSetRecord(other)); + if (SetDataSize(O.SetData).value > otherRec.Size) { + return Value.false; + } + + let thisSize = O.SetData.length; + let index = 0; + while (index < thisSize) { + /* + a. Let e be O.[[SetData]][index]. + b. Set index to index + 1. + c. If e is not empty, then + i. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)). + ii. If inOther is false, return false. + iii. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]]. + iv. Set thisSize to the number of elements in O.[[SetData]]. + */ + const e = O.SetData[index]; + index += 1; + if (e !== undefined) { + const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e]))); + if (inOther === Value.false) { + return Value.false; + } + thisSize = O.SetData.length; + } + } + + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.issupersetof */ +function* SetProto_isSupersetOf([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + const otherRec = Q(yield* GetSetRecord(other)); + if (SetDataSize(O.SetData).value < otherRec.Size) { + return Value.false; + } + + const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys)); + let next: Value | 'done' | 'not-started' = 'not-started'; + while (next !== 'done') { + /* + a. Set next to ? IteratorStepValue(keysIter). + b. If next is not done, then + i. If SetDataHas(O.[[SetData]], next) is false, then + 1. Perform ? IteratorClose(keysIter, NormalCompletion(unused)). + 2. Return false. + */ + next = Q(yield* IteratorStepValue(keysIter)); + if (next !== 'done' && !SetDataHas(O.SetData, next)) { + Q(yield* IteratorClose(keysIter, NormalCompletion(undefined))); + return Value.false; + } + } + + return Value.true; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.symmetricdifference */ +function* SetProto_symmetricDifference([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + + // 2. Perform ? RequireInternalSlot(O, [[SetData]]). + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + // 3. Let otherRec be ? GetSetRecord(other). + const otherRec = Q(yield* GetSetRecord(other)); + + // 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]). + const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys)); + // 5. Let resultSetData be a copy of O.[[SetData]]. + const resultSetData = [...O.SetData]; + // 6. Let next be not-started. + let next: Value | 'done' | 'not-started' = 'not-started'; + + while (next !== 'done') { + /* + a. Set next to ? IteratorStepValue(keysIter). + b. If next is not done, then + i. Set next to CanonicalizeKeyedCollectionKey(next). + ii. Let resultIndex be SetDataIndex(resultSetData, next). + iii. If resultIndex is not-found, let alreadyInResult be false. Otherwise let alreadyInResult be true. + iv. If SetDataHas(O.[[SetData]], next) is true, then + 1. If alreadyInResult is true, set resultSetData[resultIndex] to empty. + v. Else, + 1. If alreadyInResult is false, append next to resultSetData. + */ + next = Q(yield* IteratorStepValue(keysIter)); + if (next !== 'done') { + next = CanonicalizeKeyedCollectionKey(next); + const resultIndex: number | 'not-found' = SetDataIndex(resultSetData, next); + if (SetDataHas(O.SetData, next) === true) { + if (resultIndex !== 'not-found') { + resultSetData[resultIndex] = undefined; + } + } else { + if ((resultIndex === 'not-found')) { + resultSetData.push(next); + } + } + } + } + /* + 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »). + 9. Set result.[[SetData]] to resultSetData. + 10. Return result. + */ + + const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable; + result.SetData = resultSetData; + return result; +} + +/** https://tc39.es/ecma262/#sec-set.prototype.values */ +function SetProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue; + // 2. Return ? CreateSetIterator(S, value). + return Q(CreateSetIterator(S, 'value')); +} + +/** https://tc39.es/ecma262/#sec-set.prototype.union */ +function* SetProto_union([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be the this value. + const O = thisValue; + + // 2. Perform ? RequireInternalSlot(O, [[SetData]]). + Q(RequireInternalSlot(O, 'SetData')); + __ts_cast__(O); + + // 3. Let otherRec be ? GetSetRecord(other). + const otherRec = Q(yield* GetSetRecord(other)); + + // 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]). + const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys)); + + // 5. Let resultSetData be a copy of O.[[SetData]]. + const resultSetData = [...O.SetData]; + + // 6. Let next be true. + let next: Value | 'done' = Value.true; + + // 7. Repeat, while next is not DONE, + while (next !== 'done') { + // a. Set next to ? IteratorStep(keysIter). + next = Q(yield* IteratorStep(keysIter)); + + // b. If next is not DONE, then + if (next !== 'done') { + // i. Let nextValue be ? IteratorValue(next). + let nextValue = Q(yield* IteratorValue(next)); + + // ii. If nextValue is -0𝔽, set nextValue to +0𝔽. + if (nextValue instanceof NumberValue && Object.is(R(nextValue), -0)) { + nextValue = F(+0); + } + + // iii. If SetDataHas(resultSetData, nextValue) is false, then + if (!SetDataHas(resultSetData, nextValue)) { + // 1. Append nextValue to resultSetData. + resultSetData.push(nextValue); + } + } + } + + // 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »). + const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable; + + // 9. Set result.[[SetData]] to resultSetData. + result.SetData = resultSetData; + + // 10. Return result. + return EnsureCompletion(result); +} + +export function bootstrapSetPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['add', SetProto_add, 1], + ['clear', SetProto_clear, 0], + ['delete', SetProto_delete, 1], + ['difference', SetProto_difference, 1], + ['entries', SetProto_entries, 0], + ['forEach', SetProto_forEach, 1], + ['has', SetProto_has, 1], + ['intersection', SetProto_intersection, 1], + ['isDisjointFrom', SetProto_isDisjointFrom, 1], + ['isSubsetOf', SetProto_isSubsetOf, 1], + ['isSupersetOf', SetProto_isSupersetOf, 1], + ['size', [SetProto_sizeGetter]], + ['symmetricDifference', SetProto_symmetricDifference, 1], + ['values', SetProto_values, 0], + ['union', SetProto_union, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'Set'); + + const valuesFunc = X(proto.GetOwnProperty(Value('values'))) as Descriptor; + X(proto.DefineOwnProperty(Value('keys'), valuesFunc)); + X(proto.DefineOwnProperty(wellKnownSymbols.iterator, valuesFunc)); + + realmRec.Intrinsics['%Set.prototype%'] = proto; +} + +interface SetRecord { + readonly SetObject: ObjectValue; + readonly Size: number; + readonly Has: Value; + readonly Keys: FunctionObject; +} + +/** https://tc39.es/ecma262/#sec-getsetrecord */ +function* GetSetRecord(obj: Value): PlainEvaluator { + // 1. If obj is not an Object, throw a TypeError exception. + if (!(obj instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', obj); + } + + // 2. Let rawSize be ? Get(obj, "size"). + const rawSize = Q(yield* Get(obj, Value('size'))); + + // 3. Let numSize be ? ToNumber(rawSize). + // 4. NOTE: If rawSize is undefined, then numSize will be NaN. + const numSize = Q(yield* ToNumber(rawSize)); + + // 5. If numSize is NaN, throw a TypeError exception. + if (numSize.isNaN()) { + return surroundingAgent.Throw('TypeError', 'SizeIsNaN'); + } + + // 6. Let intSize be ! ToIntegerOrInfinity(numSize). + const intSize = X(ToIntegerOrInfinity(numSize)); + + // 7. If intSize < 0, throw a RangeError exception. + if (intSize < 0) { + return surroundingAgent.Throw('RangeError', 'SizeMustBePositiveInteger'); + } + + // 8. Let has be ? Get(obj, "has"). + const has = Q(yield* Get(obj, Value('has'))); + + // 9. If IsCallable(has) is false, throw a TypeError exception. + if (!IsCallable(has)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', has); + } + + // 10. Let keys be ? Get(obj, "keys"). + const keys = Q(yield* Get(obj, Value('keys'))); + + // 11. If IsCallable(keys) is false, throw a TypeError exception. + if (!IsCallable(keys)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', keys); + } + + // 12. Return a new Set Record { [[Set]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }. + const setRecord: SetRecord = { + SetObject: obj, + Size: intSize, + Has: has, + Keys: keys, + }; + + return EnsureCompletion(setRecord); +} + +/** https://tc39.es/ecma262/#sec-setdatahas */ +function SetDataHas(resultSetData: (Value | undefined)[], value: Value): boolean { + return SetDataIndex(resultSetData, value) !== 'not-found'; +} + +/** https://tc39.es/ecma262/#sec-setdataindex */ +function SetDataIndex(setData: (Value | undefined)[], value: Value): number | 'not-found' { + /* + 1. Set value to CanonicalizeKeyedCollectionKey(value). + 2. Let size be the number of elements in setData. + 3. Let index be 0. + 4. Repeat, while index < size, + a. Let e be setData[index]. + b. If e is not empty and e is value, then + i. Return index. + c. Set index to index + 1. + 5. Return not-found. + */ + value = CanonicalizeKeyedCollectionKey(value); + const size = setData.length; + let index = 0; + while (index < size) { + const e = setData[index]; + if (e !== undefined && SameValueZero(e, value) === Value.true) { + return index; + } + index += 1; + } + return 'not-found'; +} + +/** https://tc39.es/ecma262/#sec-setdatasize */ +function SetDataSize(setData: (Value | undefined)[]) { + let count = 0; + for (const e of setData) { + if (e !== undefined) { + count += 1; + } + } + return F(count); +} diff --git a/src/intrinsics/ShadowRealm.mts b/src/intrinsics/ShadowRealm.mts new file mode 100644 index 0000000..07093e7 --- /dev/null +++ b/src/intrinsics/ShadowRealm.mts @@ -0,0 +1,72 @@ +import { + Descriptor, + UndefinedValue, + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Assert, + MakeRealm, + isOrdinaryObject, + OrdinaryCreateFromConstructor, + Realm, + type FunctionObject, + type OrdinaryObject, + type Mutable, +} from '#self'; + +export interface ShadowRealmObject extends OrdinaryObject { + readonly ShadowRealm: Realm; +} + +export function isShadowRealmObject(value: Value): value is ShadowRealmObject { + return 'ShadowRealm' in value; +} + +/** https://tc39.es/ecma262/#sec-symbol-description */ +function* ShadowRealmConstructor(this: FunctionObject, _args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + Q(surroundingAgent.debugger_cannotPreview); + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%ShadowRealm.prototype%', ['ShadowRealm'])) as Mutable; + // Note: wait for https://github.com/tc39/ecma262/pull/3728 + const realm = Q(MakeRealm({ + name: 'ShadowRealm', + specifier: surroundingAgent.currentRealmRecord.HostDefined.specifier, + })); + const innerContext = realm.topContext; + + const realmRec = innerContext.Realm; + O.ShadowRealm = realmRec; + + const hostHookCompletion = surroundingAgent.hostDefinedOptions.hostHooks?.HostInitializeShadowRealm?.(realmRec, innerContext, O); + if (typeof hostHookCompletion === 'object' && hostHookCompletion && 'next' in hostHookCompletion) { + Q(yield* hostHookCompletion); + } else { + Q(hostHookCompletion); + } + + Assert(isOrdinaryObject(realmRec.GlobalObject)); + return O; +} + +export function bootstrapShadowRealm(realmRec: Realm) { + const shadowRealmConstructor = bootstrapConstructor(realmRec, ShadowRealmConstructor, 'ShadowRealm', 0, realmRec.Intrinsics['%ShadowRealm.prototype%'], [ + ]); + + X(shadowRealmConstructor.DefineOwnProperty(Value('prototype'), Descriptor({ + Value: realmRec.Intrinsics['%ShadowRealm.prototype%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%ShadowRealm%'] = shadowRealmConstructor; +} diff --git a/src/intrinsics/ShadowRealmPrototype.mts b/src/intrinsics/ShadowRealmPrototype.mts new file mode 100644 index 0000000..b58b956 --- /dev/null +++ b/src/intrinsics/ShadowRealmPrototype.mts @@ -0,0 +1,47 @@ +import { __ts_cast__ } from '../helpers.mts'; +import { PerformShadowRealmEval, ShadowRealmImportValue, ValidateShadowRealmObject } from '../abstract-ops/shadow-realm.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { type ShadowRealmObject } from './ShadowRealm.mts'; +import { + Realm, +} from '#self'; +import { + JSStringValue, Q, surroundingAgent, ToString, Value, type Arguments, type FunctionCallContext, type ValueEvaluator, +} from '#self'; + +/** https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.evaluate */ +function* ShadowRealmPrototype_evaluate([sourceText = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + Q(surroundingAgent.debugger_cannotPreview); + const O = thisValue; + Q(ValidateShadowRealmObject(O)); + __ts_cast__(O); + if (!(sourceText instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', sourceText); + } + const callerRealm = surroundingAgent.currentRealmRecord; + const evalRealm = O.ShadowRealm; + return Q(yield* PerformShadowRealmEval(sourceText.stringValue(), callerRealm, evalRealm)); +} + +/** https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue */ +function* ShadowRealmPrototype_importValue([specifier = Value.undefined, exportName = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + Q(surroundingAgent.debugger_cannotPreview); + const O = thisValue; + Q(ValidateShadowRealmObject(O)); + __ts_cast__(O); + const specifierString = Q(yield* ToString(specifier)); + if (!(exportName instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', exportName); + } + const callerRealm = surroundingAgent.currentRealmRecord; + const evalRealm = O.ShadowRealm; + return ShadowRealmImportValue(specifierString, exportName, callerRealm, evalRealm); +} + +export function bootstrapShadowRealmPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['evaluate', ShadowRealmPrototype_evaluate, 1], + ['importValue', ShadowRealmPrototype_importValue, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'ShadowRealm'); + realmRec.Intrinsics['%ShadowRealm.prototype%'] = proto; +} diff --git a/src/intrinsics/String.mts b/src/intrinsics/String.mts new file mode 100644 index 0000000..544e169 --- /dev/null +++ b/src/intrinsics/String.mts @@ -0,0 +1,138 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BooleanValue, + JSStringValue, + NullValue, + ObjectValue, + SymbolValue, + UndefinedValue, + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { UTF16EncodeCodePoint } from '../static-semantics/all.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Assert, + Get, + GetPrototypeFromConstructor, + IsIntegralNumber, + StringCreate, + SymbolDescriptiveString, + LengthOfArrayLike, + ToNumber, + ToObject, + ToString, + ToUint16, + F, R, + type ExoticObject, + Realm, + type CodePoint, +} from '#self'; + +export interface StringObject extends ExoticObject { + readonly StringData: JSStringValue; + Prototype: ObjectValue | NullValue; + Extensible: BooleanValue; +} +export function isStringObject(o: Value): o is StringObject { + return 'StringData' in o; +} +/** https://tc39.es/ecma262/#sec-string-constructor-string-value */ +function* StringConstructor([value]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + let s; + if (value === undefined) { + s = Value(''); + } else { + if (NewTarget === Value.undefined && value instanceof SymbolValue) { + return X(SymbolDescriptiveString(value)); + } + s = Q(yield* ToString(value)); + } + if (NewTarget instanceof UndefinedValue) { + return s; + } + return X(StringCreate(s, Q(yield* GetPrototypeFromConstructor(NewTarget, '%String.prototype%')))); +} + +/** https://tc39.es/ecma262/#sec-string.fromcharcode */ +function* String_fromCharCode(codeUnits: Arguments): ValueEvaluator { + const length = codeUnits.length; + const elements = []; + let nextIndex = 0; + while (nextIndex < length) { + const next = codeUnits[nextIndex]!; + const nextCU = Q(yield* ToUint16(next)); + elements.push(nextCU); + nextIndex += 1; + } + const result = elements.reduce((previous, current) => previous + String.fromCharCode(R(current)), ''); + return Value(result); +} + +/** https://tc39.es/ecma262/#sec-string.fromcodepoint */ +function* String_fromCodePoint(codePoints: Arguments) { + // 1. Let result be the empty String. + let result = ''; + // 2. For each element next of codePoints, do + for (const next of codePoints.values()) { + // a. Let nextCP be ? ToNumber(next). + const nextCP = Q(yield* ToNumber(next)); + // b. If IsIntegralNumber(nextCP) is false, throw a RangeError exception. + if (X(IsIntegralNumber(nextCP)) === Value.false) { + return surroundingAgent.Throw('RangeError', 'StringCodePointInvalid', next); + } + // c. If ℝ(nextCP) < 0 or ℝ(nextCP) > 0x10FFFF, throw a RangeError exception. + if (R(nextCP) < 0 || R(nextCP) > 0x10FFFF) { + return surroundingAgent.Throw('RangeError', 'StringCodePointInvalid', nextCP); + } + // d. Set result to the string-concatenation of result and UTF16EncodeCodePoint(ℝ(nextCP)). + result += UTF16EncodeCodePoint(R(nextCP) as CodePoint); + } + // 3. Assert: If codePoints is empty, then result is the empty String. + Assert(!(codePoints.length === 0) || result.length === 0); + // 4. Return result. + return Value(result); +} + +/** https://tc39.es/ecma262/#sec-string.raw */ +function* String_raw([template = Value.undefined, ...substitutions]: Arguments): ValueEvaluator { + const numberOfSubstitutions = substitutions.length; + const cooked = Q(ToObject(template)); + const raw = Q(ToObject(Q(yield* Get(cooked, Value('raw'))))); + const literalSegments = Q(yield* LengthOfArrayLike(raw)); + if (literalSegments <= 0) { + return 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(F(nextIndex))); + const nextSeg = Q(yield* ToString(Q(yield* Get(raw, nextKey)))); + stringElements.push(nextSeg.stringValue()); + if (nextIndex + 1 === literalSegments) { + return Value(stringElements.join('')); + } + let next; + if (nextIndex < numberOfSubstitutions) { + next = substitutions[nextIndex]; + } else { + next = Value(''); + } + const nextSub = Q(yield* ToString(next!)); + stringElements.push(nextSub.stringValue()); + nextIndex += 1; + } +} + +export function bootstrapString(realmRec: Realm) { + 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/src/intrinsics/StringIteratorPrototype.mts b/src/intrinsics/StringIteratorPrototype.mts new file mode 100644 index 0000000..054961a --- /dev/null +++ b/src/intrinsics/StringIteratorPrototype.mts @@ -0,0 +1,23 @@ +import { Q, type ValueEvaluator } from '../completion.mts'; +import { + Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + GeneratorResume, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-%stringiteratorprototype%.next */ +function* StringIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Return ? GeneratorResume(this value, empty, "%StringIteratorPrototype%"). + return Q(yield* GeneratorResume(thisValue, undefined, Value('%StringIteratorPrototype%'))); +} + +export function bootstrapStringIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', StringIteratorPrototype_next, 0], + ], realmRec.Intrinsics['%Iterator.prototype%'], 'String Iterator'); + + realmRec.Intrinsics['%StringIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/StringPrototype.mts b/src/intrinsics/StringPrototype.mts new file mode 100644 index 0000000..9a64469 --- /dev/null +++ b/src/intrinsics/StringPrototype.mts @@ -0,0 +1,858 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, + JSStringValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, + UndefinedValue, +} from '../value.mts'; +import { + GetSubstitution, + TrimString, + StringPad, + StringIndexOf, +} from '../runtime-semantics/all.mts'; +import { + CodePointAt, + IsStringWellFormedUnicode, + UTF16EncodeCodePoint, +} from '../static-semantics/all.mts'; +import { Q, X } from '../completion.mts'; +import type { ValueEvaluator, YieldEvaluator } from '../evaluator.mts'; +import { assignProps } from './bootstrap.mts'; +import { + ArrayCreate, + Assert, + Call, + CreateDataPropertyOrThrow, + CreateIteratorFromClosure, + Get, + GetMethod, + Invoke, + IsCallable, + IsRegExp, + RegExpCreate, + RequireObjectCoercible, + ToIntegerOrInfinity, + ToNumber, + ToString, + ToUint32, + StringCreate, + Yield, + F, R, R as MathematicalValue, + Realm, +} from '#self'; + + +function thisStringValue(value: Value) { + if (value instanceof JSStringValue) { + return value; + } + if (value instanceof ObjectValue && 'StringData' in value) { + const s = value.StringData; + Assert(s instanceof JSStringValue); + return s; + } + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'String', value); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.charat */ +function* StringProto_charAt([pos = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const position = Q(yield* ToIntegerOrInfinity(pos)); + const size = S.stringValue().length; + if (position < 0 || position >= size) { + return Value(''); + } + return Value(S.stringValue()[position]); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.charcodeat */ +function* StringProto_charCodeAt([pos = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const position = Q(yield* ToIntegerOrInfinity(pos)); + const size = S.stringValue().length; + if (position < 0 || position >= size) { + return F(NaN); + } + return F(S.stringValue().charCodeAt(position)); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.codepointat */ +function* StringProto_codePointAt([pos = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const position = Q(yield* ToIntegerOrInfinity(pos)); + const size = S.stringValue().length; + if (position < 0 || position >= size) { + return Value.undefined; + } + const cp = X(CodePointAt(S.stringValue(), position)); + return F(cp.CodePoint); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.concat */ +function* StringProto_concat(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + let R = S.stringValue(); + const _args = [...args]; + while (_args.length > 0) { + const next = _args.shift()!; + const nextString = Q(yield* ToString(next)); + R = `${R}${nextString.stringValue()}`; + } + return Value(R); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.endswith */ +function* StringProto_endsWith([searchString = Value.undefined, endPosition = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const isRegExp = Q(yield* IsRegExp(searchString)); + if (isRegExp === Value.true) { + return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.endsWith'); + } + const searchStr = Q(yield* ToString(searchString)).stringValue(); + const len = S.length; + let pos; + if (endPosition === Value.undefined) { + pos = len; + } else { + pos = Q(yield* ToIntegerOrInfinity(endPosition)); + } + 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; +} + +/** https://tc39.es/ecma262/#sec-string.prototype.includes */ +function* StringProto_includes([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const isRegExp = Q(yield* IsRegExp(searchString)); + if (isRegExp === Value.true) { + return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.includes'); + } + const searchStr = Q(yield* ToString(searchString)).stringValue(); + const pos = Q(yield* ToIntegerOrInfinity(position)); + Assert(!(position === Value.undefined) || pos === 0); + const len = S.length; + const start = Math.min(Math.max(pos, 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; +} + +/** https://tc39.es/ecma262/#sec-string.prototype.indexof */ +function* StringProto_indexOf([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. Let S be ? ToString(O). + const S = Q(yield* ToString(O)); + // 3. Let searchStr be ? ToString(searchString). + const searchStr = Q(yield* ToString(searchString)); + // 4. Let pos be ? ToIntegerOrInfinity(position). + const pos = Q(yield* ToIntegerOrInfinity(position)); + // 5. Assert: If position is undefined, then pos is 0. + Assert(!(position === Value.undefined) || pos === 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, 0), len); + // 8. Return ! StringIndexOf(S, searchStr, start). + return X(StringIndexOf(S, searchStr, start)); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.iswellformed */ +function* StringProto_isWellFormed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. Let S be ? ToString(O). + const S = Q(yield* ToString(O)); + // 3. Return IsStringWellFormedUnicode(S). + return IsStringWellFormedUnicode(S) ? Value.true : Value.false; +} + +/** https://tc39.es/ecma262/#sec-string.prototype.lastindexof */ +function* StringProto_lastIndexOf([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const searchStr = Q(yield* ToString(searchString)).stringValue(); + const numPos = Q(yield* ToNumber(position)); + Assert(!(position === Value.undefined) || numPos.isNaN()); + let pos; + if (numPos.isNaN()) { + pos = Infinity; + } else { + pos = X(ToIntegerOrInfinity(numPos)); + } + const len = S.length; + const start = Math.min(Math.max(pos, 0), len); + const searchLen = searchStr.length; + if (len < searchLen) { + return F(-1); + } + 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 F(k); + } + } + k -= 1; + } + return F(-1); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.localecompare */ +function* StringProto_localeCompare([that = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const That = Q(yield* ToString(that)).stringValue(); + if (S === That) { + return F(+0); + } else if (S < That) { + return F(-1); + } else { + return F(1); + } +} + +/** https://tc39.es/ecma262/#sec-string.prototype.match */ +function* StringProto_match([regexp = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + + if (regexp instanceof ObjectValue) { + const matcher = Q(yield* GetMethod(regexp, wellKnownSymbols.match)); + if (matcher !== Value.undefined) { + return Q(yield* Call(matcher, regexp, [O])); + } + } + + const S = Q(yield* ToString(O)); + const rx = Q(yield* RegExpCreate(regexp, Value.undefined)); + return Q(yield* Invoke(rx, wellKnownSymbols.match, [S])); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.matchall */ +function* StringProto_matchAll([regexp = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. If regexp is an Object, then + if (regexp instanceof ObjectValue) { + // a. Let isRegExp be ? IsRegExp(regexp). + const isRegExp = Q(yield* IsRegExp(regexp)); + // b. If isRegExp is true, then + if (isRegExp === Value.true) { + // i. Let flags be ? Get(regexp, "flags"). + const flags = Q(yield* Get(regexp as ObjectValue, Value('flags'))); + // ii. Perform ? RequireObjectCoercible(flags). + Q(RequireObjectCoercible(flags)); + // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. + if (!Q(yield* ToString(flags)).stringValue().includes('g')) { + return surroundingAgent.Throw('TypeError', 'StringPrototypeMethodGlobalRegExp', 'matchAll'); + } + } + // c. Let matcher be ? GetMethod(regexp, @@matchAll). + const matcher = Q(yield* GetMethod(regexp, wellKnownSymbols.matchAll)); + // d. If matcher is not undefined, then + if (matcher !== Value.undefined) { + // i. Return ? Call(matcher, regexp, « O »). + return Q(yield* Call(matcher, regexp, [O])); + } + } + // 3. Let S be ? ToString(O). + const S = Q(yield* ToString(O)); + // 4. Let rx be ? RegExpCreate(regexp, "g"). + const rx = Q(yield* RegExpCreate(regexp, Value('g'))); + // 5. Return ? Invoke(rx, @@matchAll, « S »). + return Q(yield* Invoke(rx, wellKnownSymbols.matchAll, [S])); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.normalize */ +function* StringProto_normalize([form = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + if (form === Value.undefined) { + form = Value('NFC'); + } else { + form = Q(yield* 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 Value(ns); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.padend */ +function* StringProto_padEnd([maxLength = Value.undefined, fillString = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + return Q(yield* StringPad(O, maxLength, fillString, 'end')); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.padstart */ +function* StringProto_padStart([maxLength = Value.undefined, fillString = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + return Q(yield* StringPad(O, maxLength, fillString, 'start')); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.repeat */ +function* StringProto_repeat([count = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const n = Q(yield* ToIntegerOrInfinity(count)); + if (n < 0) { + return surroundingAgent.Throw('RangeError', 'StringRepeatCount', n); + } + if (n === Infinity || n === -Infinity) { + return surroundingAgent.Throw('RangeError', 'StringRepeatCount', n); + } + if (n === 0) { + return Value(''); + } + let T = ''; + for (let i = 0; i < n; i += 1) { + T += S.stringValue(); + } + return Value(T); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.replace */ +function* StringProto_replace([searchValue = Value.undefined, replaceValue = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + if (searchValue instanceof ObjectValue) { + const replacer = Q(yield* GetMethod(searchValue, wellKnownSymbols.replace)); + if (replacer !== Value.undefined) { + return Q(yield* Call(replacer, searchValue, [O, replaceValue])); + } + } + const string = Q(yield* ToString(O)); + const searchString = Q(yield* ToString(searchValue)); + const functionalReplace = IsCallable(replaceValue); + if (!functionalReplace) { + replaceValue = Q(yield* ToString(replaceValue)); + } + const searchLength = searchString.stringValue().length; + const position = string.stringValue().indexOf(searchString.stringValue(), 0); + if (position === -1) { + return string; + } + const preceding = string.stringValue().slice(0, position); + const following = string.stringValue().slice(position + searchLength); + let replacement: JSStringValue; + if (functionalReplace) { + replacement = Q(yield* ToString(Q(yield* Call(replaceValue, Value.undefined, [searchString, F(position), string])))); + } else { + Assert(replaceValue instanceof JSStringValue); + const captures: readonly (JSStringValue | UndefinedValue)[] = []; + replacement = X(GetSubstitution(searchString, string, position, captures, Value.undefined, replaceValue)); + } + return Value(preceding + replacement.stringValue() + following); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.replaceall */ +function* StringProto_replaceAll([searchValue = Value.undefined, replaceValue = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2.If searchValue is an Object, then + if (searchValue instanceof ObjectValue) { + // a. Let isRegExp be ? IsRegExp(searchValue). + const isRegExp = Q(yield* IsRegExp(searchValue)); + // b. If isRegExp is true, then + if (isRegExp === Value.true) { + // i. Let flags be ? Get(searchValue, "flags"). + const flags = Q(yield* Get(searchValue as ObjectValue, Value('flags'))); + // ii. Perform ? RequireObjectCoercible(flags). + Q(RequireObjectCoercible(flags)); + // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. + if (!Q(yield* ToString(flags)).stringValue().includes('g')) { + return surroundingAgent.Throw('TypeError', 'StringPrototypeMethodGlobalRegExp', 'replaceAll'); + } + } + // c. Let replacer be ? GetMethod(searchValue, @@replace). + const replacer = Q(yield* GetMethod(searchValue, wellKnownSymbols.replace)); + // d. If replacer is not undefined, then + if (replacer !== Value.undefined) { + // i. Return ? Call(replacer, searchValue, « O, replaceValue »). + return Q(yield* Call(replacer, searchValue, [O, replaceValue])); + } + } + // 3. Let string be ? ToString(O). + const string = Q(yield* ToString(O)); + // 4. Let searchString be ? ToString(searchValue). + const searchString = Q(yield* ToString(searchValue)); + // 5. Let functionalReplace be IsCallable(replaceValue). + const functionalReplace = IsCallable(replaceValue); + // 6. If functionalReplace is false, then + if (!functionalReplace) { + // a. Let replaceValue be ? ToString(replaceValue). + replaceValue = Q(yield* 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 = R(X(StringIndexOf(string, searchString, 0))); + // 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 = R(X(StringIndexOf(string, searchString, position + advanceBy))); + } + // 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) { + // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »). + replacement = Q(yield* ToString(Q(yield* Call(replaceValue, Value.undefined, [searchString, F(position), string])))); + } else { // b. Else, + // i. Assert: Type(replaceValue) is String. + Assert(replaceValue instanceof JSStringValue); + // ii. Let captures be a new empty List. + const captures: readonly (JSStringValue | UndefinedValue)[] = []; + // iii. Let replacement be GetSubstitution(searchString, string, position, captures, undefined, replaceValue). + replacement = X(GetSubstitution(searchString, string, 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 Value(result); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.slice */ +function* StringProto_search([regexp = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + + if (regexp instanceof ObjectValue) { + const searcher = Q(yield* GetMethod(regexp, wellKnownSymbols.search)); + if (searcher !== Value.undefined) { + return Q(yield* Call(searcher, regexp, [O])); + } + } + + const string = Q(yield* ToString(O)); + const rx = Q(yield* RegExpCreate(regexp, Value.undefined)); + return Q(yield* Invoke(rx, wellKnownSymbols.search, [string])); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.slice */ +function* StringProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const len = S.length; + const intStart = Q(yield* ToIntegerOrInfinity(start)); + let intEnd; + if (end === Value.undefined) { + intEnd = len; + } else { + intEnd = Q(yield* ToIntegerOrInfinity(end)); + } + 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 Value(S.slice(from, from + span)); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.split */ +function* StringProto_split([separator = Value.undefined, limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + if (separator instanceof ObjectValue) { + const splitter = Q(yield* GetMethod(separator, wellKnownSymbols.split)); + if (splitter !== Value.undefined) { + return Q(yield* Call(splitter, separator, [O, limit])); + } + } + const S = Q(yield* ToString(O)); + const A = X(ArrayCreate(0)); + let lengthA = 0; + let lim; + if (limit === Value.undefined) { + lim = F((2 ** 32) - 1); + } else { + lim = Q(yield* ToUint32(limit)); + } + const s = S.stringValue().length; + let p = 0; + const R = Q(yield* ToString(separator)); + if (MathematicalValue(lim) === 0) { + return A; + } + if (separator === Value.undefined) { + X(CreateDataPropertyOrThrow(A, Value('0'), S)); + return A; + } + if (s === 0) { + if (R.stringValue() !== '') { + X(CreateDataPropertyOrThrow(A, Value('0'), S)); + } + return A; + } + let q = p; + while (q !== s) { + const e = yield* SplitMatch(S, q, R); + if (e === false) { + q += 1; + } else { + if (e === p) { + q += 1; + } else { + const T = Value(S.stringValue().substring(p, q)); + X(CreateDataPropertyOrThrow(A, X(ToString(F(lengthA))), T)); + lengthA += 1; + if (lengthA === MathematicalValue(lim)) { + return A; + } + p = e; + q = p; + } + } + } + const T = Value(S.stringValue().substring(p, s)); + X(CreateDataPropertyOrThrow(A, X(ToString(F(lengthA))), T)); + return A; +} + +/** https://tc39.es/ecma262/#sec-splitmatch */ +function* SplitMatch(S: JSStringValue, q: number, R: JSStringValue) { + Assert(R instanceof JSStringValue); + 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; +} + +/** https://tc39.es/ecma262/#sec-string.prototype.startswith */ +function* StringProto_startsWith([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const isRegExp = Q(yield* IsRegExp(searchString)); + if (isRegExp === Value.true) { + return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.startsWith'); + } + const searchStr = Q(yield* ToString(searchString)).stringValue(); + const pos = Q(yield* ToIntegerOrInfinity(position)); + 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; +} + +/** https://tc39.es/ecma262/#sec-string.prototype.substring */ +function* StringProto_substring([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)).stringValue(); + const len = S.length; + const intStart = Q(yield* ToIntegerOrInfinity(start)); + let intEnd; + if (end === Value.undefined) { + intEnd = len; + } else { + intEnd = Q(yield* ToIntegerOrInfinity(end)); + } + 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 Value(S.slice(from, to)); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.tolocalelowercase */ +function* StringProto_toLocaleLowerCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const L = S.stringValue().toLocaleLowerCase(); + return Value(L); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.tolocaleuppercase */ +function* StringProto_toLocaleUpperCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const L = S.stringValue().toLocaleUpperCase(); + return Value(L); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.tolowercase */ +function* StringProto_toLowerCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const L = S.stringValue().toLowerCase(); + return Value(L); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.tostring */ +function* StringProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + return Q(thisStringValue(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.touppercase */ +function* StringProto_toUpperCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + const S = Q(yield* ToString(O)); + const L = S.stringValue().toUpperCase(); + return Value(L); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.towellformed */ +function* StringProto_toWellFormed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. Let S be ? ToString(O). + const S = Q(yield* ToString(O)); + // 3. Let strLen be the length of S. + const strLen = S.stringValue().length; + // 4. Let k be 0. + let k = 0; + // 5. Let result be the empty String. + let result = ''; + // 6. Repeat, while k < strLen, + while (k < strLen) { + // a. Let cp be CodePointAt(S, k). + const cp = CodePointAt(S.stringValue(), k); + // b. If cp.[[IsUnpairedSurrogate]] is true, then + if (cp.IsUnpairedSurrogate) { + // i. Set result to the string-concatenation of result and 0xFFFD (REPLACEMENT CHARACTER). + result += '\uFFFD'; + } else { // c. Else, + // i. Set result to the string-concatenation of result and UTF16EncodeCodePoint(cp.[[CodePoint]]). + result += UTF16EncodeCodePoint(cp.CodePoint); + } + // d. Set k to k + cp.[[CodeUnitCount]]. + k += cp.CodeUnitCount; + } + // 7. Return result. + return Value(result); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.trim */ +function* StringProto_trim(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const S = thisValue; + return Q(yield* TrimString(S, 'start+end')); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.trimend */ +function* StringProto_trimEnd(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const S = thisValue; + return Q(yield* TrimString(S, 'end')); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.trimstart */ +function* StringProto_trimStart(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const S = thisValue; + return Q(yield* TrimString(S, 'start')); +} + +/** https://tc39.es/ecma262/#sec-string.prototype.valueof */ +function* StringProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + return Q(thisStringValue(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-string.prototype-@@iterator */ +function* StringProto_iterator(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. Let s be ? ToString(O). + const s = Q(yield* ToString(O)).stringValue(); + // 3. Let closure be a new Abstract Closure with no parameters that captures s and performs the following steps when called: + const closure = function* closure(): YieldEvaluator { + // a. Let position be 0. + let position = 0; + // b. Let len be the length of s. + const len = s.length; + // c. Repeat, while position < len, + while (position < len) { + // i. Let cp be ! CodePointAt(s, position). + const cp = X(CodePointAt(s, position)); + // ii. Let nextIndex be position + cp.[[CodeUnitCount]]. + const nextIndex = position + cp.CodeUnitCount; + // iii. Let resultString be the substring of s from position to nextIndex. + const resultString = Value(s.slice(position, nextIndex)); + // iv. Set position to nextIndex. + position = nextIndex; + // v. Perform ? Yield(resultString). + Q(yield* Yield(resultString)); + } + // NON-SPEC + generator.HostCapturedValues = undefined; + // d. Return undefined. + return Value.undefined; + }; + // 4. Return ! CreateIteratorFromClosure(closure, "%StringIteratorPrototype%", %StringIteratorPrototype%). + const generator = X(CreateIteratorFromClosure(closure, Value('%StringIteratorPrototype%'), surroundingAgent.intrinsic('%StringIteratorPrototype%'), ['HostCapturedValues'], [O])); + return generator; +} + +/** https://tc39.es/ecma262/#sec-string.prototype.at */ +function* StringProto_at([index = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + Q(RequireObjectCoercible(O)); + // 2. Let S be ? ToString(O). + const S = Q(yield* ToString(O)); + // 3. Let len be the length of S. + const len = S.stringValue().length; + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). + const relativeIndex = Q(yield* ToIntegerOrInfinity(index)); + let k; + // 5. If relativeIndex ≥ 0, then + if (relativeIndex >= 0) { + // a. Let k be relativeIndex. + k = relativeIndex; + } else { // 6. Else, + // a. Let k be len + relativeIndex. + k = len + relativeIndex; + } + // 7. If k < 0 or k ≥ len, then return undefined. + if (k < 0 || k >= len) { + return Value.undefined; + } + // 8. Return the String value consisting of only the code unit at position k in S. + return Value(S.stringValue()[k]); +} + +export function bootstrapStringPrototype(realmRec: Realm) { + const proto = StringCreate(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], + ['isWellFormed', StringProto_isWellFormed, 0], + ['at', StringProto_at, 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], + ['toWellFormed', StringProto_toWellFormed, 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/src/intrinsics/Symbol.mts b/src/intrinsics/Symbol.mts new file mode 100644 index 0000000..1bd7d59 --- /dev/null +++ b/src/intrinsics/Symbol.mts @@ -0,0 +1,106 @@ +import { + Descriptor, + type JSStringValue, + SymbolValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + KeyForSymbol, + Realm, + SameValue, + ToString, + type FunctionObject, + type OrdinaryObject, +} from '#self'; + +export interface GlobalSymbolRegistryRecord { + readonly Key: JSStringValue; + readonly Symbol: SymbolValue; +} +export const GlobalSymbolRegistry: GlobalSymbolRegistryRecord[] = []; + +export interface SymbolObject extends OrdinaryObject { + readonly SymbolData: SymbolValue; +} +export function isSymbolObject(o: Value): o is SymbolObject { + return 'SymbolData' in o; +} +/** https://tc39.es/ecma262/#sec-symbol-description */ +function* SymbolConstructor(this: FunctionObject, [description = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + // 1. If NewTarget is not undefined, throw a TypeError exception. + if (NewTarget !== Value.undefined) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', 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(yield* ToString(description)); + } + // 4. Return a new unique Symbol value whose [[Description]] value is descString. + return new SymbolValue(descString); +} + +/** https://tc39.es/ecma262/#sec-symbol.for */ +function* Symbol_for([key = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let stringKey be ? ToString(key). + const stringKey = Q(yield* 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; +} + +/** https://tc39.es/ecma262/#sec-symbol.keyfor */ +function Symbol_keyFor([sym = Value.undefined]: Arguments) { + // 1. If Type(sym) is not Symbol, throw a TypeError exception. + if (!(sym instanceof SymbolValue)) { + return surroundingAgent.Throw('TypeError', 'NotASymbol', sym); + } + // 2. Return KeyForSymbol(sym). + return KeyForSymbol(sym); +} + +export function bootstrapSymbol(realmRec: Realm) { + 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)) { + X(symbolConstructor.DefineOwnProperty(Value(name), Descriptor({ + Value: sym, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + } + + X(symbolConstructor.DefineOwnProperty(Value('prototype'), Descriptor({ + Value: realmRec.Intrinsics['%Symbol.prototype%'], + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + + realmRec.Intrinsics['%Symbol%'] = symbolConstructor; +} diff --git a/src/intrinsics/SymbolPrototype.mts b/src/intrinsics/SymbolPrototype.mts new file mode 100644 index 0000000..4b63113 --- /dev/null +++ b/src/intrinsics/SymbolPrototype.mts @@ -0,0 +1,83 @@ +import { + ObjectValue, + SymbolValue, + Value, + wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { Q, type ValueCompletion } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + Assert, + Realm, + SymbolDescriptiveString, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-thissymbolvalue */ +function thisSymbolValue(value: Value) { + // 1. If Type(value) is Symbol, return value. + if (value instanceof SymbolValue) { + return value; + } + // 2. If Type(value) is Object and value has a [[SymbolData]] internal slot, then + if (value instanceof ObjectValue && 'SymbolData' in value) { + // a. Let s be value.[[SymbolData]]. + const s = value.SymbolData; + // b. Assert: Type(s) is Symbol. + Assert(s instanceof SymbolValue); + // c. Return s. + return s; + } + // 3. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Symbol', value); +} + +/** https://tc39.es/ecma262/#sec-symbol.prototype.description */ +function SymbolProto_descriptionGetter(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 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; +} + +/** https://tc39.es/ecma262/#sec-symbol.prototype.tostring */ +function SymbolProto_toString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let sym be ? thisSymbolValue(this value). + const sym = Q(thisSymbolValue(thisValue)); + // 2. Return SymbolDescriptiveString(sym). + return SymbolDescriptiveString(sym); +} + +/** https://tc39.es/ecma262/#sec-symbol.prototype.valueof */ +function SymbolProto_valueOf(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Return ? thisSymbolValue(this value). + return Q(thisSymbolValue(thisValue)); +} + +/** https://tc39.es/ecma262/#sec-symbol.prototype-@@toprimitive */ +function SymbolProto_toPrimitive(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Return ? thisSymbolValue(this value). + return Q(thisSymbolValue(thisValue)); +} + +export function bootstrapSymbolPrototype(realmRec: Realm) { + 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/src/intrinsics/Temporal/Duration.mts b/src/intrinsics/Temporal/Duration.mts new file mode 100644 index 0000000..302ef23 --- /dev/null +++ b/src/intrinsics/Temporal/Duration.mts @@ -0,0 +1,139 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { __ts_cast__ } from '../../helpers.mts'; +import { ToIntegerIfIntegral, GetOptionsObject } from '../../abstract-ops/temporal/addition.mts'; +import { bootstrapTemporalDurationPrototype } from './DurationPrototype.mts'; +import { + ObjectValue, Q, Value, type OrdinaryObject, type ValueEvaluator, + type Realm, + type Arguments, + type FunctionCallContext, + F, + UndefinedValue, + Throw, + Add24HourDaysToTimeDuration, + CompareTimeDuration, + CreateTemporalDuration, + DateDurationDays, + DefaultTemporalLargestUnit, + ToInternalDurationRecord, + ToTemporalDuration, + GetTemporalRelativeToOption, + IsCalendarUnit, + TemporalUnitCategory, + AddZonedDateTime, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances */ +export interface TemporalDurationObject extends OrdinaryObject { + readonly InitializedTemporalDuration: never; + readonly Years: number; + readonly Months: number; + readonly Weeks: number; + readonly Days: number; + readonly Hours: number; + readonly Minutes: number; + readonly Seconds: number; + readonly Milliseconds: number; + readonly Microseconds: number; + readonly Nanoseconds: number; +} + +export function isTemporalDurationObject(item: Value): item is TemporalDurationObject { + return item instanceof ObjectValue && 'InitializedTemporalDuration' in item; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration */ +function* DurationConstructor([ + years = Value.undefined, + months = Value.undefined, + weeks = Value.undefined, + days = Value.undefined, + hours = Value.undefined, + minutes = Value.undefined, + seconds = Value.undefined, + milliseconds = Value.undefined, + microseconds = Value.undefined, + nanoseconds = Value.undefined, +]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.Duration constructor cannot be called without new'); + } + const y = years instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(years)); + const mo = months instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(months)); + const w = weeks instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(weeks)); + const d = days instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(days)); + const h = hours instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(hours)); + const m = minutes instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(minutes)); + const s = seconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(seconds)); + const ms = milliseconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(milliseconds)); + const mis = microseconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(microseconds)); + const ns = nanoseconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(nanoseconds)); + return Q(yield* CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.from */ +function* Duration_From([item = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalDuration(item)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.compare */ +function* Duration_Compare([_one = Value.undefined, _two = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalDuration(_one)); + const two = Q(yield* ToTemporalDuration(_two)); + const resolvedOptions = Q(GetOptionsObject(options)); + const relativeToRecord = Q(yield* GetTemporalRelativeToOption(resolvedOptions)); + if (one.Years === two.Years + && one.Months === two.Months + && one.Weeks === two.Weeks + && one.Days === two.Days + && one.Hours === two.Hours + && one.Minutes === two.Minutes + && one.Seconds === two.Seconds + && one.Milliseconds === two.Milliseconds + && one.Microseconds === two.Microseconds + && one.Nanoseconds === two.Nanoseconds) { + return F(0); + } + const zonedRelativeTo = relativeToRecord.ZonedRelativeTo; + const plainRelativeTo = relativeToRecord.PlainRelativeTo; + const largestUnit1 = DefaultTemporalLargestUnit(one); + const largestUnit2 = DefaultTemporalLargestUnit(two); + const duration1 = ToInternalDurationRecord(one); + const duration2 = ToInternalDurationRecord(two); + if (zonedRelativeTo !== undefined + && (TemporalUnitCategory(largestUnit1) === 'date' || TemporalUnitCategory(largestUnit2) === 'date')) { + const timeZone = zonedRelativeTo.TimeZone; + const calendar = zonedRelativeTo.Calendar; + const after1 = Q(AddZonedDateTime(zonedRelativeTo.EpochNanoseconds, timeZone, calendar, duration1, 'constrain')); + const after2 = Q(AddZonedDateTime(zonedRelativeTo.EpochNanoseconds, timeZone, calendar, duration2, 'constrain')); + if (after1 > after2) return F(1); + if (after1 < after2) return F(-1); + return F(0); + } + let days1; + let days2; + if (IsCalendarUnit(largestUnit1) || IsCalendarUnit(largestUnit2)) { + if (plainRelativeTo === undefined) { + return Throw.RangeError('relativeTo option is required when comparing durations with calendar units'); + } + days1 = Q(DateDurationDays(duration1.Date, plainRelativeTo)); + days2 = Q(DateDurationDays(duration2.Date, plainRelativeTo)); + } else { + days1 = one.Days; + days2 = two.Days; + } + const timeDuration1 = Q(Add24HourDaysToTimeDuration(duration1.Time, days1)); + const timeDuration2 = Q(Add24HourDaysToTimeDuration(duration2.Time, days2)); + return F(CompareTimeDuration(timeDuration1, timeDuration2)); +} + +export function bootstrapTemporalDuration(realmRec: Realm) { + const prototype = bootstrapTemporalDurationPrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, DurationConstructor, 'Duration', 0, prototype, [ + ['from', Duration_From, 1], + ['compare', Duration_Compare, 2], + ]); + realmRec.Intrinsics['%Temporal.Duration%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/DurationPrototype.mts b/src/intrinsics/Temporal/DurationPrototype.mts new file mode 100644 index 0000000..71bd823 --- /dev/null +++ b/src/intrinsics/Temporal/DurationPrototype.mts @@ -0,0 +1,424 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { abs } from '../../abstract-ops/math.mts'; +import { __ts_cast__ } from '../../helpers.mts'; +import { + GetOptionsObject, GetRoundingIncrementOption, GetRoundingModeOption, RoundingMode, +} from '../../abstract-ops/temporal/addition.mts'; +import { + type TemporalDurationObject, +} from './Duration.mts'; +import { + AddDurations, + AddTime, + AddZonedDateTime, + AdjustDateDurationRecord, + Assert, + CalendarDateAdd, + CombineDateAndTimeDuration, + CombineISODateAndTimeRecord, + CreateDataPropertyOrThrow, + CreateDateDurationRecord, + CreateNegatedTemporalDuration, + CreateTemporalDuration, + DefaultTemporalLargestUnit, + DifferencePlainDateTimeWithRounding, + DifferencePlainDateTimeWithTotal, + DifferenceZonedDateTimeWithRounding, + DifferenceZonedDateTimeWithTotal, + DurationSign, + F, + GetTemporalFractionalSecondDigitsOption, + GetTemporalRelativeToOption, + GetTemporalUnitValuedOption, + IsCalendarUnit, + JSStringValue, + LargerOfTwoTemporalUnits, + MaximumTemporalDurationRoundingIncrement, + MidnightTimeRecord, + OrdinaryObjectCreate, + Q, + Realm, + RequireInternalSlot, + RoundNumberToIncrement, + RoundTimeDuration, + TemporalDurationFromInternal, + TemporalDurationToString, + TemporalUnit, + TemporalUnitCategory, + Throw, + ToInternalDurationRecord, + ToInternalDurationRecordWith24HourDays, + ToSecondsStringPrecisionRecord, + TotalTimeDuration, + ToTemporalPartialDurationRecord, + UndefinedValue, + ValidateTemporalRoundingIncrement, + ValidateTemporalUnitValue, + Value, + X, + ZeroDateDuration, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type TimeDuration, + type TimeUnit, + type ValueEvaluator, +} from '#self'; + +function thisTemporalDurationValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalDuration')); + return value as TemporalDurationObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years */ +function DurationProto_yearsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Years); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.months */ +function DurationProto_monthsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Months); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.weeks */ +function DurationProto_weeksGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Weeks); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.days */ +function DurationProto_daysGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Days); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.hours */ +function DurationProto_hoursGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Hours); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.minutes */ +function DurationProto_minutesGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Minutes); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.seconds */ +function DurationProto_secondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Seconds); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.milliseconds */ +function DurationProto_millisecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Milliseconds); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.microseconds */ +function DurationProto_microsecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Microseconds); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds */ +function DurationProto_nanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(duration.Nanoseconds); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.sign */ +function DurationProto_signGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return F(DurationSign(duration)); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.blank */ +function DurationProto_blankGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return DurationSign(duration) === 0 ? Value.true : Value.false; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with */ +function* DurationProto_with([_temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + const temporalDurationLike = Q(yield* ToTemporalPartialDurationRecord(_temporalDurationLike)); + const years = temporalDurationLike.Years ?? duration.Years; + const months = temporalDurationLike.Months ?? duration.Months; + const weeks = temporalDurationLike.Weeks ?? duration.Weeks; + const days = temporalDurationLike.Days ?? duration.Days; + const hours = temporalDurationLike.Hours ?? duration.Hours; + const minutes = temporalDurationLike.Minutes ?? duration.Minutes; + const seconds = temporalDurationLike.Seconds ?? duration.Seconds; + const milliseconds = temporalDurationLike.Milliseconds ?? duration.Milliseconds; + const microseconds = temporalDurationLike.Microseconds ?? duration.Microseconds; + const nanoseconds = temporalDurationLike.Nanoseconds ?? duration.Nanoseconds; + return Q(yield* CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.negated */ +function DurationProto_negated(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return CreateNegatedTemporalDuration(duration); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.abs */ +function* DurationProto_abs(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + return X(CreateTemporalDuration( + abs(duration.Years), + abs(duration.Months), + abs(duration.Weeks), + abs(duration.Days), + abs(duration.Hours), + abs(duration.Minutes), + abs(duration.Seconds), + abs(duration.Milliseconds), + abs(duration.Microseconds), + abs(duration.Nanoseconds), + )); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add */ +function* DurationProto_add([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + return Q(yield* AddDurations('add', duration, other)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract */ +function* DurationProto_subtract([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + return Q(yield* AddDurations('subtract', duration, other)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round */ +function* DurationProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + if (roundTo instanceof UndefinedValue) { + return Throw.TypeError('roundTo is required'); + } + if (roundTo instanceof JSStringValue) { + const paramString = roundTo; + roundTo = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString)); + } else { + roundTo = Q(GetOptionsObject(roundTo)); + } + + let smallestUnitPresent = true; + let largestUnitPresent = true; + + const largestUnitOption = Q(yield* GetTemporalUnitValuedOption(roundTo, 'largestUnit', 'unset')); + const relativeToRecord = Q(yield* GetTemporalRelativeToOption(roundTo)); + const zonedRelativeTo = relativeToRecord.ZonedRelativeTo; + const plainRelativeTo = relativeToRecord.PlainRelativeTo; + const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo)); + const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand)); + let smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'unset')); + Q(ValidateTemporalUnitValue(smallestUnit, 'datetime')); + + if (smallestUnit === 'unset') { + smallestUnitPresent = false; + smallestUnit = TemporalUnit.Nanosecond; + } + + const existingLargestUnit = DefaultTemporalLargestUnit(duration); + // TODO(temporal): this assert does not in the spec. + Assert(smallestUnit !== 'auto'); + const defaultLargestUnit = LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit); + let largestUnit; + if (largestUnitOption === 'unset') { + largestUnitPresent = false; + largestUnit = defaultLargestUnit; + } else if (largestUnitOption === 'auto') { + largestUnit = defaultLargestUnit; + } else { + largestUnit = largestUnitOption; + } + + if (!smallestUnitPresent && !largestUnitPresent) { + return Throw.RangeError('smallestUnit and largestUnit cannot both be omitted'); + } + if (LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) { + return Throw.RangeError('largestUnit must be larger than smallestUnit'); + } + + const maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit); + if (maximum !== 'unset') { + Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false)); + } + if (roundingIncrement > 1 && largestUnit !== smallestUnit && TemporalUnitCategory(smallestUnit) === 'date') { + return Throw.RangeError('roundingIncrement must be 1 when rounding a date unit to a larger unit'); + } + + if (zonedRelativeTo !== undefined) { + let internalDuration = ToInternalDurationRecord(duration); + const timeZone = zonedRelativeTo.TimeZone; + const calendar = zonedRelativeTo.Calendar; + const relativeEpochNs = zonedRelativeTo.EpochNanoseconds; + const targetEpochNs = Q(AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, 'constrain')); + internalDuration = Q(DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode)); + if (TemporalUnitCategory(largestUnit) === 'date') { + largestUnit = TemporalUnit.Hour; + } + return Q(yield* TemporalDurationFromInternal(internalDuration, largestUnit)); + } + + if (plainRelativeTo !== undefined) { + let internalDuration = ToInternalDurationRecordWith24HourDays(duration); + const targetTime = AddTime(MidnightTimeRecord(), internalDuration.Time); + const calendar = plainRelativeTo.Calendar; + const dateDuration = X(AdjustDateDurationRecord(internalDuration.Date, targetTime.Days)); + const targetDate = Q(CalendarDateAdd(calendar, plainRelativeTo.ISODate, dateDuration, 'constrain')); + const isoDateTime = CombineISODateAndTimeRecord(plainRelativeTo.ISODate, MidnightTimeRecord()); + const targetDateTime = CombineISODateAndTimeRecord(targetDate, targetTime); + internalDuration = Q(DifferencePlainDateTimeWithRounding(isoDateTime, targetDateTime, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode)); + return Q(yield* TemporalDurationFromInternal(internalDuration, largestUnit)); + } + + if (IsCalendarUnit(existingLargestUnit) || IsCalendarUnit(largestUnit)) { + return Throw.RangeError('relativeTo is required for calendar units'); + } + Assert(IsCalendarUnit(smallestUnit) === false); + + let internalDuration = ToInternalDurationRecordWith24HourDays(duration); + if (smallestUnit === TemporalUnit.Day) { + const fractionalDays = TotalTimeDuration(internalDuration.Time, TemporalUnit.Day); + const days = RoundNumberToIncrement(fractionalDays, roundingIncrement, roundingMode); + const dateDuration = Q(CreateDateDurationRecord(0, 0, 0, days)); + internalDuration = CombineDateAndTimeDuration(dateDuration, 0 as TimeDuration); + } else { + const timeDuration = Q(RoundTimeDuration(internalDuration.Time, roundingIncrement, smallestUnit, roundingMode)); + internalDuration = CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration); + } + return Q(yield* TemporalDurationFromInternal(internalDuration, largestUnit)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total */ +function* DurationProto_total([totalOf = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + if (totalOf instanceof UndefinedValue) { + return Throw.TypeError('totalOf is required'); + } + if (totalOf instanceof JSStringValue) { + const paramString = totalOf; + totalOf = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(totalOf, Value('unit'), paramString)); + } else { + totalOf = Q(GetOptionsObject(totalOf)); + } + + const relativeToRecord = Q(yield* GetTemporalRelativeToOption(totalOf)); + const zonedRelativeTo = relativeToRecord.ZonedRelativeTo; + const plainRelativeTo = relativeToRecord.PlainRelativeTo; + const unit = Q(yield* GetTemporalUnitValuedOption(totalOf, 'unit', 'required')); + Q(ValidateTemporalUnitValue(unit, 'datetime')); + Assert(unit !== 'auto' && unit !== 'unset'); // TODO(temporal): missing assert in spec? + + let total; + if (zonedRelativeTo !== undefined) { + const internalDuration = ToInternalDurationRecord(duration); + const timeZone = zonedRelativeTo.TimeZone; + const calendar = zonedRelativeTo.Calendar; + const relativeEpochNs = zonedRelativeTo.EpochNanoseconds; + const targetEpochNs = Q(AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, 'constrain')); + total = Q(DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit)); + } else if (plainRelativeTo !== undefined) { + const internalDuration = ToInternalDurationRecordWith24HourDays(duration); + const targetTime = AddTime(MidnightTimeRecord(), internalDuration.Time); + const calendar = plainRelativeTo.Calendar; + const dateDuration = X(AdjustDateDurationRecord(internalDuration.Date, targetTime.Days)); + const targetDate = Q(CalendarDateAdd(calendar, plainRelativeTo.ISODate, dateDuration, 'constrain')); + const isoDateTime = CombineISODateAndTimeRecord(plainRelativeTo.ISODate, MidnightTimeRecord()); + const targetDateTime = CombineISODateAndTimeRecord(targetDate, targetTime); + total = Q(DifferencePlainDateTimeWithTotal(isoDateTime, targetDateTime, calendar, unit)); + } else { + const largestUnit = DefaultTemporalLargestUnit(duration); + if (IsCalendarUnit(largestUnit) || IsCalendarUnit(unit)) { + return Throw.RangeError('relativeTo is required for calendar units'); + } + const internalDuration = ToInternalDurationRecordWith24HourDays(duration); + total = TotalTimeDuration(internalDuration.Time, unit); + } + return F(total); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring */ +function* DurationProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const duration = Q(thisTemporalDurationValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions)); + const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, RoundingMode.Trunc)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + __ts_cast__(smallestUnit); + + if (smallestUnit === TemporalUnit.Hour || smallestUnit === TemporalUnit.Minute) { + return Throw.RangeError('smallestUnit cannot be hour or minute'); + } + + const precision = ToSecondsStringPrecisionRecord(smallestUnit, digits); + + if (precision.Unit === TemporalUnit.Nanosecond && precision.Increment === 1) { + return Value(TemporalDurationToString(duration, precision.Precision)); + } + + const largestUnit = DefaultTemporalLargestUnit(duration); + let internalDuration = ToInternalDurationRecord(duration); + const timeDuration = Q(RoundTimeDuration(internalDuration.Time, precision.Increment, precision.Unit, roundingMode)); + internalDuration = CombineDateAndTimeDuration(internalDuration.Date, timeDuration); + const roundedLargestUnit = LargerOfTwoTemporalUnits(largestUnit, TemporalUnit.Second); + const roundedDuration = Q(yield* TemporalDurationFromInternal(internalDuration, roundedLargestUnit)); + return Value(TemporalDurationToString(roundedDuration, precision.Precision)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tojson */ +function DurationProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return Value(TemporalDurationToString(duration, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tolocalestring */ +function DurationProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const duration = Q(thisTemporalDurationValue(thisValue)); + return Value(TemporalDurationToString(duration, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.valueof */ +function DurationProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalDurationValue(thisValue)); + return Throw.TypeError('Temporal.Duration cannot be converted to primitive value. If you are comparing two Temporal.Duration objects with > or <, use Temporal.Duration.compare() instead.'); +} + +export function bootstrapTemporalDurationPrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['years', [DurationProto_yearsGetter]], + ['months', [DurationProto_monthsGetter]], + ['weeks', [DurationProto_weeksGetter]], + ['days', [DurationProto_daysGetter]], + ['hours', [DurationProto_hoursGetter]], + ['minutes', [DurationProto_minutesGetter]], + ['seconds', [DurationProto_secondsGetter]], + ['milliseconds', [DurationProto_millisecondsGetter]], + ['microseconds', [DurationProto_microsecondsGetter]], + ['nanoseconds', [DurationProto_nanosecondsGetter]], + ['sign', [DurationProto_signGetter]], + ['blank', [DurationProto_blankGetter]], + ['with', DurationProto_with, 1], + ['negated', DurationProto_negated, 0], + ['abs', DurationProto_abs, 0], + ['add', DurationProto_add, 1], + ['subtract', DurationProto_subtract, 1], + ['round', DurationProto_round, 1], + ['total', DurationProto_total, 1], + ['toString', DurationProto_toString, 0], + ['toJSON', DurationProto_toJSON, 0], + ['toLocaleString', DurationProto_toLocaleString, 0], + ['valueOf', DurationProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.Duration'); + realmRec.Intrinsics['%Temporal.Duration.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/Instant.mts b/src/intrinsics/Temporal/Instant.mts new file mode 100644 index 0000000..49bd5e8 --- /dev/null +++ b/src/intrinsics/Temporal/Instant.mts @@ -0,0 +1,90 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { NumberToBigInt } from '../../runtime-semantics/all.mts'; +import { bootstrapTemporalInstantPrototype } from './InstantPrototype.mts'; +import { + Q, + Throw, + X, + type OrdinaryObject, + type Realm, + type Arguments, + type FunctionCallContext, + F, + UndefinedValue, + ToNumber, + ToBigInt, + R, + Value, + type ValueEvaluator, + CompareEpochNanoseconds, + CreateTemporalInstant, + IsValidEpochNanoseconds, + ToTemporalInstant, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-instant-instances */ +export interface TemporalInstantObject extends OrdinaryObject { + readonly InitializedTemporalInstant: never; + readonly EpochNanoseconds: bigint; +} + +export function isTemporalInstantObject(o: Value): o is TemporalInstantObject { + return 'InitializedTemporalInstant' in o; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant */ +function* InstantConstructor([_epochNanoseconds = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.Instant cannot be called without new'); + } + const epochNanoseconds = R(Q(yield* ToBigInt(_epochNanoseconds))); + if (!IsValidEpochNanoseconds(epochNanoseconds)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds); + } + return Q(yield* CreateTemporalInstant(epochNanoseconds, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.from */ +function* Instant_from([item = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalInstant(item)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds */ +function* Instant_fromEpochMilliseconds([___epochMilliseconds = Value.undefined]: Arguments): ValueEvaluator { + const __epochMilliseconds = Q(yield* ToNumber(___epochMilliseconds)); + const _epochMilliseconds = R(Q(NumberToBigInt(__epochMilliseconds))); + const epochMilliseconds = _epochMilliseconds * BigInt(10e6); + if (!IsValidEpochNanoseconds(epochMilliseconds)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochMilliseconds); + } + return X(CreateTemporalInstant(epochMilliseconds)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds */ +function* Instant_fromEpochNanoseconds([_epochNanoseconds = Value.undefined]: Arguments): ValueEvaluator { + const epochNanoseconds = R(Q(yield* ToBigInt(_epochNanoseconds))); + if (!IsValidEpochNanoseconds(epochNanoseconds)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds); + } + return X(CreateTemporalInstant(epochNanoseconds)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.compare */ +function* Instant_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalInstant(_one)); + const two = Q(yield* ToTemporalInstant(_two)); + return F(CompareEpochNanoseconds(one.EpochNanoseconds, two.EpochNanoseconds)); +} + +export function bootstrapTemporalInstant(realmRec: Realm) { + const prototype = bootstrapTemporalInstantPrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, InstantConstructor, 'Instant', 1, prototype, [ + ['from', Instant_from, 1], + ['fromEpochMilliseconds', Instant_fromEpochMilliseconds, 1], + ['fromEpochNanoseconds', Instant_fromEpochNanoseconds, 1], + ['compare', Instant_compare, 2], + ]); + realmRec.Intrinsics['%Temporal.Instant%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/InstantPrototype.mts b/src/intrinsics/Temporal/InstantPrototype.mts new file mode 100644 index 0000000..656bc6d --- /dev/null +++ b/src/intrinsics/Temporal/InstantPrototype.mts @@ -0,0 +1,208 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { + GetOptionsObject, + GetRoundingIncrementOption, + GetRoundingModeOption, + RoundingMode, + type TimeZoneIdentifier, +} from '../../abstract-ops/temporal/addition.mts'; +import { + GetTemporalFractionalSecondDigitsOption, + GetTemporalUnitValuedOption, + TemporalUnit, + ToSecondsStringPrecisionRecord, + ValidateTemporalRoundingIncrement, + ValidateTemporalUnitValue, + type TimeUnit, +} from '../../abstract-ops/temporal/temporal.mts'; +import { + AddDurationToInstant, + CreateTemporalInstant, + DifferenceTemporalInstant, + nsPerDay, + RoundTemporalInstant, + TemporalInstantToString, + ToTemporalInstant, +} from '../../abstract-ops/temporal/instant.mts'; +import { CreateTemporalZonedDateTime } from '../../abstract-ops/temporal/zoned-datetime.mts'; +import { ToTemporalTimeZoneIdentifier } from '../../abstract-ops/temporal/time-zone.mts'; +import type { TemporalInstantObject } from './Instant.mts'; +import { + Assert, + CreateDataPropertyOrThrow, + Get, + HoursPerDay, + JSStringValue, + MinutesPerHour, + msPerDay, + OrdinaryObjectCreate, + Q, + RequireInternalSlot, + SecondsPerMinute, + Throw, + UndefinedValue, + Value, + X, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, +} from '#self'; + +function thisTemporalInstantValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalInstant')); + return value as TemporalInstantObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds */ +function InstantProto_epochMillisecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const instant = Q(thisTemporalInstantValue(thisValue)); + const ns = instant.EpochNanoseconds; + const ms = Math.floor(Number(ns) / 10e6); + return Value(ms); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds */ +function InstantProto_epochNanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Value(instant.EpochNanoseconds); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add */ +function* InstantProto_add([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Q(yield* AddDurationToInstant('add', instant, temporalDurationLike)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract */ +function* InstantProto_subtract([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Q(yield* AddDurationToInstant('subtract', instant, temporalDurationLike)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until */ +function* InstantProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Q(yield* DifferenceTemporalInstant('until', instant, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.since */ +function* InstantProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Q(yield* DifferenceTemporalInstant('since', instant, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round */ +function* InstantProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + if (roundTo instanceof UndefinedValue) { + return Throw.TypeError('roundTo is required'); + } + if (roundTo instanceof JSStringValue) { + const paramString = roundTo; + roundTo = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString)); + } else { + roundTo = Q(GetOptionsObject(roundTo)); + } + const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo)); + const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + let maximum: number; + if (smallestUnit === TemporalUnit.Hour) { + maximum = HoursPerDay; + } else if (smallestUnit === TemporalUnit.Minute) { + maximum = MinutesPerHour * HoursPerDay; + } else if (smallestUnit === TemporalUnit.Second) { + maximum = SecondsPerMinute * MinutesPerHour * HoursPerDay; + } else if (smallestUnit === TemporalUnit.Millisecond) { + maximum = msPerDay; + } else if (smallestUnit === TemporalUnit.Microsecond) { + maximum = 1e3 * msPerDay; + } else { + Assert(smallestUnit === TemporalUnit.Nanosecond); + maximum = nsPerDay; + } + Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true)); + const roundedNs = RoundTemporalInstant(instant.EpochNanoseconds, roundingIncrement, smallestUnit, roundingMode); + return X(CreateTemporalInstant(roundedNs)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals */ +function* InstantProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + const other = Q(yield* ToTemporalInstant(_other)); + return instant.EpochNanoseconds === other.EpochNanoseconds ? Value.true : Value.false; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring */ +function* InstantProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const instant = Q(thisTemporalInstantValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions)); + const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, RoundingMode.Trunc)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset')); + const _timeZone = Q(yield* Get(resolvedOptions, Value('timeZone'))); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + if (smallestUnit === TemporalUnit.Hour) { + return Throw.RangeError('smallestUnit cannot be hour'); + } + let timeZone: TimeZoneIdentifier | undefined; + if (!(_timeZone instanceof UndefinedValue)) { + timeZone = Q(ToTemporalTimeZoneIdentifier(_timeZone)); + } + const precision = ToSecondsStringPrecisionRecord( + smallestUnit as Exclude | 'unset', + digits, + ); + const roundedNs = RoundTemporalInstant(instant.EpochNanoseconds, precision.Increment, precision.Unit, roundingMode); + const roundedInstant = X(CreateTemporalInstant(roundedNs)); + return Value(TemporalInstantToString(roundedInstant, timeZone, precision.Precision)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring */ +function InstantProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Value(TemporalInstantToString(instant, undefined, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tojson */ +function InstantProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const instant = Q(thisTemporalInstantValue(thisValue)); + return Value(TemporalInstantToString(instant, undefined, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof */ +function InstantProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalInstantValue(thisValue)); + return Throw.TypeError('Temporal.Instant cannot be converted to primitive value If you are comparing two Temporal.Duration objects with > or <, use Temporal.Instant.compare() instead.'); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso */ +function InstantProto_toZonedDateTimeISO([_timeZone = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const instant = Q(thisTemporalInstantValue(thisValue)); + const timeZone = Q(ToTemporalTimeZoneIdentifier(_timeZone)); + return X(CreateTemporalZonedDateTime(instant.EpochNanoseconds, timeZone, 'iso8601')); +} + +export function bootstrapTemporalInstantPrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['epochMilliseconds', [InstantProto_epochMillisecondsGetter]], + ['epochNanoseconds', [InstantProto_epochNanosecondsGetter]], + ['add', InstantProto_add, 1], + ['subtract', InstantProto_subtract, 1], + ['until', InstantProto_until, 1], + ['since', InstantProto_since, 1], + ['round', InstantProto_round, 1], + ['equals', InstantProto_equals, 1], + ['toString', InstantProto_toString, 0], + ['toLocaleString', InstantProto_toLocaleString, 0], + ['toJSON', InstantProto_toJSON, 0], + ['valueOf', InstantProto_valueOf, 0], + ['toZonedDateTimeISO', InstantProto_toZonedDateTimeISO, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.Instant'); + realmRec.Intrinsics['%Temporal.Instant.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/Now.mts b/src/intrinsics/Temporal/Now.mts new file mode 100644 index 0000000..5515444 --- /dev/null +++ b/src/intrinsics/Temporal/Now.mts @@ -0,0 +1,66 @@ +import { SystemTimeZoneIdentifier } from '../../abstract-ops/temporal/addition.mts'; +import { ToTemporalTimeZoneIdentifier } from '../../abstract-ops/temporal/time-zone.mts'; +import { bootstrapPrototype } from '../bootstrap.mts'; +import { + type Realm, X, Value, Q, type Arguments, type PlainCompletion, + CreateTemporalInstant, + SystemDateTime, + SystemUTCEpochNanoseconds, + CreateTemporalDate, + CreateTemporalZonedDateTime, + CreateTemporalDateTime, + CreateTemporalTime, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal.now.timezoneid */ +function TemporalNow_timeZoneId(): Value { + return Value(SystemTimeZoneIdentifier()); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.now.instant */ +function TemporalNow_instant(): Value { + const ns = SystemUTCEpochNanoseconds(); + return X(CreateTemporalInstant(ns)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetimeiso */ +function TemporalNow_plainDateTimeISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion { + const isoDateTime = Q(SystemDateTime(temporalTimeZoneLike)); + return X(CreateTemporalDateTime(isoDateTime, 'iso8601')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetimeiso */ +function TemporalNow_zonedDateTimeISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion { + let timeZone; + if (temporalTimeZoneLike === Value.undefined) { + timeZone = SystemTimeZoneIdentifier(); + } else { + timeZone = Q(ToTemporalTimeZoneIdentifier(temporalTimeZoneLike)); + } + const ns = SystemUTCEpochNanoseconds(); + return X(CreateTemporalZonedDateTime(ns, timeZone, 'iso8601')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso */ +function TemporalNow_plainDateISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion { + const isoDateTime = Q(SystemDateTime(temporalTimeZoneLike)); + return X(CreateTemporalDate(isoDateTime.ISODate, 'iso8601')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.now.plaintimeiso */ +function TemporalNow_plainTimeISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion { + const isoDateTime = Q(SystemDateTime(temporalTimeZoneLike)); + return X(CreateTemporalTime(isoDateTime.Time)); +} + +export function bootstrapTemporalNow(realmRec: Realm) { + const Now = bootstrapPrototype(realmRec, [ + ['timeZoneId', TemporalNow_timeZoneId, 0], + ['instant', TemporalNow_instant, 0], + ['plainDateTimeISO', TemporalNow_plainDateTimeISO, 0], + ['zonedDateTimeISO', TemporalNow_zonedDateTimeISO, 0], + ['plainDateISO', TemporalNow_plainDateISO, 0], + ['plainTimeISO', TemporalNow_plainTimeISO, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.Now'); + return Now; +} diff --git a/src/intrinsics/Temporal/PlainDate.mts b/src/intrinsics/Temporal/PlainDate.mts new file mode 100644 index 0000000..1869d9b --- /dev/null +++ b/src/intrinsics/Temporal/PlainDate.mts @@ -0,0 +1,78 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { bootstrapTemporalPlainDatePrototype } from './PlainDatePrototype.mts'; +import { + type Realm, Value, UndefinedValue, Q, JSStringValue, type FunctionCallContext, type Arguments, F, type OrdinaryObject, type ValueEvaluator, + Throw, + CompareISODate, + CreateISODateRecord, + CreateTemporalDate, + IsValidISODate, + ToTemporalDate, + type CalendarType, + CanonicalizeCalendar, + ToIntegerWithTruncation, +} from '#self'; + +export interface TemporalPlainDateObject extends OrdinaryObject { + /** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaindate-instances */ + readonly InitializedTemporalDate: never; + readonly ISODate: ISODateRecord; + readonly Calendar: CalendarType; +} +export function isTemporalPlainDateObject(o: Value): o is TemporalPlainDateObject { + return 'InitializedTemporalDate' in o; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-iso-date-records */ +export interface ISODateRecord { + readonly Year: number; + readonly Month: number; + readonly Day: number; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate */ +function* PlainDateConstructor([isoYear = Value.undefined, isoMonth = Value.undefined, isoDay = Value.undefined, _calendar = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.PlainDate constructor cannot be called without new'); + } + const y = Q(yield* ToIntegerWithTruncation(isoYear)); + const m = Q(yield* ToIntegerWithTruncation(isoMonth)); + const d = Q(yield* ToIntegerWithTruncation(isoDay)); + if (_calendar instanceof UndefinedValue) { + _calendar = Value('iso8601'); + } + if (!(_calendar instanceof JSStringValue)) { + return Throw.TypeError('calendar must be a string, but $1', _calendar); + } + const calendar = Q(CanonicalizeCalendar(_calendar.stringValue())); + if (!IsValidISODate(y, m, d)) { + return Throw.RangeError('Invalid date'); + } + const isoDate = CreateISODateRecord(y, m, d); + return Q(yield* CreateTemporalDate(isoDate, calendar, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.from */ +function* PlainDate_From([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalDate(item, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.compare */ +function* PlainDate_Compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalDate(_one)); + const two = Q(yield* ToTemporalDate(_two)); + return F(CompareISODate(one.ISODate, two.ISODate)); +} + +export function bootstrapTemporalPlainDate(realmRec: Realm) { + const prototype = bootstrapTemporalPlainDatePrototype(realmRec); + realmRec.Intrinsics['%Temporal.PlainDate.prototype%'] = prototype; + + const constructor = bootstrapConstructor(realmRec, PlainDateConstructor, 'PlainDate', 3, prototype, [ + ['from', PlainDate_From, 1], + ['compare', PlainDate_Compare, 2], + ]); + realmRec.Intrinsics['%Temporal.PlainDate%'] = constructor; + + return constructor; +} diff --git a/src/intrinsics/Temporal/PlainDatePrototype.mts b/src/intrinsics/Temporal/PlainDatePrototype.mts new file mode 100644 index 0000000..1c0b971 --- /dev/null +++ b/src/intrinsics/Temporal/PlainDatePrototype.mts @@ -0,0 +1,331 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { GetOptionsObject } from '../../abstract-ops/temporal/addition.mts'; +import type { TimeZoneIdentifier } from '../../abstract-ops/temporal/addition.mts'; +import type { TemporalPlainDateObject } from './PlainDate.mts'; +import { + AddDurationToDate, + CalendarDateFromFields, + CalendarEquals, + CalendarISOToDate, + CalendarMergeFields, + CalendarMonthDayFromFields, + CalendarYearMonthFromFields, + CompareISODate, + CombineISODateAndTimeRecord, + CreateTemporalDate, + CreateTemporalDateTime, + CreateTemporalMonthDay, + CreateTemporalYearMonth, + CreateTemporalZonedDateTime, + DifferenceTemporalPlainDate, + Get, + GetEpochNanosecondsFor, + GetStartOfDay, + GetTemporalOverflowOption, + GetTemporalShowCalendarNameOption, + ISODateTimeWithinLimits, + ISODateToFields, + IsPartialTemporalObject, + PrepareCalendarFields, + Q, + RequireInternalSlot, + Throw, + ToTemporalCalendarIdentifier, + ToTemporalDate, + ToTemporalTime, + ToTemporalTimeZoneIdentifier, + ToTimeRecordOrMidnight, + Value, + X, + F, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, + ObjectValue, + TemporalDateToString, +} from '#self'; + +function thisTemporalDateValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalDate')); + return value as TemporalPlainDateObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.calendarid */ +function PlainDateProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Value(plainDate.Calendar); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.era */ +function PlainDateProto_eraGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Value(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Era); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.erayear */ +function PlainDateProto_eraYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const result = CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).EraYear; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.year */ +function PlainDateProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Year); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.month */ +function PlainDateProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Month); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.monthcode */ +function PlainDateProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Value(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).MonthCode); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.day */ +function PlainDateProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Day); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofweek */ +function PlainDateProto_dayOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DayOfWeek); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofyear */ +function PlainDateProto_dayOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DayOfYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.weekofyear */ +function PlainDateProto_weekOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const result = CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).WeekOfYear.Week; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.yearofweek */ +function PlainDateProto_yearOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const result = CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).WeekOfYear.Year; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinweek */ +function PlainDateProto_daysInWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DaysInWeek); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinmonth */ +function PlainDateProto_daysInMonthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DaysInMonth); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinyear */ +function PlainDateProto_daysInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DaysInYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.monthsinyear */ +function PlainDateProto_monthsInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).MonthsInYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.inleapyear */ +function PlainDateProto_inLeapYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Value(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).InLeapYear); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainyearmonth */ +function* PlainDateProto_toPlainYearMonth(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const calendar = plainDate.Calendar; + const fields = ISODateToFields(calendar, plainDate.ISODate, 'date'); + const isoDate = Q(yield* CalendarYearMonthFromFields(calendar, fields, 'constrain')); + return X(CreateTemporalYearMonth(isoDate, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainmonthday */ +function* PlainDateProto_toPlainMonthDay(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const calendar = plainDate.Calendar; + const fields = ISODateToFields(calendar, plainDate.ISODate, 'date'); + const isoDate = Q(yield* CalendarMonthDayFromFields(calendar, fields, 'constrain')); + return X(CreateTemporalMonthDay(isoDate, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.add */ +function* PlainDateProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Q(yield* AddDurationToDate('add', plainDate, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.subtract */ +function* PlainDateProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Q(yield* AddDurationToDate('subtract', plainDate, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.with */ +function* PlainDateProto_with([temporalDateLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + if (!Q(yield* IsPartialTemporalObject(temporalDateLike))) { + return Throw.TypeError('$1 is not a partial Temporal object', temporalDateLike); + } + const calendar = plainDate.Calendar; + let fields = ISODateToFields(calendar, plainDate.ISODate, 'date'); + const partialDate = Q(yield* PrepareCalendarFields(calendar, temporalDateLike as ObjectValue, ['year', 'month', 'month-code', 'day'], [], 'partial')); + fields = CalendarMergeFields(calendar, fields, partialDate); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = Q(yield* CalendarDateFromFields(calendar, fields, overflow)); + return X(CreateTemporalDate(isoDate, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.withcalendar */ +function PlainDateProto_withCalendar([calendarLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const calendar = Q(ToTemporalCalendarIdentifier(calendarLike)); + return X(CreateTemporalDate(plainDate.ISODate, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.until */ +function* PlainDateProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Q(yield* DifferenceTemporalPlainDate('until', plainDate, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.since */ +function* PlainDateProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Q(yield* DifferenceTemporalPlainDate('since', plainDate, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.equals */ +function* PlainDateProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const other = Q(yield* ToTemporalDate(_other)); + if (CompareISODate(plainDate.ISODate, other.ISODate) !== 0) { + return Value.false; + } + return Value(CalendarEquals(plainDate.Calendar, other.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplaindatetime */ +function* PlainDateProto_toPlainDateTime([temporalTime = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const time = Q(yield* ToTimeRecordOrMidnight(temporalTime)); + const isoDateTime = CombineISODateAndTimeRecord(plainDate.ISODate, time); + return Q(yield* CreateTemporalDateTime(isoDateTime, plainDate.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tozoneddatetime */ +function* PlainDateProto_toZonedDateTime([item = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + let timeZone: TimeZoneIdentifier; + let temporalTime: Value; + if (item instanceof ObjectValue) { + const timeZoneLike = Q(yield* Get(item, Value('timeZone'))); + if (timeZoneLike === Value.undefined) { + timeZone = Q(ToTemporalTimeZoneIdentifier(item)); + temporalTime = Value.undefined; + } else { + timeZone = Q(ToTemporalTimeZoneIdentifier(timeZoneLike)); + temporalTime = Q(yield* Get(item, Value('plainTime'))); + } + } else { + timeZone = Q(ToTemporalTimeZoneIdentifier(item)); + temporalTime = Value.undefined; + } + let epochNs: bigint; + if (temporalTime === Value.undefined) { + epochNs = Q(GetStartOfDay(timeZone, plainDate.ISODate)); + } else { + const temporalTime2 = Q(yield* ToTemporalTime(temporalTime)); + const isoDateTime = CombineISODateAndTimeRecord(plainDate.ISODate, temporalTime2.Time); + if (!ISODateTimeWithinLimits(isoDateTime)) { + return Throw.RangeError('DateTime outside of range'); + } + epochNs = Q(GetEpochNanosecondsFor(timeZone, isoDateTime, 'compatible')); + } + return X(CreateTemporalZonedDateTime(epochNs, timeZone, plainDate.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tostring */ +function* PlainDateProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDate = Q(thisTemporalDateValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions)); + return Value(TemporalDateToString(plainDate, showCalendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tolocalestring */ +function PlainDateProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Value(TemporalDateToString(plainDate, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tojson */ +function PlainDateProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDate = Q(thisTemporalDateValue(thisValue)); + return Value(TemporalDateToString(plainDate, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.valueof */ +function PlainDateProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalDateValue(thisValue)); + return Throw.TypeError('Temporal.PlainDate cannot be converted to primitive value. If you are comparing two Temporal.PlainDate objects with > or <, use Temporal.PlainDate.compare() instead.'); +} + +export function bootstrapTemporalPlainDatePrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['calendarId', [PlainDateProto_calendarIdGetter]], + ['era', [PlainDateProto_eraGetter]], + ['eraYear', [PlainDateProto_eraYearGetter]], + ['year', [PlainDateProto_yearGetter]], + ['month', [PlainDateProto_monthGetter]], + ['monthCode', [PlainDateProto_monthCodeGetter]], + ['day', [PlainDateProto_dayGetter]], + ['dayOfWeek', [PlainDateProto_dayOfWeekGetter]], + ['dayOfYear', [PlainDateProto_dayOfYearGetter]], + ['weekOfYear', [PlainDateProto_weekOfYearGetter]], + ['yearOfWeek', [PlainDateProto_yearOfWeekGetter]], + ['daysInWeek', [PlainDateProto_daysInWeekGetter]], + ['daysInMonth', [PlainDateProto_daysInMonthGetter]], + ['daysInYear', [PlainDateProto_daysInYearGetter]], + ['monthsInYear', [PlainDateProto_monthsInYearGetter]], + ['inLeapYear', [PlainDateProto_inLeapYearGetter]], + ['toPlainYearMonth', PlainDateProto_toPlainYearMonth, 0], + ['toPlainMonthDay', PlainDateProto_toPlainMonthDay, 0], + ['add', PlainDateProto_add, 1], + ['subtract', PlainDateProto_subtract, 1], + ['with', PlainDateProto_with, 1], + ['withCalendar', PlainDateProto_withCalendar, 1], + ['until', PlainDateProto_until, 1], + ['since', PlainDateProto_since, 1], + ['equals', PlainDateProto_equals, 1], + ['toPlainDateTime', PlainDateProto_toPlainDateTime, 0], + ['toZonedDateTime', PlainDateProto_toZonedDateTime, 1], + ['toString', PlainDateProto_toString, 0], + ['toLocaleString', PlainDateProto_toLocaleString, 0], + ['toJSON', PlainDateProto_toJSON, 0], + ['valueOf', PlainDateProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainDate'); + realmRec.Intrinsics['%Temporal.PlainDate.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/PlainDateTime.mts b/src/intrinsics/Temporal/PlainDateTime.mts new file mode 100644 index 0000000..9334e67 --- /dev/null +++ b/src/intrinsics/Temporal/PlainDateTime.mts @@ -0,0 +1,113 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { + CanonicalizeCalendar, + type CalendarType, +} from '../../abstract-ops/temporal/calendar.mts'; +import { bootstrapTemporalPlainDateTimePrototype } from './PlainDateTimePrototype.mts'; +import type { ISODateRecord } from './PlainDate.mts'; +import { + JSStringValue, + Q, + Throw, + Value, + type OrdinaryObject, + type ValueEvaluator, + type Realm, + type Arguments, + type FunctionCallContext, + UndefinedValue, + F, + CombineISODateAndTimeRecord, + CompareISODateTime, + CreateISODateRecord, + CreateTemporalDateTime, + CreateTimeRecord, + IsValidISODate, + IsValidTime, + ToIntegerWithTruncation, + ToTemporalDateTime, + type TimeRecord, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaindatetime-instances */ +export interface TemporalPlainDateTimeObject extends OrdinaryObject { + readonly InitializedTemporalDateTime: never; + readonly ISODateTime: ISODateTimeRecord; + readonly Calendar: CalendarType; +} +export function isTemporalPlainDateTimeObject(o: Value): o is TemporalPlainDateTimeObject { + return 'InitializedTemporalDateTime' in o; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-iso-date-time-records */ +export interface ISODateTimeRecord { + readonly ISODate: ISODateRecord; + readonly Time: TimeRecord; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime */ +function* PlainDateTimeConstructor([ + _isoYear = Value.undefined, + _isoMonth = Value.undefined, + _isoDay = Value.undefined, + _hour = Value.undefined, + _minute = Value.undefined, + _second = Value.undefined, + _millisecond = Value.undefined, + _microsecond = Value.undefined, + _nanosecond = Value.undefined, + _calendar = Value.undefined, +]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.PlainDateTime cannot be called without new'); + } + const isoYear = Q(yield* ToIntegerWithTruncation(_isoYear)); + const isoMonth = Q(yield* ToIntegerWithTruncation(_isoMonth)); + const isoDay = Q(yield* ToIntegerWithTruncation(_isoDay)); + const hour = _hour instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_hour)); + const minute = _minute instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_minute)); + const second = _second instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_second)); + const millisecond = _millisecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_millisecond)); + const microsecond = _microsecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_microsecond)); + const nanosecond = _nanosecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_nanosecond)); + if (_calendar instanceof UndefinedValue) { + _calendar = Value('iso8601'); + } + if (!(_calendar instanceof JSStringValue)) { + return Throw.TypeError('calendar is not a string'); + } + const calendar = Q(CanonicalizeCalendar(_calendar.stringValue())); + if (!IsValidISODate(isoYear, isoMonth, isoDay)) { + return Throw.RangeError('$1-$2-$3 is not a valid date', isoYear, isoMonth, isoDay); + } + const isoDate = CreateISODateRecord(isoYear, isoMonth, isoDay); + if (!IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)) { + return Throw.RangeError('Invalid time'); + } + const time = CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond); + const isoDateTime = CombineISODateAndTimeRecord(isoDate, time); + return Q(yield* CreateTemporalDateTime(isoDateTime, calendar, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.from */ +function* PlainDateTime_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalDateTime(item, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.compare */ +function* PlainDateTime_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalDateTime(_one)); + const two = Q(yield* ToTemporalDateTime(_two)); + return F(CompareISODateTime(one.ISODateTime, two.ISODateTime)); +} + +export function bootstrapTemporalPlainDateTime(realmRec: Realm) { + const prototype = bootstrapTemporalPlainDateTimePrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, PlainDateTimeConstructor, 'PlainDateTime', 3, prototype, [ + ['from', PlainDateTime_from, 1], + ['compare', PlainDateTime_compare, 2], + ]); + realmRec.Intrinsics['%Temporal.PlainDateTime%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/PlainDateTimePrototype.mts b/src/intrinsics/Temporal/PlainDateTimePrototype.mts new file mode 100644 index 0000000..eadc7a1 --- /dev/null +++ b/src/intrinsics/Temporal/PlainDateTimePrototype.mts @@ -0,0 +1,419 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { + GetOptionsObject, GetRoundingIncrementOption, GetRoundingModeOption, RoundingMode, +} from '../../abstract-ops/temporal/addition.mts'; +import { + GetTemporalFractionalSecondDigitsOption, + GetTemporalShowCalendarNameOption, + GetTemporalUnitValuedOption, + GetTemporalOverflowOption, + GetTemporalDisambiguationOption, + IsPartialTemporalObject, + ISODateToFields, + MaximumTemporalDurationRoundingIncrement, + TemporalUnit, + ToSecondsStringPrecisionRecord, + ValidateTemporalRoundingIncrement, + ValidateTemporalUnitValue, + type TimeUnit, + __IsTimeUnit, +} from '../../abstract-ops/temporal/temporal.mts'; +import { + CalendarEquals, CalendarISOToDate, CalendarMergeFields, PrepareCalendarFields, + ToTemporalCalendarIdentifier, +} from '../../abstract-ops/temporal/calendar.mts'; +import { + CombineISODateAndTimeRecord, + CompareISODateTime, + CreateTemporalDateTime, + DifferenceTemporalPlainDateTime, + AddDurationToDateTime, + InterpretTemporalDateTimeFields, + ISODateTimeToString, + ISODateTimeWithinLimits, + RoundISODateTime, + ToTemporalDateTime, +} from '../../abstract-ops/temporal/plain-date-time.mts'; +import { ToTimeRecordOrMidnight, CreateTemporalTime } from '../../abstract-ops/temporal/plain-time.mts'; +import { CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts'; +import { CreateTemporalZonedDateTime } from '../../abstract-ops/temporal/zoned-datetime.mts'; +import { GetEpochNanosecondsFor, ToTemporalTimeZoneIdentifier } from '../../abstract-ops/temporal/time-zone.mts'; +import type { TimeZoneIdentifier } from '../../abstract-ops/temporal/addition.mts'; +import type { TemporalPlainDateTimeObject } from './PlainDateTime.mts'; +import { + Assert, + CreateDataPropertyOrThrow, + F, + JSStringValue, + ObjectValue, + OrdinaryObjectCreate, + Q, + RequireInternalSlot, + Throw, + UndefinedValue, + Value, + X, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, +} from '#self'; + +function thisTemporalDateTimeValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalDateTime')); + return value as TemporalPlainDateTimeObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.calendarid */ +function PlainDateTimeProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Value(plainDateTime.Calendar); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.era */ +function PlainDateTimeProto_eraGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Value(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Era); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.erayear */ +function PlainDateTimeProto_eraYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const result = CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).EraYear; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.year */ +function PlainDateTimeProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Year); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.month */ +function PlainDateTimeProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Month); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.monthcode */ +function PlainDateTimeProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Value(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).MonthCode); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.day */ +function PlainDateTimeProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Day); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.hour */ +function PlainDateTimeProto_hourGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(plainDateTime.ISODateTime.Time.Hour); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.minute */ +function PlainDateTimeProto_minuteGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(plainDateTime.ISODateTime.Time.Minute); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.second */ +function PlainDateTimeProto_secondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(plainDateTime.ISODateTime.Time.Second); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.millisecond */ +function PlainDateTimeProto_millisecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(plainDateTime.ISODateTime.Time.Millisecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.microsecond */ +function PlainDateTimeProto_microsecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(plainDateTime.ISODateTime.Time.Microsecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.nanosecond */ +function PlainDateTimeProto_nanosecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(plainDateTime.ISODateTime.Time.Nanosecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.dayofweek */ +function PlainDateTimeProto_dayOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DayOfWeek); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.dayofyear */ +function PlainDateTimeProto_dayOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DayOfYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.weekofyear */ +function PlainDateTimeProto_weekOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const result = CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).WeekOfYear.Week; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.yearofweek */ +function PlainDateTimeProto_yearOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const result = CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).WeekOfYear.Year; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinweek */ +function PlainDateTimeProto_daysInWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DaysInWeek); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinmonth */ +function PlainDateTimeProto_daysInMonthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DaysInMonth); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinyear */ +function PlainDateTimeProto_daysInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DaysInYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.monthsinyear */ +function PlainDateTimeProto_monthsInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).MonthsInYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.inleapyear */ +function PlainDateTimeProto_inLeapYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Value(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).InLeapYear); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.with */ +function* PlainDateTimeProto_with([temporalDateTimeLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + if (!Q(yield* IsPartialTemporalObject(temporalDateTimeLike))) { + return Throw.TypeError('$1 is not a partial Temporal object', temporalDateTimeLike); + } + const calendar = plainDateTime.Calendar; + let fields = ISODateToFields(calendar, plainDateTime.ISODateTime.ISODate, 'date'); + fields.Hour = plainDateTime.ISODateTime.Time.Hour; + fields.Minute = plainDateTime.ISODateTime.Time.Minute; + fields.Second = plainDateTime.ISODateTime.Time.Second; + fields.Millisecond = plainDateTime.ISODateTime.Time.Millisecond; + fields.Microsecond = plainDateTime.ISODateTime.Time.Microsecond; + fields.Nanosecond = plainDateTime.ISODateTime.Time.Nanosecond; + const partialDateTime = Q(yield* PrepareCalendarFields(calendar, temporalDateTimeLike as ObjectValue, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'], 'partial')); + fields = CalendarMergeFields(calendar, fields, partialDateTime); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const result = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, overflow)); + return Q(yield* CreateTemporalDateTime(result, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withplaintime */ +function* PlainDateTimeProto_withPlainTime([plainTimeLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const time = Q(yield* ToTimeRecordOrMidnight(plainTimeLike)); + const isoDateTime = CombineISODateAndTimeRecord(plainDateTime.ISODateTime.ISODate, time); + return Q(yield* CreateTemporalDateTime(isoDateTime, plainDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withcalendar */ +function PlainDateTimeProto_withCalendar([calendarLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const calendar = Q(ToTemporalCalendarIdentifier(calendarLike)); + return X(CreateTemporalDateTime(plainDateTime.ISODateTime, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.add */ +function* PlainDateTimeProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Q(yield* AddDurationToDateTime('add', plainDateTime, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.subtract */ +function* PlainDateTimeProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Q(yield* AddDurationToDateTime('subtract', plainDateTime, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.until */ +function* PlainDateTimeProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Q(yield* DifferenceTemporalPlainDateTime('until', plainDateTime, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.since */ +function* PlainDateTimeProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Q(yield* DifferenceTemporalPlainDateTime('since', plainDateTime, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round */ +function* PlainDateTimeProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + if (roundTo instanceof UndefinedValue) { + return Throw.TypeError('roundTo is required'); + } + if (roundTo instanceof JSStringValue) { + const paramString = roundTo; + roundTo = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString)); + } else { + roundTo = Q(GetOptionsObject(roundTo)); + } + const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo)); + const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time', [TemporalUnit.Day])); + let maximum: number; + let inclusive: boolean; + if (smallestUnit === TemporalUnit.Day) { + maximum = 1; + inclusive = true; + } else { + const maximum2 = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit); + Assert(maximum2 !== 'unset'); + maximum = maximum2; + inclusive = false; + } + Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, inclusive)); + if (smallestUnit === TemporalUnit.Nanosecond && roundingIncrement === 1) { + return X(CreateTemporalDateTime(plainDateTime.ISODateTime, plainDateTime.Calendar)); + } + const result = RoundISODateTime( + plainDateTime.ISODateTime, + roundingIncrement, + smallestUnit as TimeUnit, + roundingMode, + ); + return Q(yield* CreateTemporalDateTime(result, plainDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.equals */ +function* PlainDateTimeProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const other = Q(yield* ToTemporalDateTime(_other)); + if (CompareISODateTime(plainDateTime.ISODateTime, other.ISODateTime) !== 0) { + return Value.false; + } + return Value(CalendarEquals(plainDateTime.Calendar, other.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tostring */ +function* PlainDateTimeProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions)); + const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions)); + const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, 3)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + if (smallestUnit === TemporalUnit.Hour) { + return Throw.RangeError('smallestUnit cannot be hour'); + } + Assert(smallestUnit !== 'auto' && (smallestUnit === 'unset' || __IsTimeUnit(smallestUnit))); // TODO(temporal): not in spec + const precision = ToSecondsStringPrecisionRecord(smallestUnit, digits); + const result = RoundISODateTime(plainDateTime.ISODateTime, precision.Increment, precision.Unit, roundingMode); + if (!ISODateTimeWithinLimits(result)) { + return Throw.RangeError('DateTime outside of range'); + } + return Value(ISODateTimeToString(result, plainDateTime.Calendar, precision.Precision, showCalendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tolocalestring */ +function PlainDateTimeProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Value(ISODateTimeToString(plainDateTime.ISODateTime, plainDateTime.Calendar, 'auto', 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tojson */ +function PlainDateTimeProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return Value(ISODateTimeToString(plainDateTime.ISODateTime, plainDateTime.Calendar, 'auto', 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.valueof */ +function PlainDateTimeProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalDateTimeValue(thisValue)); + return Throw.TypeError('Temporal.PlainDateTime cannot be converted to primitive value. If you are comparing two Temporal.PlainDateTime objects with > or <, use Temporal.PlainDateTime.compare() instead.'); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tozoneddatetime */ +function* PlainDateTimeProto_toZonedDateTime([temporalTimeZoneLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + const timeZone = Q(ToTemporalTimeZoneIdentifier(temporalTimeZoneLike)) as TimeZoneIdentifier; + const resolvedOptions = Q(GetOptionsObject(options)); + const disambiguation = Q(yield* GetTemporalDisambiguationOption(resolvedOptions)); + const epochNs = Q(GetEpochNanosecondsFor(timeZone, plainDateTime.ISODateTime, disambiguation)); + return X(CreateTemporalZonedDateTime(epochNs, timeZone, plainDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaindate */ +function PlainDateTimeProto_toPlainDate(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return X(CreateTemporalDate(plainDateTime.ISODateTime.ISODate, plainDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaintime */ +function PlainDateTimeProto_toPlainTime(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainDateTime = Q(thisTemporalDateTimeValue(thisValue)); + return X(CreateTemporalTime(plainDateTime.ISODateTime.Time)); +} + +export function bootstrapTemporalPlainDateTimePrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['calendarId', [PlainDateTimeProto_calendarIdGetter]], + ['era', [PlainDateTimeProto_eraGetter]], + ['eraYear', [PlainDateTimeProto_eraYearGetter]], + ['year', [PlainDateTimeProto_yearGetter]], + ['month', [PlainDateTimeProto_monthGetter]], + ['monthCode', [PlainDateTimeProto_monthCodeGetter]], + ['day', [PlainDateTimeProto_dayGetter]], + ['hour', [PlainDateTimeProto_hourGetter]], + ['minute', [PlainDateTimeProto_minuteGetter]], + ['second', [PlainDateTimeProto_secondGetter]], + ['millisecond', [PlainDateTimeProto_millisecondGetter]], + ['microsecond', [PlainDateTimeProto_microsecondGetter]], + ['nanosecond', [PlainDateTimeProto_nanosecondGetter]], + ['dayOfWeek', [PlainDateTimeProto_dayOfWeekGetter]], + ['dayOfYear', [PlainDateTimeProto_dayOfYearGetter]], + ['weekOfYear', [PlainDateTimeProto_weekOfYearGetter]], + ['yearOfWeek', [PlainDateTimeProto_yearOfWeekGetter]], + ['daysInWeek', [PlainDateTimeProto_daysInWeekGetter]], + ['daysInMonth', [PlainDateTimeProto_daysInMonthGetter]], + ['daysInYear', [PlainDateTimeProto_daysInYearGetter]], + ['monthsInYear', [PlainDateTimeProto_monthsInYearGetter]], + ['inLeapYear', [PlainDateTimeProto_inLeapYearGetter]], + ['with', PlainDateTimeProto_with, 1], + ['withPlainTime', PlainDateTimeProto_withPlainTime, 0], + ['withCalendar', PlainDateTimeProto_withCalendar, 1], + ['add', PlainDateTimeProto_add, 1], + ['subtract', PlainDateTimeProto_subtract, 1], + ['until', PlainDateTimeProto_until, 1], + ['since', PlainDateTimeProto_since, 1], + ['round', PlainDateTimeProto_round, 1], + ['equals', PlainDateTimeProto_equals, 1], + ['toString', PlainDateTimeProto_toString, 0], + ['toLocaleString', PlainDateTimeProto_toLocaleString, 0], + ['toJSON', PlainDateTimeProto_toJSON, 0], + ['valueOf', PlainDateTimeProto_valueOf, 0], + ['toZonedDateTime', PlainDateTimeProto_toZonedDateTime, 1], + ['toPlainDate', PlainDateTimeProto_toPlainDate, 0], + ['toPlainTime', PlainDateTimeProto_toPlainTime, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainDateTime'); + realmRec.Intrinsics['%Temporal.PlainDateTime.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/PlainMonthDay.mts b/src/intrinsics/Temporal/PlainMonthDay.mts new file mode 100644 index 0000000..6d8a9b7 --- /dev/null +++ b/src/intrinsics/Temporal/PlainMonthDay.mts @@ -0,0 +1,81 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { + CanonicalizeCalendar, + type CalendarType, +} from '../../abstract-ops/temporal/calendar.mts'; +import { bootstrapTemporalPlainMonthDayPrototype } from './PlainMonthDayPrototype.mts'; +import type { ISODateRecord } from './PlainDate.mts'; +import { + JSStringValue, + Q, + Throw, + Value, + UndefinedValue, + F, + ToIntegerWithTruncation, + type Arguments, + type FunctionCallContext, + type Realm, + type OrdinaryObject, + type ValueEvaluator, + CreateISODateRecord, + CreateTemporalMonthDay, + IsValidISODate, + ToTemporalMonthDay, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plainmonthday-instances */ +export interface TemporalPlainMonthDayObject extends OrdinaryObject { + readonly InitializedTemporalMonthDay: never; + readonly ISODate: ISODateRecord; + readonly Calendar: CalendarType; +} + +export function isTemporalPlainMonthDayObject(o: Value): o is TemporalPlainMonthDayObject { + return 'InitializedTemporalMonthDay' in o; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday */ +function* PlainMonthDayConstructor([ + isoMonth = Value.undefined, + isoDay = Value.undefined, + _calendar = Value.undefined, + referenceISOYear = Value.undefined, +]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.PlainMonthDay cannot be called without new'); + } + if (referenceISOYear instanceof UndefinedValue) { + referenceISOYear = F(1972); + } + const m = Q(yield* ToIntegerWithTruncation(isoMonth)); + const d = Q(yield* ToIntegerWithTruncation(isoDay)); + if (_calendar instanceof UndefinedValue) { + _calendar = Value('iso8601'); + } + if (!(_calendar instanceof JSStringValue)) { + return Throw.TypeError('calendar is not a string'); + } + const calendar = Q(CanonicalizeCalendar(_calendar.stringValue())); + const y = Q(yield* ToIntegerWithTruncation(referenceISOYear)); + if (!IsValidISODate(y, m, d)) { + return Throw.RangeError('$1-$2-$3 is not a valid date', y, m, d); + } + const isoDate = CreateISODateRecord(y, m, d); + return Q(yield* CreateTemporalMonthDay(isoDate, calendar, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.from */ +function* PlainMonthDay_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalMonthDay(item, options)); +} + +export function bootstrapTemporalPlainMonthDay(realmRec: Realm) { + const prototype = bootstrapTemporalPlainMonthDayPrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, PlainMonthDayConstructor, 'PlainMonthDay', 2, prototype, [ + ['from', PlainMonthDay_from, 1], + ]); + realmRec.Intrinsics['%Temporal.PlainMonthDay%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/PlainMonthDayPrototype.mts b/src/intrinsics/Temporal/PlainMonthDayPrototype.mts new file mode 100644 index 0000000..2738e9c --- /dev/null +++ b/src/intrinsics/Temporal/PlainMonthDayPrototype.mts @@ -0,0 +1,139 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { GetOptionsObject } from '../../abstract-ops/temporal/addition.mts'; +import { + GetTemporalOverflowOption, + GetTemporalShowCalendarNameOption, + IsPartialTemporalObject, + ISODateToFields, +} from '../../abstract-ops/temporal/temporal.mts'; +import { + CalendarDateFromFields, + CalendarEquals, + CalendarISOToDate, + CalendarMergeFields, + CalendarMonthDayFromFields, + PrepareCalendarFields, +} from '../../abstract-ops/temporal/calendar.mts'; +import { CompareISODate, CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts'; +import { CreateTemporalMonthDay, TemporalMonthDayToString, ToTemporalMonthDay } from '../../abstract-ops/temporal/plain-month-day.mts'; +import type { TemporalPlainMonthDayObject } from './PlainMonthDay.mts'; +import { + F, + ObjectValue, + Q, + RequireInternalSlot, + Throw, + Value, + X, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, +} from '#self'; + +function thisTemporalMonthDayValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalMonthDay')); + return value as TemporalPlainMonthDayObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.calendarid */ +function PlainMonthDayProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + return Value(plainMonthDay.Calendar); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.monthcode */ +function PlainMonthDayProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + return Value(CalendarISOToDate(plainMonthDay.Calendar, plainMonthDay.ISODate).MonthCode); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.day */ +function PlainMonthDayProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + return F(CalendarISOToDate(plainMonthDay.Calendar, plainMonthDay.ISODate).Day); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.with */ +function* PlainMonthDayProto_with([temporalMonthDayLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + if (!Q(yield* IsPartialTemporalObject(temporalMonthDayLike))) { + return Throw.TypeError('$1 is not a partial Temporal object', temporalMonthDayLike); + } + const calendar = plainMonthDay.Calendar; + let fields = ISODateToFields(calendar, plainMonthDay.ISODate, 'month-day'); + const partialMonthDay = Q(yield* PrepareCalendarFields(calendar, temporalMonthDayLike as ObjectValue, ['year', 'month', 'month-code', 'day'], [], 'partial')); + fields = CalendarMergeFields(calendar, fields, partialMonthDay); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = Q(yield* CalendarMonthDayFromFields(calendar, fields, overflow)); + return X(CreateTemporalMonthDay(isoDate, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.equals */ +function* PlainMonthDayProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + const other = Q(yield* ToTemporalMonthDay(_other)); + if (CompareISODate(plainMonthDay.ISODate, other.ISODate) !== 0) { + return Value.false; + } + return Value(CalendarEquals(plainMonthDay.Calendar, other.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tostring */ +function* PlainMonthDayProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions)); + return Value(TemporalMonthDayToString(plainMonthDay, showCalendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring */ +function PlainMonthDayProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + return Value(TemporalMonthDayToString(plainMonthDay, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tojson */ +function PlainMonthDayProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + return Value(TemporalMonthDayToString(plainMonthDay, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.valueof */ +function PlainMonthDayProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalMonthDayValue(thisValue)); + return Throw.TypeError('Temporal.PlainMonthDay cannot be converted to primitive value. If you are comparing two Temporal.PlainMonthDay objects with > or <, use Temporal.PlainMonthDay.compare() instead.'); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.toplaindate */ +function* PlainMonthDayProto_toPlainDate([item = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue)); + if (!(item instanceof ObjectValue)) { + return Throw.TypeError('$1 is not an object', item); + } + const calendar = plainMonthDay.Calendar; + const fields = ISODateToFields(calendar, plainMonthDay.ISODate, 'month-day'); + const inputFields = Q(yield* PrepareCalendarFields(calendar, item, ['year'], [], [])); + const mergedFields = CalendarMergeFields(calendar, fields, inputFields); + const isoDate = Q(yield* CalendarDateFromFields(calendar, mergedFields, 'constrain')); + return X(CreateTemporalDate(isoDate, calendar)); +} + +export function bootstrapTemporalPlainMonthDayPrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['calendarId', [PlainMonthDayProto_calendarIdGetter]], + ['monthCode', [PlainMonthDayProto_monthCodeGetter]], + ['day', [PlainMonthDayProto_dayGetter]], + ['with', PlainMonthDayProto_with, 1], + ['equals', PlainMonthDayProto_equals, 1], + ['toString', PlainMonthDayProto_toString, 0], + ['toLocaleString', PlainMonthDayProto_toLocaleString, 0], + ['toJSON', PlainMonthDayProto_toJSON, 0], + ['valueOf', PlainMonthDayProto_valueOf, 0], + ['toPlainDate', PlainMonthDayProto_toPlainDate, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainMonthDay'); + realmRec.Intrinsics['%Temporal.PlainMonthDay.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/PlainTime.mts b/src/intrinsics/Temporal/PlainTime.mts new file mode 100644 index 0000000..855518c --- /dev/null +++ b/src/intrinsics/Temporal/PlainTime.mts @@ -0,0 +1,76 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { + ToIntegerWithTruncation, +} from '../../abstract-ops/temporal/temporal.mts'; +import { bootstrapTemporalPlainTimePrototype } from './PlainTimePrototype.mts'; +import { + Q, Throw, UndefinedValue, Value, type OrdinaryObject, type ValueEvaluator, + type Realm, + type Arguments, + type FunctionCallContext, + F, + CompareTimeRecord, + CreateTemporalTime, + CreateTimeRecord, + IsValidTime, + ToTemporalTime, + type TimeRecord, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaintime-instances */ +export interface TemporalPlainTimeObject extends OrdinaryObject { + readonly InitializedTemporalTime: never; + readonly Time: TimeRecord; +} + +export function isTemporalPlainTimeObject(value: Value): value is TemporalPlainTimeObject { + return 'InitializedTemporalTime' in value; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime */ +function* PlainTimeConstructor([ + _hour = Value.undefined, + _minute = Value.undefined, + _second = Value.undefined, + _millisecond = Value.undefined, + _microsecond = Value.undefined, + _nanosecond = Value.undefined, +]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.PlainTime cannot be called without new'); + } + const hour = _hour instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_hour)); + const minute = _minute instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_minute)); + const second = _second instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_second)); + const millisecond = _millisecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_millisecond)); + const microsecond = _microsecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_microsecond)); + const nanosecond = _nanosecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_nanosecond)); + if (!IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)) { + return Throw.RangeError('Invalid time'); + } + const time = CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond); + return Q(yield* CreateTemporalTime(time, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.from */ +function* PlainTime_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalTime(item, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.compare */ +function* PlainTime_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalTime(_one)); + const two = Q(yield* ToTemporalTime(_two)); + return F(CompareTimeRecord(one.Time, two.Time)); +} + +export function bootstrapTemporalPlainTime(realmRec: Realm) { + const prototype = bootstrapTemporalPlainTimePrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, PlainTimeConstructor, 'PlainTime', 0, prototype, [ + ['from', PlainTime_from, 1], + ['compare', PlainTime_compare, 2], + ]); + realmRec.Intrinsics['%Temporal.PlainTime%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/PlainTimePrototype.mts b/src/intrinsics/Temporal/PlainTimePrototype.mts new file mode 100644 index 0000000..b0db4ea --- /dev/null +++ b/src/intrinsics/Temporal/PlainTimePrototype.mts @@ -0,0 +1,222 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { + GetOptionsObject, GetRoundingIncrementOption, GetRoundingModeOption, RoundingMode, +} from '../../abstract-ops/temporal/addition.mts'; +import { + GetTemporalFractionalSecondDigitsOption, + GetTemporalOverflowOption, + GetTemporalUnitValuedOption, + IsPartialTemporalObject, + MaximumTemporalDurationRoundingIncrement, + TemporalUnit, + ToSecondsStringPrecisionRecord, + ValidateTemporalRoundingIncrement, + ValidateTemporalUnitValue, + type TimeUnit, +} from '../../abstract-ops/temporal/temporal.mts'; +import { + AddDurationToTime, + CompareTimeRecord, + CreateTemporalTime, + DifferenceTemporalPlainTime, + RegulateTime, + RoundTime, + TimeRecordToString, + ToTemporalTime, + ToTemporalTimeRecord, +} from '../../abstract-ops/temporal/plain-time.mts'; +import type { TemporalPlainTimeObject } from './PlainTime.mts'; +import { + Assert, + CreateDataPropertyOrThrow, + F, + JSStringValue, + OrdinaryObjectCreate, + Q, + RequireInternalSlot, + Throw, + UndefinedValue, + Value, + X, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, +} from '#self'; + +function thisTemporalTimeValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalTime')); + return value as TemporalPlainTimeObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.hour */ +function PlainTimeProto_hourGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return F(plainTime.Time.Hour); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute */ +function PlainTimeProto_minuteGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return F(plainTime.Time.Minute); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.second */ +function PlainTimeProto_secondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return F(plainTime.Time.Second); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.millisecond */ +function PlainTimeProto_millisecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return F(plainTime.Time.Millisecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.microsecond */ +function PlainTimeProto_microsecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return F(plainTime.Time.Microsecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.nanosecond */ +function PlainTimeProto_nanosecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return F(plainTime.Time.Nanosecond); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.add */ +function* PlainTimeProto_add([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return Q(yield* AddDurationToTime('add', plainTime, temporalDurationLike)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract */ +function* PlainTimeProto_subtract([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return Q(yield* AddDurationToTime('subtract', plainTime, temporalDurationLike)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with */ +function* PlainTimeProto_with([temporalTimeLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + if (!Q(yield* IsPartialTemporalObject(temporalTimeLike))) { + return Throw.TypeError('$1 is not a partial Temporal object', temporalTimeLike); + } + const partialTime = Q(yield* ToTemporalTimeRecord(temporalTimeLike as never, 'partial')); + const hour = partialTime.Hour ?? plainTime.Time.Hour; + const minute = partialTime.Minute ?? plainTime.Time.Minute; + const second = partialTime.Second ?? plainTime.Time.Second; + const millisecond = partialTime.Millisecond ?? plainTime.Time.Millisecond; + const microsecond = partialTime.Microsecond ?? plainTime.Time.Microsecond; + const nanosecond = partialTime.Nanosecond ?? plainTime.Time.Nanosecond; + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const result = Q(RegulateTime(hour, minute, second, millisecond, microsecond, nanosecond, overflow)); + return Q(yield* CreateTemporalTime(result)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until */ +function* PlainTimeProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return Q(yield* DifferenceTemporalPlainTime('until', plainTime, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since */ +function* PlainTimeProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return Q(yield* DifferenceTemporalPlainTime('since', plainTime, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round */ +function* PlainTimeProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + if (roundTo instanceof UndefinedValue) { + return Throw.TypeError('Options parameter is required'); + } + if (roundTo instanceof JSStringValue) { + const paramString = roundTo; + roundTo = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString)); + } else { + roundTo = Q(GetOptionsObject(roundTo)); + } + const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo)); + const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + const maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit); + Assert(maximum !== 'unset'); + Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false)); + const result = RoundTime(plainTime.Time, roundingIncrement, smallestUnit as TimeUnit | TemporalUnit.Day, roundingMode); + return Q(yield* CreateTemporalTime(result)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals */ +function* PlainTimeProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + const other = Q(yield* ToTemporalTime(_other)); + return CompareTimeRecord(plainTime.Time, other.Time) === 0 ? Value.true : Value.false; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tostring */ +function* PlainTimeProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions)); + const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, 3)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + if (smallestUnit === TemporalUnit.Hour) { + return Throw.RangeError('smallestUnit cannot be hour'); + } + const precision = ToSecondsStringPrecisionRecord( + smallestUnit as Exclude | 'unset', + digits, + ); + const roundResult = RoundTime(plainTime.Time, precision.Increment, precision.Unit, roundingMode); + return Value(TimeRecordToString(roundResult, precision.Precision)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tolocalestring */ +function PlainTimeProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return Value(Q(TimeRecordToString(plainTime.Time, 'auto'))); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tojson */ +function PlainTimeProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainTime = Q(thisTemporalTimeValue(thisValue)); + return Value(Q(TimeRecordToString(plainTime.Time, 'auto'))); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.valueof */ +function PlainTimeProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalTimeValue(thisValue)); + return Throw.TypeError('Temporal.PlainTime cannot be converted to primitive value. If you are comparing two Temporal.PlainTime objects with > or <, use Temporal.PlainTime.compare() instead.'); +} + +export function bootstrapTemporalPlainTimePrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['hour', [PlainTimeProto_hourGetter]], + ['minute', [PlainTimeProto_minuteGetter]], + ['second', [PlainTimeProto_secondGetter]], + ['millisecond', [PlainTimeProto_millisecondGetter]], + ['microsecond', [PlainTimeProto_microsecondGetter]], + ['nanosecond', [PlainTimeProto_nanosecondGetter]], + ['add', PlainTimeProto_add, 1], + ['subtract', PlainTimeProto_subtract, 1], + ['with', PlainTimeProto_with, 1], + ['until', PlainTimeProto_until, 1], + ['since', PlainTimeProto_since, 1], + ['round', PlainTimeProto_round, 1], + ['equals', PlainTimeProto_equals, 1], + ['toString', PlainTimeProto_toString, 0], + ['toLocaleString', PlainTimeProto_toLocaleString, 0], + ['toJSON', PlainTimeProto_toJSON, 0], + ['valueOf', PlainTimeProto_valueOf, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainTime'); + realmRec.Intrinsics['%Temporal.PlainTime.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/PlainYearMonth.mts b/src/intrinsics/Temporal/PlainYearMonth.mts new file mode 100644 index 0000000..fdbd225 --- /dev/null +++ b/src/intrinsics/Temporal/PlainYearMonth.mts @@ -0,0 +1,96 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { + CanonicalizeCalendar, + type CalendarType, +} from '../../abstract-ops/temporal/calendar.mts'; +import { bootstrapTemporalPlainYearMonthPrototype } from './PlainYearMonthPrototype.mts'; +import type { ISODateRecord } from './PlainDate.mts'; +import { + JSStringValue, + Q, + Throw, + Value, + UndefinedValue, + F, + ToIntegerWithTruncation, + type Arguments, + type FunctionCallContext, + type Realm, + type OrdinaryObject, + type ValueEvaluator, + CompareISODate, + CreateISODateRecord, + CreateTemporalYearMonth, + IsValidISODate, + ToTemporalYearMonth, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plainyearmonth-instances */ +export interface TemporalPlainYearMonthObject extends OrdinaryObject { + readonly InitializedTemporalYearMonth: never; + readonly ISODate: ISODateRecord; + readonly Calendar: CalendarType; +} + +export function isTemporalPlainYearMonthObject(o: Value): o is TemporalPlainYearMonthObject { + return 'InitializedTemporalYearMonth' in o; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-iso-year-month-records */ +export interface ISOYearMonthRecord { + readonly Year: number; + readonly Month: number; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth */ +function* PlainYearMonthConstructor([ + isoYear = Value.undefined, + isoMonth = Value.undefined, + _calendar = Value.undefined, + referenceISODay = Value.undefined, +]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.PlainYearMonth cannot be called without new'); + } + if (referenceISODay instanceof UndefinedValue) { + referenceISODay = F(1); + } + const y = Q(yield* ToIntegerWithTruncation(isoYear)); + const m = Q(yield* ToIntegerWithTruncation(isoMonth)); + if (_calendar instanceof UndefinedValue) { + _calendar = Value('iso8601'); + } + if (!(_calendar instanceof JSStringValue)) { + return Throw.TypeError('calendar is not a string'); + } + const calendar = Q(CanonicalizeCalendar(_calendar.stringValue())); + const ref = Q(yield* ToIntegerWithTruncation(referenceISODay)); + if (!IsValidISODate(y, m, ref)) { + return Throw.RangeError('$1-$2-$3 is not a valid date', y, m, ref); + } + const isoDate = CreateISODateRecord(y, m, ref); + return Q(yield* CreateTemporalYearMonth(isoDate, calendar, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.from */ +function* PlainYearMonth_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalYearMonth(item, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.compare */ +function* PlainYearMonth_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalYearMonth(_one)); + const two = Q(yield* ToTemporalYearMonth(_two)); + return F(CompareISODate(one.ISODate, two.ISODate)); +} + +export function bootstrapTemporalPlainYearMonth(realmRec: Realm) { + const prototype = bootstrapTemporalPlainYearMonthPrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, PlainYearMonthConstructor, 'PlainYearMonth', 2, prototype, [ + ['from', PlainYearMonth_from, 1], + ['compare', PlainYearMonth_compare, 2], + ]); + realmRec.Intrinsics['%Temporal.PlainYearMonth%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/PlainYearMonthPrototype.mts b/src/intrinsics/Temporal/PlainYearMonthPrototype.mts new file mode 100644 index 0000000..2f2339a --- /dev/null +++ b/src/intrinsics/Temporal/PlainYearMonthPrototype.mts @@ -0,0 +1,223 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { GetOptionsObject } from '../../abstract-ops/temporal/addition.mts'; +import { + GetTemporalOverflowOption, + GetTemporalShowCalendarNameOption, + IsPartialTemporalObject, + ISODateToFields, +} from '../../abstract-ops/temporal/temporal.mts'; +import { + CalendarDateFromFields, + CalendarEquals, + CalendarISOToDate, + CalendarMergeFields, + CalendarYearMonthFromFields, + PrepareCalendarFields, +} from '../../abstract-ops/temporal/calendar.mts'; +import { CompareISODate, CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts'; +import { + AddDurationToYearMonth, + CreateTemporalYearMonth, + DifferenceTemporalPlainYearMonth, + TemporalYearMonthToString, + ToTemporalYearMonth, +} from '../../abstract-ops/temporal/plain-year-month.mts'; +import type { TemporalPlainYearMonthObject } from './PlainYearMonth.mts'; +import { + F, + ObjectValue, + Q, + RequireInternalSlot, + Throw, + Value, + X, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, +} from '#self'; + +function thisTemporalYearMonthValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalYearMonth')); + return value as TemporalPlainYearMonthObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.calendarid */ +function PlainYearMonthProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Value(plainYearMonth.Calendar); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.era */ +function PlainYearMonthProto_eraGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Value(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).Era); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.erayear */ +function PlainYearMonthProto_eraYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + const result = CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).EraYear; + return result === undefined ? Value.undefined : F(result); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.year */ +function PlainYearMonthProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).Year); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.month */ +function PlainYearMonthProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).Month); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.monthcode */ +function PlainYearMonthProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Value(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).MonthCode); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.daysinyear */ +function PlainYearMonthProto_daysInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).DaysInYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.daysinmonth */ +function PlainYearMonthProto_daysInMonthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).DaysInMonth); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.monthsinyear */ +function PlainYearMonthProto_monthsInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).MonthsInYear); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.inleapyear */ +function PlainYearMonthProto_inLeapYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Value(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).InLeapYear); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.with */ +function* PlainYearMonthProto_with([temporalYearMonthLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + if (!Q(yield* IsPartialTemporalObject(temporalYearMonthLike))) { + return Throw.TypeError('$1 is not a partial Temporal object', temporalYearMonthLike); + } + const calendar = plainYearMonth.Calendar; + let fields = ISODateToFields(calendar, plainYearMonth.ISODate, 'year-month'); + const partialYearMonth = Q(yield* PrepareCalendarFields(calendar, temporalYearMonthLike as ObjectValue, ['year', 'month', 'month-code'], [], 'partial')); + fields = CalendarMergeFields(calendar, fields, partialYearMonth); + const resolvedOptions = Q(GetOptionsObject(options)); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const isoDate = Q(yield* CalendarYearMonthFromFields(calendar, fields, overflow)); + return X(CreateTemporalYearMonth(isoDate, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.add */ +function* PlainYearMonthProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Q(yield* AddDurationToYearMonth('add', plainYearMonth, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.subtract */ +function* PlainYearMonthProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Q(yield* AddDurationToYearMonth('subtract', plainYearMonth, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.until */ +function* PlainYearMonthProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Q(yield* DifferenceTemporalPlainYearMonth('until', plainYearMonth, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.since */ +function* PlainYearMonthProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Q(yield* DifferenceTemporalPlainYearMonth('since', plainYearMonth, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.equals */ +function* PlainYearMonthProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + const other = Q(yield* ToTemporalYearMonth(_other)); + if (CompareISODate(plainYearMonth.ISODate, other.ISODate) !== 0) { + return Value.false; + } + return Value(CalendarEquals(plainYearMonth.Calendar, other.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tostring */ +function* PlainYearMonthProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions)); + return Value(TemporalYearMonthToString(plainYearMonth, showCalendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tolocalestring */ +function PlainYearMonthProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Value(TemporalYearMonthToString(plainYearMonth, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tojson */ +function PlainYearMonthProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + return Value(TemporalYearMonthToString(plainYearMonth, 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.valueof */ +function PlainYearMonthProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalYearMonthValue(thisValue)); + return Throw.TypeError('Temporal.PlainYearMonth cannot be converted to primitive value. If you are comparing two Temporal.PlainYearMonth objects with > or <, use Temporal.PlainYearMonth.compare() instead.'); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.toplaindate */ +function* PlainYearMonthProto_toPlainDate([item = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue)); + if (!(item instanceof ObjectValue)) { + return Throw.TypeError('$1 is not an object', item); + } + const calendar = plainYearMonth.Calendar; + const fields = ISODateToFields(calendar, plainYearMonth.ISODate, 'year-month'); + const inputFields = Q(yield* PrepareCalendarFields(calendar, item, ['day'], [], [])); + const mergedFields = CalendarMergeFields(calendar, fields, inputFields); + const isoDate = Q(yield* CalendarDateFromFields(calendar, mergedFields, 'constrain')); + return X(CreateTemporalDate(isoDate, calendar)); +} + +export function bootstrapTemporalPlainYearMonthPrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['calendarId', [PlainYearMonthProto_calendarIdGetter]], + ['era', [PlainYearMonthProto_eraGetter]], + ['eraYear', [PlainYearMonthProto_eraYearGetter]], + ['year', [PlainYearMonthProto_yearGetter]], + ['month', [PlainYearMonthProto_monthGetter]], + ['monthCode', [PlainYearMonthProto_monthCodeGetter]], + ['daysInYear', [PlainYearMonthProto_daysInYearGetter]], + ['daysInMonth', [PlainYearMonthProto_daysInMonthGetter]], + ['monthsInYear', [PlainYearMonthProto_monthsInYearGetter]], + ['inLeapYear', [PlainYearMonthProto_inLeapYearGetter]], + ['with', PlainYearMonthProto_with, 1], + ['add', PlainYearMonthProto_add, 1], + ['subtract', PlainYearMonthProto_subtract, 1], + ['until', PlainYearMonthProto_until, 1], + ['since', PlainYearMonthProto_since, 1], + ['equals', PlainYearMonthProto_equals, 1], + ['toString', PlainYearMonthProto_toString, 0], + ['toLocaleString', PlainYearMonthProto_toLocaleString, 0], + ['toJSON', PlainYearMonthProto_toJSON, 0], + ['valueOf', PlainYearMonthProto_valueOf, 0], + ['toPlainDate', PlainYearMonthProto_toPlainDate, 1], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainYearMonth'); + realmRec.Intrinsics['%Temporal.PlainYearMonth.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/Temporal/Temporal.mts b/src/intrinsics/Temporal/Temporal.mts new file mode 100644 index 0000000..f8b95e7 --- /dev/null +++ b/src/intrinsics/Temporal/Temporal.mts @@ -0,0 +1,30 @@ +// Using spec: 87c74ec2ae7d55cf10c58674478784d09f323ff1 + +import { bootstrapPrototype } from '../bootstrap.mts'; +import { bootstrapTemporalDuration } from './Duration.mts'; +import { bootstrapTemporalInstant } from './Instant.mts'; +import { bootstrapTemporalPlainDate } from './PlainDate.mts'; +import { bootstrapTemporalPlainDateTime } from './PlainDateTime.mts'; +import { bootstrapTemporalPlainMonthDay } from './PlainMonthDay.mts'; +import { bootstrapTemporalPlainTime } from './PlainTime.mts'; +import { bootstrapTemporalPlainYearMonth } from './PlainYearMonth.mts'; +import { bootstrapTemporalNow } from './Now.mts'; +import { bootstrapTemporalZonedDateTime } from './ZonedDateTime.mts'; +import { type Realm } from '#self'; + +export function bootstrapTemporal(realmRec: Realm) { + const TemporalObject = bootstrapPrototype(realmRec, [ + ['Duration', bootstrapTemporalDuration(realmRec)], + ['Instant', bootstrapTemporalInstant(realmRec)], + ['PlainDateTime', bootstrapTemporalPlainDateTime(realmRec)], + ['Now', bootstrapTemporalNow(realmRec)], + ['PlainDate', bootstrapTemporalPlainDate(realmRec)], + ['PlainTime', bootstrapTemporalPlainTime(realmRec)], + ['PlainYearMonth', bootstrapTemporalPlainYearMonth(realmRec)], + ['PlainMonthDay', bootstrapTemporalPlainMonthDay(realmRec)], + ['ZonedDateTime', bootstrapTemporalZonedDateTime(realmRec)], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal'); + + realmRec.Intrinsics['%Temporal%'] = TemporalObject; + return TemporalObject; +} diff --git a/src/intrinsics/Temporal/ZonedDateTime.mts b/src/intrinsics/Temporal/ZonedDateTime.mts new file mode 100644 index 0000000..7c76d5f --- /dev/null +++ b/src/intrinsics/Temporal/ZonedDateTime.mts @@ -0,0 +1,103 @@ +import { bootstrapConstructor } from '../bootstrap.mts'; +import { + type TimeZoneIdentifier, +} from '../../abstract-ops/temporal/addition.mts'; +import { + FormatOffsetTimeZoneIdentifier, + GetAvailableNamedTimeZoneIdentifier, +} from '../../abstract-ops/temporal/time-zone.mts'; +import { + CanonicalizeCalendar, + type CalendarType, +} from '../../abstract-ops/temporal/calendar.mts'; +import { CreateTemporalZonedDateTime, ToTemporalZonedDateTime } from '../../abstract-ops/temporal/zoned-datetime.mts'; +import { ParseTimeZoneIdentifier } from '../../parser/TemporalParser.mts'; +import { bootstrapTemporalZonedDateTimePrototype } from './ZonedDateTimePrototype.mts'; +import { + JSStringValue, + Value, + Q, + type OrdinaryObject, + type ValueEvaluator, + Throw, + type Realm, + type Arguments, + type FunctionCallContext, + UndefinedValue, + F, + ToBigInt, + R, + CompareEpochNanoseconds, + IsValidEpochNanoseconds, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-zoneddatetime-instances */ +export interface TemporalZonedDateTimeObject extends OrdinaryObject { + readonly InitializedTemporalZonedDateTime: never; + readonly EpochNanoseconds: bigint; + readonly TimeZone: TimeZoneIdentifier; + readonly Calendar: CalendarType; +} +export function isTemporalZonedDateTimeObject(o: Value): o is TemporalZonedDateTimeObject { + return 'InitializedTemporalZonedDateTime' in o; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime */ +function* ZonedDateTimeConstructor([ + _epochNanoseconds = Value.undefined, + _timeZone = Value.undefined, + _calendar = Value.undefined, +]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + if (NewTarget instanceof UndefinedValue) { + return Throw.TypeError('Temporal.ZonedDateTime cannot be called without new'); + } + const epochNanoseconds = R(Q(yield* ToBigInt(_epochNanoseconds))); + if (!IsValidEpochNanoseconds(epochNanoseconds)) { + return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds); + } + if (!(_timeZone instanceof JSStringValue)) { + return Throw.TypeError('timeZone is not a string'); + } + const timeZoneParse = Q(ParseTimeZoneIdentifier(_timeZone.stringValue())); + let timeZone; + if (timeZoneParse.OffsetMinutes === undefined) { + const identifierRecord = GetAvailableNamedTimeZoneIdentifier((timeZoneParse.Name || '') as TimeZoneIdentifier); + if (identifierRecord === undefined) { + return Throw.RangeError('invalid time zone identifier: $1', timeZoneParse.Name || ''); + } + timeZone = identifierRecord.Identifier; + } else { + timeZone = FormatOffsetTimeZoneIdentifier(timeZoneParse.OffsetMinutes); + } + if (_calendar instanceof UndefinedValue) { + _calendar = Value('iso8601'); + } + if (!(_calendar instanceof JSStringValue)) { + return Throw.TypeError('calendar is not a string'); + } + const calendar = Q(CanonicalizeCalendar(_calendar.stringValue())); + return Q(yield* CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.from */ +function* ZonedDateTime_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* ToTemporalZonedDateTime(item, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.compare */ +function* ZonedDateTime_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator { + const one = Q(yield* ToTemporalZonedDateTime(_one)); + const two = Q(yield* ToTemporalZonedDateTime(_two)); + return F(CompareEpochNanoseconds(one.EpochNanoseconds, two.EpochNanoseconds)); +} + +export function bootstrapTemporalZonedDateTime(realmRec: Realm) { + const prototype = bootstrapTemporalZonedDateTimePrototype(realmRec); + + const constructor = bootstrapConstructor(realmRec, ZonedDateTimeConstructor, 'ZonedDateTime', 2, prototype, [ + ['from', ZonedDateTime_from, 1], + ['compare', ZonedDateTime_compare, 2], + ]); + realmRec.Intrinsics['%Temporal.ZonedDateTime%'] = constructor; + return constructor; +} diff --git a/src/intrinsics/Temporal/ZonedDateTimePrototype.mts b/src/intrinsics/Temporal/ZonedDateTimePrototype.mts new file mode 100644 index 0000000..ae0cf8b --- /dev/null +++ b/src/intrinsics/Temporal/ZonedDateTimePrototype.mts @@ -0,0 +1,485 @@ +import { bootstrapPrototype } from '../bootstrap.mts'; +import { + GetOptionsObject, + GetRoundingIncrementOption, + GetRoundingModeOption, + IsOffsetTimeZoneIdentifier, + RoundingMode, +} from '../../abstract-ops/temporal/addition.mts'; +import { + GetTemporalFractionalSecondDigitsOption, + GetDirectionOption, + GetTemporalDisambiguationOption, + GetTemporalOffsetOption, + GetTemporalOverflowOption, + IsPartialTemporalObject, + GetTemporalShowCalendarNameOption, + GetTemporalShowOffsetOption, + GetTemporalShowTimeZoneNameOption, + GetTemporalUnitValuedOption, + ISODateToFields, + MaximumTemporalDurationRoundingIncrement, + TemporalUnit, + ToSecondsStringPrecisionRecord, + ValidateTemporalRoundingIncrement, + ValidateTemporalUnitValue, + type TimeUnit, +} from '../../abstract-ops/temporal/temporal.mts'; +import { + CalendarEquals, + CalendarISOToDate, + CalendarMergeFields, + PrepareCalendarFields, + ToTemporalCalendarIdentifier, +} from '../../abstract-ops/temporal/calendar.mts'; +import { + AddDurationToZonedDateTime, + CreateTemporalZonedDateTime, + DifferenceTemporalZonedDateTime, + InterpretISODateTimeOffset, + TemporalZonedDateTimeToString, + ToTemporalZonedDateTime, +} from '../../abstract-ops/temporal/zoned-datetime.mts'; +import { + GetISODateTimeFor, + GetEpochNanosecondsFor, + GetOffsetNanosecondsFor, + GetNamedTimeZoneNextTransition, + GetNamedTimeZonePreviousTransition, + FormatUTCOffsetNanoseconds, + TimeZoneEquals, + ToTemporalTimeZoneIdentifier, + GetStartOfDay, +} from '../../abstract-ops/temporal/time-zone.mts'; +import { AddDaysToISODate, CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts'; +import { ParseDateTimeUTCOffset } from '../../parser/TemporalParser.mts'; +import { CreateTemporalTime, ToTemporalTime } from '../../abstract-ops/temporal/plain-time.mts'; +import { + CombineISODateAndTimeRecord, + CreateTemporalDateTime, + InterpretTemporalDateTimeFields, + RoundISODateTime, +} from '../../abstract-ops/temporal/plain-date-time.mts'; +import { CreateTemporalInstant } from '../../abstract-ops/temporal/instant.mts'; +import { __ts_cast__ } from '../../helpers.mts'; +import type { TemporalZonedDateTimeObject } from './ZonedDateTime.mts'; +import { + AddTimeDurationToEpochNanoseconds, + Assert, + CreateDataPropertyOrThrow, + F, + JSStringValue, + OrdinaryObjectCreate, + type ObjectValue, + Q, + RequireInternalSlot, + RoundTimeDurationToIncrement, + Throw, + TimeDurationFromEpochNanosecondsDifference, + UndefinedValue, + Value, + X, + type Arguments, + type FunctionCallContext, + type PlainCompletion, + type Realm, + type ValueEvaluator, +} from '#self'; + +function thisTemporalZonedDateTimeValue(value: Value): PlainCompletion { + Q(RequireInternalSlot(value, 'InitializedTemporalZonedDateTime')); + return value as TemporalZonedDateTimeObject; +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.calendarid */ +function ZonedDateTimeProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + return Value(Q(thisTemporalZonedDateTimeValue(thisValue)).Calendar); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.timezoneid */ +function ZonedDateTimeProto_timeZoneIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + return Value(Q(thisTemporalZonedDateTimeValue(thisValue)).TimeZone); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.year */ +function ZonedDateTimeProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return F(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).Year); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.month */ +function ZonedDateTimeProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return F(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).Month); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.monthcode */ +function ZonedDateTimeProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return Value(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).MonthCode); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.day */ +function ZonedDateTimeProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return F(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).Day); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.hour */ +function ZonedDateTimeProto_hourGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Hour); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.minute */ +function ZonedDateTimeProto_minuteGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Minute); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.second */ +function ZonedDateTimeProto_secondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Second); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.millisecond */ +function ZonedDateTimeProto_millisecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Millisecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.microsecond */ +function ZonedDateTimeProto_microsecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Microsecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.nanosecond */ +function ZonedDateTimeProto_nanosecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Nanosecond); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochmilliseconds */ +function ZonedDateTimeProto_epochMillisecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const ns = Q(thisTemporalZonedDateTimeValue(thisValue)).EpochNanoseconds; + return F(Number(ns / 1_000_000n)); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochnanoseconds */ +function ZonedDateTimeProto_epochNanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + return Value(Q(thisTemporalZonedDateTimeValue(thisValue)).EpochNanoseconds); +} + +/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.offsetnanoseconds */ +function ZonedDateTimeProto_offsetNanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return F(GetOffsetNanosecondsFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.with */ +function* ZonedDateTimeProto_with([temporalZonedDateTimeLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + if (!Q(yield* IsPartialTemporalObject(temporalZonedDateTimeLike))) { + return Throw.TypeError('$1 is not a partial Temporal object', temporalZonedDateTimeLike); + } + const epochNs = zonedDateTime.EpochNanoseconds; + const timeZone = zonedDateTime.TimeZone; + const calendar = zonedDateTime.Calendar; + const offsetNanoseconds = GetOffsetNanosecondsFor(timeZone, epochNs); + const isoDateTime = GetISODateTimeFor(timeZone, epochNs); + let fields = ISODateToFields(calendar, isoDateTime.ISODate, 'date'); + fields.Hour = isoDateTime.Time.Hour; + fields.Minute = isoDateTime.Time.Minute; + fields.Second = isoDateTime.Time.Second; + fields.Millisecond = isoDateTime.Time.Millisecond; + fields.Microsecond = isoDateTime.Time.Microsecond; + fields.Nanosecond = isoDateTime.Time.Nanosecond; + fields.OffsetString = FormatUTCOffsetNanoseconds(offsetNanoseconds); + const partialZonedDateTime = Q(yield* PrepareCalendarFields(calendar, temporalZonedDateTimeLike as ObjectValue, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset'], 'partial')); + fields = CalendarMergeFields(calendar, fields, partialZonedDateTime); + const resolvedOptions = Q(GetOptionsObject(options)); + const disambiguation = Q(yield* GetTemporalDisambiguationOption(resolvedOptions)); + const offset = Q(yield* GetTemporalOffsetOption(resolvedOptions, 'prefer')); + const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions)); + const dateTimeResult = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, overflow)); + const offsetString = fields.OffsetString!; + const newOffsetNanoseconds = X(ParseDateTimeUTCOffset(offsetString)); + const epochNanoseconds = Q(InterpretISODateTimeOffset(dateTimeResult.ISODate, dateTimeResult.Time, 'option', newOffsetNanoseconds, timeZone, disambiguation, offset, 'match-exactly')); + return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withplaintime */ +function* ZonedDateTimeProto_withPlainTime([plainTimeLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const timeZone = zonedDateTime.TimeZone; + const calendar = zonedDateTime.Calendar; + const isoDateTime = GetISODateTimeFor(timeZone, zonedDateTime.EpochNanoseconds); + let epochNs; + if (plainTimeLike instanceof UndefinedValue) { + epochNs = Q(GetStartOfDay(timeZone, isoDateTime.ISODate)); + } else { + const plainTime = Q(yield* ToTemporalTime(plainTimeLike)); + const resultISODateTime = CombineISODateAndTimeRecord(isoDateTime.ISODate, plainTime.Time); + epochNs = Q(GetEpochNanosecondsFor(timeZone, resultISODateTime, 'compatible')); + } + return X(CreateTemporalZonedDateTime(epochNs, timeZone, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withtimezone */ +function* ZonedDateTimeProto_withTimeZone([timeZoneLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const timeZone = Q(ToTemporalTimeZoneIdentifier(timeZoneLike)); + return X(CreateTemporalZonedDateTime(zonedDateTime.EpochNanoseconds, timeZone, zonedDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withcalendar */ +function ZonedDateTimeProto_withCalendar([calendarLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const calendar = Q(ToTemporalCalendarIdentifier(calendarLike)); + return X(CreateTemporalZonedDateTime(zonedDateTime.EpochNanoseconds, zonedDateTime.TimeZone, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.add */ +function* ZonedDateTimeProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return Q(yield* AddDurationToZonedDateTime('add', zonedDateTime, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.subtract */ +function* ZonedDateTimeProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return Q(yield* AddDurationToZonedDateTime('subtract', zonedDateTime, temporalDurationLike, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.until */ +function* ZonedDateTimeProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return Q(yield* DifferenceTemporalZonedDateTime('until', zonedDateTime, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.since */ +function* ZonedDateTimeProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return Q(yield* DifferenceTemporalZonedDateTime('since', zonedDateTime, other, options)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round */ +function* ZonedDateTimeProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + if (roundTo instanceof UndefinedValue) { + return Throw.TypeError('roundTo is required'); + } + if (roundTo instanceof JSStringValue) { + const paramString = roundTo; + roundTo = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString)); + } else { + roundTo = Q(GetOptionsObject(roundTo)); + } + const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo)); + const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required')); + Q(ValidateTemporalUnitValue(smallestUnit, 'time', [TemporalUnit.Day])); + let maximum; + let inclusive; + if (smallestUnit === TemporalUnit.Day) { + maximum = 1; + inclusive = true; + } else { + maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit); + Assert(maximum !== 'unset'); + inclusive = false; + } + Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, inclusive)); + if (smallestUnit === TemporalUnit.Nanosecond && roundingIncrement === 1) { + return X(CreateTemporalZonedDateTime(zonedDateTime.EpochNanoseconds, zonedDateTime.TimeZone, zonedDateTime.Calendar)); + } + const thisNs = zonedDateTime.EpochNanoseconds; + const timeZone = zonedDateTime.TimeZone; + const calendar = zonedDateTime.Calendar; + const isoDateTime = GetISODateTimeFor(timeZone, thisNs); + let epochNanoseconds; + if (smallestUnit === TemporalUnit.Day) { + const dateStart = isoDateTime.ISODate; + const dateEnd = AddDaysToISODate(dateStart, 1); + const startNs = Q(GetStartOfDay(timeZone, dateStart)); + Assert(thisNs >= startNs); + const endNs = Q(GetStartOfDay(timeZone, dateEnd)); + Assert(thisNs < endNs); + const dayLengthNs = endNs - startNs; + const dayProgressNs = TimeDurationFromEpochNanosecondsDifference(thisNs, startNs); + const roundedDayNs = X(RoundTimeDurationToIncrement(dayProgressNs, Number(dayLengthNs), roundingMode)); + epochNanoseconds = AddTimeDurationToEpochNanoseconds(roundedDayNs, startNs); + } else { + const roundResult = RoundISODateTime(isoDateTime, roundingIncrement, smallestUnit as TimeUnit | TemporalUnit.Day, roundingMode); + const offsetNanoseconds = GetOffsetNanosecondsFor(timeZone, thisNs); + epochNanoseconds = Q(InterpretISODateTimeOffset(roundResult.ISODate, roundResult.Time, 'option', offsetNanoseconds, timeZone, 'compatible', 'prefer', 'match-exactly')); + } + return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.equals */ +function* ZonedDateTimeProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const other = Q(yield* ToTemporalZonedDateTime(_other)); + if (zonedDateTime.EpochNanoseconds !== other.EpochNanoseconds) { + return Value.false; + } + if (!TimeZoneEquals(zonedDateTime.TimeZone, other.TimeZone)) { + return Value.false; + } + return Value(CalendarEquals(zonedDateTime.Calendar, other.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tostring */ +function* ZonedDateTimeProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const resolvedOptions = Q(GetOptionsObject(options)); + const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions)); + const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions)); + const showOffset = Q(yield* GetTemporalShowOffsetOption(resolvedOptions)); + const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, 3)); + const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset')); + const showTimeZone = Q(yield* GetTemporalShowTimeZoneNameOption(resolvedOptions)); + Q(ValidateTemporalUnitValue(smallestUnit, 'time')); + if (smallestUnit === TemporalUnit.Hour) { + return Throw.RangeError('smallestUnit cannot be hour'); + } + const precision = ToSecondsStringPrecisionRecord( + smallestUnit as Exclude | 'unset', + digits, + ); + return Value(TemporalZonedDateTimeToString(zonedDateTime, precision.Precision, showCalendar, showTimeZone, showOffset, precision.Increment, precision.Unit, roundingMode)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tolocalestring */ +function ZonedDateTimeProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return Value(TemporalZonedDateTimeToString(zonedDateTime, 'auto', 'auto', 'auto', 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tojson */ +function ZonedDateTimeProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return Value(TemporalZonedDateTimeToString(zonedDateTime, 'auto', 'auto', 'auto', 'auto')); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.valueof */ +function ZonedDateTimeProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + Q(thisTemporalZonedDateTimeValue(thisValue)); + return Throw.TypeError('Temporal.ZonedDateTime cannot be converted to primitive value. If you are comparing two Temporal.ZonedDateTime objects with > or <, use Temporal.ZonedDateTime.compare() instead.'); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.startofday */ +function ZonedDateTimeProto_startOfDay(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const timeZone = zonedDateTime.TimeZone; + const calendar = zonedDateTime.Calendar; + const isoDateTime = GetISODateTimeFor(timeZone, zonedDateTime.EpochNanoseconds).ISODate; + const epochNanoseconds = Q(GetStartOfDay(timeZone, isoDateTime)); + return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.gettimezonetransition */ +function* ZonedDateTimeProto_getTimeZoneTransition([directionParam = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const timeZone = zonedDateTime.TimeZone; + if (directionParam instanceof UndefinedValue) { + return Throw.TypeError('directionParam is required'); + } + if (directionParam instanceof JSStringValue) { + const paramString = directionParam; + directionParam = OrdinaryObjectCreate(Value.null); + X(CreateDataPropertyOrThrow(directionParam, Value('direction'), paramString)); + } else { + directionParam = Q(GetOptionsObject(directionParam)); + } + const direction = Q(yield* GetDirectionOption(directionParam)); + if (IsOffsetTimeZoneIdentifier(timeZone)) { + return Value.null; + } + let transition; + if (direction === 'next') { + transition = GetNamedTimeZoneNextTransition(timeZone, zonedDateTime.EpochNanoseconds); + } else { + Assert(direction === 'previous'); + transition = GetNamedTimeZonePreviousTransition(timeZone, zonedDateTime.EpochNanoseconds); + } + if (transition === null) return Value.null; + return X(CreateTemporalZonedDateTime(transition, timeZone, zonedDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toinstant */ +function ZonedDateTimeProto_toInstant(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + return X(CreateTemporalInstant(zonedDateTime.EpochNanoseconds)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaindate */ +function ZonedDateTimeProto_toPlainDate(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return X(CreateTemporalDate(isoDateTime.ISODate, zonedDateTime.Calendar)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaintime */ +function ZonedDateTimeProto_toPlainTime(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return X(CreateTemporalTime(isoDateTime.Time)); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaindatetime */ +function ZonedDateTimeProto_toPlainDateTime(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion { + const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue)); + const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds); + return X(CreateTemporalDateTime(isoDateTime, zonedDateTime.Calendar)); +} + +export function bootstrapTemporalZonedDateTimePrototype(realmRec: Realm) { + const prototype = bootstrapPrototype(realmRec, [ + ['calendarId', [ZonedDateTimeProto_calendarIdGetter]], + ['timeZoneId', [ZonedDateTimeProto_timeZoneIdGetter]], + ['year', [ZonedDateTimeProto_yearGetter]], + ['month', [ZonedDateTimeProto_monthGetter]], + ['monthCode', [ZonedDateTimeProto_monthCodeGetter]], + ['day', [ZonedDateTimeProto_dayGetter]], + ['hour', [ZonedDateTimeProto_hourGetter]], + ['minute', [ZonedDateTimeProto_minuteGetter]], + ['second', [ZonedDateTimeProto_secondGetter]], + ['millisecond', [ZonedDateTimeProto_millisecondGetter]], + ['microsecond', [ZonedDateTimeProto_microsecondGetter]], + ['nanosecond', [ZonedDateTimeProto_nanosecondGetter]], + ['epochMilliseconds', [ZonedDateTimeProto_epochMillisecondsGetter]], + ['epochNanoseconds', [ZonedDateTimeProto_epochNanosecondsGetter]], + ['offsetNanoseconds', [ZonedDateTimeProto_offsetNanosecondsGetter]], + ['with', ZonedDateTimeProto_with, 1], + ['withTimeZone', ZonedDateTimeProto_withTimeZone, 1], + ['withCalendar', ZonedDateTimeProto_withCalendar, 1], + ['withPlainTime', ZonedDateTimeProto_withPlainTime, 0], + ['add', ZonedDateTimeProto_add, 1], + ['subtract', ZonedDateTimeProto_subtract, 1], + ['until', ZonedDateTimeProto_until, 1], + ['since', ZonedDateTimeProto_since, 1], + ['round', ZonedDateTimeProto_round, 1], + ['equals', ZonedDateTimeProto_equals, 1], + ['toString', ZonedDateTimeProto_toString, 0], + ['toLocaleString', ZonedDateTimeProto_toLocaleString, 0], + ['toJSON', ZonedDateTimeProto_toJSON, 0], + ['valueOf', ZonedDateTimeProto_valueOf, 0], + ['startOfDay', ZonedDateTimeProto_startOfDay, 0], + ['getTimeZoneTransition', ZonedDateTimeProto_getTimeZoneTransition, 1], + ['toInstant', [ZonedDateTimeProto_toInstant]], + ['toPlainDate', [ZonedDateTimeProto_toPlainDate]], + ['toPlainTime', [ZonedDateTimeProto_toPlainTime]], + ['toPlainDateTime', [ZonedDateTimeProto_toPlainDateTime]], + ], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.ZonedDateTime'); + realmRec.Intrinsics['%Temporal.ZonedDateTime.prototype%'] = prototype; + return prototype; +} diff --git a/src/intrinsics/ThrowTypeError.mts b/src/intrinsics/ThrowTypeError.mts new file mode 100644 index 0000000..04faa28 --- /dev/null +++ b/src/intrinsics/ThrowTypeError.mts @@ -0,0 +1,21 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { X } from '../completion.mts'; +import { + Assert, + CreateBuiltinFunction, + Realm, + SetIntegrityLevel, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-%throwtypeerror% */ +function ThrowTypeError() { + // 1. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'StrictPoisonPill'); +} + +export function bootstrapThrowTypeError(realmRec: Realm) { + const f = X(CreateBuiltinFunction(ThrowTypeError, 0, Value(''), [], realmRec)); + Assert(X(SetIntegrityLevel(f, 'frozen')) === Value.true); + realmRec.Intrinsics['%ThrowTypeError%'] = f; +} diff --git a/src/intrinsics/TypedArray.mts b/src/intrinsics/TypedArray.mts new file mode 100644 index 0000000..c9e47bf --- /dev/null +++ b/src/intrinsics/TypedArray.mts @@ -0,0 +1,576 @@ +import { + Q, X, type PlainCompletion, +} from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BigIntValue, + BooleanValue, + JSStringValue, + NullValue, + NumberValue, + ObjectValue, + UndefinedValue, + Value, wellKnownSymbols, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { type Mutable, __ts_cast__ } from '../helpers.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Assert, + Call, + Get, + GetMethod, + IsCallable, + IsConstructor, + IteratorToList, + Set, + LengthOfArrayLike, + ToObject, + ToString, + F, + Realm, + type BuiltinFunctionObject, + type FunctionObject, + GetIteratorFromMethod, + AllocateArrayBuffer, + Construct, + GetPrototypeFromConstructor, + isNonNegativeInteger, + IsTypedArrayOutOfBounds, + MakeTypedArrayWithBufferWitnessRecord, + R, + RequireInternalSlot, + SpeciesConstructor, + ToBigInt64, + ToBigUint64, + ToInt16, + ToInt32, + ToInt8, + ToNumber, + ToUint16, + ToUint32, + ToUint8, + ToUint8Clamp, + TypedArrayCreate, + TypedArrayLength, + type ArrayBufferObject, + type ExoticObject, + type Intrinsics, + type TypedArrayWithBufferWitnessRecord, + CloneArrayBuffer, + GetValueFromBuffer, + SetValueInBuffer, + ToIndex, + IsFixedLengthArrayBuffer, + IsDetachedBuffer, + ArrayBufferByteLength, +} from '#self'; + +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, + }, +} as const; +export type TypedArrayConstructorNames = keyof typeof typedArrayInfoByName; + +export const typedArrayInfoByType = { + Int8: typedArrayInfoByName.Int8Array, + Uint8: typedArrayInfoByName.Uint8Array, + Uint8C: typedArrayInfoByName.Uint8ClampedArray, + Int16: typedArrayInfoByName.Int16Array, + Uint16: typedArrayInfoByName.Uint16Array, + Int32: typedArrayInfoByName.Int32Array, + Uint32: typedArrayInfoByName.Uint32Array, + BigInt64: typedArrayInfoByName.BigInt64Array, + BigUint64: typedArrayInfoByName.BigUint64Array, + Float32: typedArrayInfoByName.Float32Array, + Float64: typedArrayInfoByName.Float64Array, +} as const; +export type TypedArrayTypes = keyof typeof typedArrayInfoByType; + +export interface TypedArrayObject extends ExoticObject { + readonly Prototype: ObjectValue | NullValue; + readonly Extensible: BooleanValue; + + ViewedArrayBuffer: ArrayBufferObject | UndefinedValue; + readonly ArrayLength: number | 'auto'; + readonly ByteOffset: number; + readonly ContentType: 'BigInt' | 'Number'; + readonly TypedArrayName: JSStringValue; + readonly ByteLength: number | 'auto'; +} +export function isTypedArrayObject(value: Value): value is TypedArrayObject { + return 'TypedArrayName' in value; +} + +/** https://tc39.es/ecma262/#typedarray-species-create */ +export function* TypedArraySpeciesCreate(exemplar: TypedArrayObject, argumentList: Arguments): ValueEvaluator { + // 1. Assert: exemplar is an Object that has [[TypedArrayName]] and [[ContentType]] internal slots. + Assert(exemplar instanceof ObjectValue + && '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() as TypedArrayConstructorNames].IntrinsicName); + // 3. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). + const constructor = Q(yield* SpeciesConstructor(exemplar, defaultConstructor)); + // 4. Let result be ? TypedArrayCreate(constructor, argumentList). + const result = Q(yield* TypedArrayCreateFromConstructor(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; +} + +/** https://tc39.es/ecma262/#sec-typedarraycreatefromconstructor */ +export function* TypedArrayCreateFromConstructor(constructor: FunctionObject, argumentList: Arguments): ValueEvaluator { + const newTypedArray = Q(yield* Construct(constructor, argumentList)) as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(newTypedArray, 'seq-cst')); + if (argumentList.length === 1 && argumentList[0] instanceof NumberValue) { + if (IsTypedArrayOutOfBounds(taRecord)) { + // TODO: error message + return surroundingAgent.Throw('TypeError', 'Raw', 'TypedArrayCreateFromConstructor:IsTypedArrayOutOfBounds'); + } + const length = TypedArrayLength(taRecord); + if (length < R(argumentList[0])) { + return surroundingAgent.Throw('TypeError', 'TypedArrayTooSmall'); + } + } + return newTypedArray; +} + +/** https://tc39.es/ecma262/#sec-typedarray-create-same-type */ +export function* TypedArrayCreateSameType(exemplar: TypedArrayObject, length: number): ValueEvaluator { + const constructor = surroundingAgent.intrinsic(typedArrayInfoByName[exemplar.TypedArrayName.stringValue() as TypedArrayConstructorNames].IntrinsicName); + const result = Q(yield* TypedArrayCreateFromConstructor(constructor, [Value(length)])); + Assert('TypedArrayName' in result && 'ContentType' in result); + Assert(result.ContentType === exemplar.ContentType); + return result; +} + +/** https://tc39.es/ecma262/#sec-validatetypedarray */ +export function ValidateTypedArray(O: Value, order: 'seq-cst' | 'unordered'): PlainCompletion { + Q(RequireInternalSlot(O, 'TypedArrayName')); + Assert('ViewedArrayBuffer' in O); + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O as TypedArrayObject, order); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds'); + } + return taRecord; +} + +/** https://tc39.es/ecma262/#sec-typedarrayelementsize */ +export function TypedArrayElementSize(O: TypedArrayObject): number { + const type = O.TypedArrayName.stringValue() as TypedArrayConstructorNames; + return typedArrayInfoByName[type].ElementSize; +} + +/** https://tc39.es/ecma262/#sec-typedarrayelementtype */ +export function TypedArrayElementType(O: TypedArrayObject): TypedArrayTypes { + const type = O.TypedArrayName.stringValue() as TypedArrayConstructorNames; + return typedArrayInfoByName[type].ElementType; +} + +/** https://tc39.es/ecma262/#sec-comparetypedarrayelements */ +export function* CompareTypedArrayElements(x: NumberValue | BigIntValue, y: NumberValue | BigIntValue, comparator: FunctionObject | UndefinedValue): ValueEvaluator { + Assert( + (x instanceof NumberValue && y instanceof NumberValue) + || (x instanceof BigIntValue && y instanceof BigIntValue), + ); + if (!(comparator instanceof UndefinedValue)) { + const v = Q(yield* ToNumber(Q(yield* Call(comparator, Value.undefined, [x, y])))); + if (v.isNaN()) { + return F(0); + } + return v; + } + if (x.isNaN() && y.isNaN()) { + return F(0); + } + if (x.isNaN()) { + return F(1); + } + if (y.isNaN()) { + return F(-1); + } + if (x.value < y.value) { + return F(-1); + } + if (x.value > y.value) { + return F(1); + } + if (Object.is(-0, x.value) && Object.is(0, y.value)) { + return F(-1); + } + if (Object.is(0, x.value) && Object.is(-0, y.value)) { + return F(1); + } + return F(0); +} + +/** https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object */ +function TypedArrayConstructor(this: BuiltinFunctionObject) { + // 1. Throw a TypeError exception. + return surroundingAgent.Throw('TypeError', 'NotAConstructor', this); +} + +/** https://tc39.es/ecma262/#sec-allocatetypedarray */ +export function* AllocateTypedArray(constructorName: JSStringValue, newTarget: FunctionObject, defaultProto: keyof Intrinsics, length?: number): ValueEvaluator> { + // 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto). + const proto = Q(yield* GetPrototypeFromConstructor(newTarget, defaultProto)); + // 2. Let obj be TypedArrayCreate(proto). + const obj = TypedArrayCreate(proto) as Mutable; + // 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 = 0; + // 1. Set obj.[[ByteOffset]] to 0. + obj.ByteOffset = 0; + // 1. Set obj.[[ArrayLength]] to 0. + obj.ArrayLength = 0; + } else { + // a. Perform ? AllocateTypedArrayBuffer(obj, length). + Q(yield* AllocateTypedArrayBuffer(obj, length)); + } + // 9. Return obj. + return obj; +} + +/** https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray */ +export function* InitializeTypedArrayFromTypedArray(O: Mutable, srcArray: TypedArrayObject): PlainEvaluator { + const srcData = srcArray.ViewedArrayBuffer as ArrayBufferObject; + const elementType = TypedArrayElementType(O); + const elementSize = TypedArrayElementSize(O); + const srcType = TypedArrayElementType(srcArray); + const srcElementSize = TypedArrayElementSize(srcArray); + const srcByteOffset = srcArray.ByteOffset; + const srcRecord = MakeTypedArrayWithBufferWitnessRecord(srcArray, 'seq-cst'); + if (IsTypedArrayOutOfBounds(srcRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds'); + } + const elementLength = TypedArrayLength(srcRecord); + const byteLength = elementSize * elementLength; + let data; + if (elementType === srcType) { + data = Q(yield* CloneArrayBuffer(srcData, srcByteOffset, byteLength)); + } else { + data = Q(yield* AllocateArrayBuffer(surroundingAgent.intrinsic('%ArrayBuffer%'), byteLength)); + if (srcArray.ContentType !== O.ContentType) { + return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch'); + } + let srcByteIndex = srcByteOffset; + let targetByteIndex = 0; + let count = elementLength; + while (count > 0) { + const value = GetValueFromBuffer(srcData, srcByteIndex, srcType, true, 'unordered'); + Q(yield* SetValueInBuffer(data, targetByteIndex, elementType, value, true, 'unordered')); + srcByteIndex += srcElementSize; + targetByteIndex += elementSize; + count -= 1; + } + } + O.ViewedArrayBuffer = data; + O.ByteLength = byteLength; + O.ByteOffset = 0; + O.ArrayLength = elementLength; +} + +/** https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer */ +export function* InitializeTypedArrayFromArrayBuffer(O: Mutable, buffer: ArrayBufferObject, byteOffset: Value, length: Value): PlainEvaluator { + const elementSize = TypedArrayElementSize(O); + const offset = Q(yield* ToIndex(byteOffset)); + if (offset % elementSize !== 0) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOffsetAlignment', offset, elementSize); + } + const bufferIsFixedLength = IsFixedLengthArrayBuffer(buffer); + let newLength; + if (length !== Value.undefined) { + newLength = Q(yield* ToIndex(length)); + } + if (IsDetachedBuffer(buffer)) { + return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); + } + const bufferByteLength = ArrayBufferByteLength(buffer, 'seq-cst'); + if (length === Value.undefined && !bufferIsFixedLength) { + if (offset > bufferByteLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB'); + } + O.ByteLength = 'auto'; + O.ArrayLength = 'auto'; + } else { + let newByteLength; + if (length === Value.undefined) { + if (bufferByteLength % elementSize !== 0) { + return surroundingAgent.Throw('RangeError', 'TypedArrayLengthAlignment', bufferByteLength, elementSize); + } + newByteLength = bufferByteLength - offset; + if (newByteLength < 0) { + return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB'); + } + } else { + Assert(newLength !== undefined); + newByteLength = newLength * elementSize; + if (offset + newByteLength > bufferByteLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB'); + } + } + O.ByteLength = newByteLength; + O.ArrayLength = newByteLength / elementSize; + } + O.ViewedArrayBuffer = buffer; + O.ByteOffset = offset; +} + +/** https://tc39.es/ecma262/#sec-initializetypedarrayfromlist */ +export function* InitializeTypedArrayFromList(O: Mutable, value: Value[]): PlainEvaluator { + const len = value.length; + Q(yield* AllocateTypedArrayBuffer(O, len)); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = value.shift()!; + Q(yield* Set(O, Pk, kValue, Value.true)); + k += 1; + } + Assert(value.length === 0); +} + +/** https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike */ +export function* InitializeTypedArrayFromArrayLike(O: Mutable, arrayLike: ObjectValue): PlainEvaluator { + const len = Q(yield* LengthOfArrayLike(arrayLike)); + Q(yield* AllocateTypedArrayBuffer(O, len)); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = Q(yield* Get(arrayLike, Pk)); + Q(yield* Set(O, Pk, kValue, Value.true)); + k += 1; + } +} + +/** https://tc39.es/ecma262/#sec-allocatetypedarraybuffer */ +export function* AllocateTypedArrayBuffer(O: TypedArrayObject, length: number): ValueEvaluator { + // 1. Assert: O is an Object that has a [[ViewedArrayBuffer]] internal slot. + Assert(O instanceof ObjectValue && 'ViewedArrayBuffer' in O); + // 2. Assert: O.[[ViewedArrayBuffer]] is undefined. + Assert(O.ViewedArrayBuffer === Value.undefined); + // 3. Assert: length is a non-negative integer. + Assert(isNonNegativeInteger(length)); + // 4. Let constructorName be the String value of O.[[TypedArrayName]]. + const constructorName = O.TypedArrayName.stringValue() as TypedArrayConstructorNames; + // 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 = elementSize * length; + // 7. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + const data = Q(yield* AllocateArrayBuffer(surroundingAgent.intrinsic('%ArrayBuffer%'), byteLength)); + // 8. Set O.[[ViewedArrayBuffer]] to data. + O.ViewedArrayBuffer = data; + // 9. Set O.[[ByteLength]] to byteLength. + __ts_cast__>(O); + O.ByteLength = byteLength; + // 10. Set O.[[ByteOffset]] to 0. + O.ByteOffset = 0; + // 11. Set O.[[ArrayLength]] to length. + O.ArrayLength = length; + // 12. Return O. + return O; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.from */ +function* TypedArray_from([source = Value.undefined, mapper = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + // 1. Let C be the this value. + const C = thisValue; + // 2. If IsConstructor(C) is false, throw a TypeError exception. + if (!IsConstructor(C)) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + // 3. If mapfn is undefined, let mapping be false. + let mapping; + if (mapper === Value.undefined) { + mapping = false; + } else { + // a. If IsCallable(mapfn) is false, throw a TypeError exception. + if (!IsCallable(mapper)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', mapper); + } + // b. Let mapping be true. + mapping = true; + } + // 5. Let usingIterator be ? GetMethod(source, @@iterator). + const usingIterator = Q(yield* GetMethod(source, wellKnownSymbols.iterator)); + // 6. If usingIterator is not undefined, then + if (!(usingIterator instanceof UndefinedValue)) { + const values = Q(yield* IteratorToList(Q(yield* GetIteratorFromMethod(source, usingIterator)))); + const len = values.length; + const targetObj = Q(yield* TypedArrayCreateFromConstructor(C, [F(len)])); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = values.shift()!; + let mappedValue; + if (mapping) { + mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)])); + } else { + mappedValue = kValue; + } + Q(yield* 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(yield* LengthOfArrayLike(arrayLike)); + // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). + const targetObj = Q(yield* TypedArrayCreateFromConstructor(C, [F(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(F(k))); + // b. Let kValue be ? Get(arrayLike, Pk). + const kValue = Q(yield* Get(arrayLike, Pk)); + let mappedValue; + // c. If mapping is true, then + if (mapping) { + // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). + mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)])); + } else { + // d. Else, let mappedValue be kValue. + mappedValue = kValue; + } + // e. Perform ? Set(targetObj, Pk, mappedValue, true). + Q(yield* Set(targetObj, Pk, mappedValue, Value.true)); + // f. Set k to k + 1. + k += 1; + } + // 13. Return targetObj. + return targetObj; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.of */ +function* TypedArray_of(items: Arguments, { thisValue }: FunctionCallContext) { + // 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)) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', C); + } + // 5. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). + const newObj = Q(yield* TypedArrayCreateFromConstructor(C, [F(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(F(k))); + // c. Perform ? Set(newObj, Pk, kValue, true). + Q(yield* Set(newObj, Pk, kValue!, Value.true)); + // d. Set k to k + 1. + k += 1; + } + // 8. Return newObj. + return newObj; +} + +/** https://tc39.es/ecma262/#sec-get-%typedarray%-@@species */ +function TypedArray_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) { + return thisValue; +} + +export function bootstrapTypedArray(realmRec: Realm) { + 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/src/intrinsics/TypedArrayConstructors.mts b/src/intrinsics/TypedArrayConstructors.mts new file mode 100644 index 0000000..9005797 --- /dev/null +++ b/src/intrinsics/TypedArrayConstructors.mts @@ -0,0 +1,85 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, UndefinedValue, Value, wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + AllocateTypedArray, InitializeTypedArrayFromArrayBuffer, InitializeTypedArrayFromArrayLike, InitializeTypedArrayFromList, InitializeTypedArrayFromTypedArray, isTypedArrayObject, typedArrayInfoByName, type TypedArrayConstructorNames, +} from './TypedArray.mts'; +import { + Assert, + GetMethod, + IteratorToList, + ToIndex, + F, + Realm, + GetIteratorFromMethod, + isArrayBufferObject, +} from '#self'; + +export function bootstrapTypedArrayConstructors(realmRec: Realm) { + Object.entries(typedArrayInfoByName).forEach(([TypedArray, info]) => { + /** https://tc39.es/ecma262/#sec-typedarray-constructors */ + function* TypedArrayConstructor(this: Value, args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator { + __ts_cast__(TypedArray); + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + const constructorName = Value(TypedArray); + const proto = `%${TypedArray}.prototype%` as const; + const numberOfArgs = args.length; + if (numberOfArgs === 0) { + return yield* AllocateTypedArray(constructorName, NewTarget, proto, 0); + } else { + const firstArgument = args[0]!; + if (firstArgument instanceof ObjectValue) { + const O = Q(yield* AllocateTypedArray(constructorName, NewTarget, proto)); + if (isTypedArrayObject(firstArgument)) { + Q(yield* InitializeTypedArrayFromTypedArray(O, firstArgument)); + } else if (isArrayBufferObject(firstArgument)) { + let byteOffset; + let length; + if (numberOfArgs > 1) { + byteOffset = args[1]!; + } else { + byteOffset = Value.undefined; + } + if (numberOfArgs > 2) { + length = args[2]!; + } else { + length = Value.undefined; + } + Q(yield* InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length)); + } else { + Assert(firstArgument instanceof ObjectValue && !isTypedArrayObject(firstArgument) && !isArrayBufferObject(firstArgument)); + const usingIterator = Q(yield* GetMethod(firstArgument, wellKnownSymbols.iterator)); + if (!(usingIterator instanceof UndefinedValue)) { + const values = Q(yield* IteratorToList(Q(yield* GetIteratorFromMethod(firstArgument, usingIterator)))); + Q(yield* InitializeTypedArrayFromList(O, values)); + } else { + Q(yield* InitializeTypedArrayFromArrayLike(O, firstArgument)); + } + } + return O; + } else { + Assert(!(firstArgument instanceof ObjectValue)); + const elementLength = Q(yield* ToIndex(firstArgument)); + return yield* AllocateTypedArray(constructorName, NewTarget, proto, elementLength); + } + } + } + + const taConstructor = bootstrapConstructor(realmRec, TypedArrayConstructor, TypedArray, 3, realmRec.Intrinsics[`%${TypedArray as TypedArrayConstructorNames}.prototype%`], [ + ['BYTES_PER_ELEMENT', F(info.ElementSize), undefined, { + Writable: Value.false, + Configurable: Value.false, + }], + ]); + X(taConstructor.SetPrototypeOf(realmRec.Intrinsics['%TypedArray%'])); + realmRec.Intrinsics[`%${TypedArray as TypedArrayConstructorNames}%`] = taConstructor; + }); +} diff --git a/src/intrinsics/TypedArrayPrototype.mts b/src/intrinsics/TypedArrayPrototype.mts new file mode 100644 index 0000000..b6edeae --- /dev/null +++ b/src/intrinsics/TypedArrayPrototype.mts @@ -0,0 +1,722 @@ +import { + Q, X, type ValueEvaluator, + type ValueCompletion, +} from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BigIntValue, + Descriptor, JSStringValue, NumberValue, ObjectValue, Value, wellKnownSymbols, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { bootstrapArrayPrototypeShared, SortIndexedProperties } from './ArrayPrototypeShared.mts'; +import { + CompareTypedArrayElements, + TypedArrayCreateSameType, + TypedArrayElementSize, + TypedArrayElementType, + TypedArraySpeciesCreate, ValidateTypedArray, type TypedArrayObject, +} from './TypedArray.mts'; +import { + Assert, + Call, + CloneArrayBuffer, + CreateArrayIterator, + Get, + GetValueFromBuffer, + TypedArraySetElement, + IsCallable, + IsSharedArrayBuffer, + SameValue, + Set, + SetValueInBuffer, + LengthOfArrayLike, + ToBoolean, + ToBigInt, + ToIntegerOrInfinity, + ToNumber, + ToObject, + ToString, + RequireInternalSlot, + F, + Realm, + type ArrayBufferObject, + MakeTypedArrayWithBufferWitnessRecord, + TypedArrayByteLength, + IsTypedArrayOutOfBounds, + TypedArrayLength, + IsValidIntegerIndex, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer */ +function TypedArrayProto_buffer(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as TypedArrayObject; + // 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; +} + +/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength */ +function TypedArrayProto_byteLength(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as TypedArrayObject; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return F(0); + } + const size = TypedArrayByteLength(taRecord); + return F(size); +} + +/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset */ +function TypedArrayProto_byteOffset(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as TypedArrayObject; + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + Q(RequireInternalSlot(O, 'TypedArrayName')); + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in O); + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return F(0); + } + const offset = O.ByteOffset; + return F(offset); +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin */ +function* TypedArrayProto_copyWithin([target = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue as TypedArrayObject; + let taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + let len = TypedArrayLength(taRecord); + const relativeTarget = Q(yield* ToIntegerOrInfinity(target)); + let targetIndex; + if (relativeTarget === -Infinity) { + targetIndex = 0; + } else if (relativeTarget < 0) { + targetIndex = Math.max(len + relativeTarget, 0); + } else { + targetIndex = Math.min(relativeTarget, len); + } + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let startIndex; + if (relativeStart === -Infinity) { + startIndex = 0; + } else if (relativeStart < 0) { + startIndex = Math.max(len + relativeStart, 0); + } else { + startIndex = Math.min(relativeStart, len); + } + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + let endIndex; + if (relativeEnd === -Infinity) { + endIndex = 0; + } else if (relativeEnd < 0) { + endIndex = Math.max(len + relativeEnd, 0); + } else { + endIndex = Math.min(relativeEnd, len); + } + let count = Math.min(endIndex - startIndex, len - targetIndex); + if (count > 0) { + const buffer = O.ViewedArrayBuffer as ArrayBufferObject; + taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOOB'); + } + len = TypedArrayLength(taRecord); + count = Math.min(count, len - startIndex, len - targetIndex); + const elementSize = TypedArrayElementSize(O); + const byteOffset = O.ByteOffset; + let toByteIndex = (targetIndex * elementSize) + byteOffset; + let fromByteIndex = (startIndex * elementSize) + byteOffset; + let countBytes = count * elementSize; + let direction; + if (fromByteIndex < toByteIndex && toByteIndex < fromByteIndex + countBytes) { + direction = -1; + fromByteIndex = fromByteIndex + countBytes - 1; + toByteIndex = toByteIndex + countBytes - 1; + } else { + direction = 1; + } + while (countBytes > 0) { + const value = GetValueFromBuffer(buffer, fromByteIndex, 'Uint8', true, 'unordered'); + Q(yield* SetValueInBuffer(buffer, toByteIndex, 'Uint8', value, true, 'unordered')); + fromByteIndex += direction; + toByteIndex += direction; + countBytes -= 1; + } + } + return O; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries */ +function TypedArrayProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as TypedArrayObject; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O, 'seq-cst')); + // 3. Return CreateArrayIterator(O, key+value). + return CreateArrayIterator(O, 'key+value'); +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill */ +function* TypedArrayProto_fill([value = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue as TypedArrayObject; + let taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + let len = TypedArrayLength(taRecord); + if (O.ContentType === 'BigInt') { + value = Q(yield* ToBigInt(value)); + } else { + value = Q(yield* ToNumber(value)); + } + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let startIndex; + if (relativeStart === -Infinity) { + startIndex = 0; + } else if (relativeStart < 0) { + startIndex = Math.max(len + relativeStart, 0); + } else { + startIndex = Math.min(relativeStart, len); + } + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = len; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + let endIndex; + if (relativeEnd === -Infinity) { + endIndex = 0; + } else if (relativeEnd < 0) { + endIndex = Math.max(len + relativeEnd, 0); + } else { + endIndex = Math.min(relativeEnd, len); + } + taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOOB'); + } + len = TypedArrayLength(taRecord); + endIndex = Math.min(endIndex, len); + let k = startIndex; + while (k < endIndex) { + const Pk = X(ToString(F(k))); + X(Set(O, Pk, value, Value.true)); + k += 1; + } + return O; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter */ +function* TypedArrayProto_filter([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const O = thisValue as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + const len = TypedArrayLength(taRecord); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + const kept = []; + let captured = 0; + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = X(Get(O, Pk)); + const selected = ToBoolean(Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O]))); + if (selected === Value.true) { + kept.push(kValue); + captured += 1; + } + k += 1; + } + const A = Q(yield* TypedArraySpeciesCreate(O, [F(captured)])); + let n = 0; + for (const e of kept) { + X(Set(A, X(ToString(F(n))), e, Value.true)); + n += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys */ +function TypedArrayProto_keys(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as TypedArrayObject; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O, 'seq-cst')); + // 3. Return CreateArrayIterator(O, key). + return CreateArrayIterator(O, 'key'); +} + +/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length */ +function TypedArrayProto_length(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = thisValue as TypedArrayObject; + Q(RequireInternalSlot(O, 'TypedArrayName')); + Assert('ViewedArrayBuffer' in O); + const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return F(0); + } + const length = TypedArrayLength(taRecord); + return F(length); +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.map */ +function* TypedArrayProto_map([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const O = thisValue as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + const len = TypedArrayLength(taRecord); + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + const A = Q(yield* TypedArraySpeciesCreate(O, [F(len)])); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + const kValue = X(Get(O, Pk)); + const mappedValue = Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O])); + X(Set(A, Pk, mappedValue, Value.true)); + k += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray */ +function* SetTypedArrayFromTypedArray(target: TypedArrayObject, targetOffset: number, source: TypedArrayObject) { + const targetBuffer = target.ViewedArrayBuffer as ArrayBufferObject; + const targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, 'seq-cst'); + if (IsTypedArrayOutOfBounds(targetRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOOB'); + } + const targetLength = TypedArrayLength(targetRecord); + let srcBuffer = source.ViewedArrayBuffer as ArrayBufferObject; + const srcRecord = MakeTypedArrayWithBufferWitnessRecord(source, 'seq-cst'); + if (IsTypedArrayOutOfBounds(srcRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOOB'); + } + const srcLength = TypedArrayLength(srcRecord); + const targetType = TypedArrayElementType(target); + const targetElementSize = TypedArrayElementSize(target); + const targetByteOffset = target.ByteOffset; + const srcType = TypedArrayElementType(source); + const srcElementSize = TypedArrayElementSize(source); + const srcByteOffset = source.ByteOffset; + if (targetOffset === +Infinity) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + if (srcLength + targetOffset > targetLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + if (target.ContentType !== source.ContentType) { + return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch'); + } + let sameSharedArrayBuffer; + if (IsSharedArrayBuffer(srcBuffer) && IsSharedArrayBuffer(targetBuffer) && srcBuffer.ArrayBufferData === targetBuffer.ArrayBufferData) { + sameSharedArrayBuffer = true; + } else { + sameSharedArrayBuffer = false; + } + let srcByteIndex; + if (SameValue(srcBuffer, targetBuffer) === Value.true || sameSharedArrayBuffer) { + const srcByteLength = TypedArrayByteLength(srcRecord); + srcBuffer = Q(yield* CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength)); + srcByteIndex = 0; + } else { + srcByteIndex = srcByteOffset; + } + let targetByteIndex = (targetOffset * targetElementSize) + targetByteOffset; + const limit = targetByteIndex + (targetElementSize * srcLength); + if (srcType === targetType) { + while (targetByteIndex < limit) { + const value = GetValueFromBuffer(srcBuffer, srcByteIndex, 'Uint8', true, 'unordered'); + Q(yield* SetValueInBuffer(targetBuffer, targetByteIndex, 'Uint8', value, true, 'unordered')); + srcByteIndex += 1; + targetByteIndex += 1; + } + } else { + while (targetByteIndex < limit) { + const value = GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, 'unordered'); + Q(yield* SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, 'unordered')); + srcByteIndex += srcElementSize; + targetByteIndex += targetElementSize; + } + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-settypedarrayfromarraylike */ +function* SetTypedArrayFromArrayLike(target: TypedArrayObject, targetOffset: number, source: Value) { + const targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, 'seq-cst'); + if (IsTypedArrayOutOfBounds(targetRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOOB'); + } + const targetLength = TypedArrayLength(targetRecord); + const src = Q(ToObject(source)); + const srcLength = Q(yield* LengthOfArrayLike(src)); + if (targetOffset === +Infinity) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + if (srcLength + targetOffset > targetLength) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + let k = 0; + while (k < srcLength) { + const Pk = X(ToString(F(k))); + const value = Q(yield* Get(src, Pk)); + const targetIndex = F(targetOffset + k); + Q(yield* TypedArraySetElement(target, targetIndex, value)); + k += 1; + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.set-overloaded-offset */ +function* TypedArrayProto_set([source = Value.undefined, offset = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let target be the this value. + const target = thisValue as TypedArrayObject; + // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). + Q(RequireInternalSlot(target, 'TypedArrayName')); + // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. + Assert('ViewedArrayBuffer' in target); + // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). + const targetOffset = Q(yield* ToIntegerOrInfinity(offset)); + // 5. If targetOffset < 0, throw a RangeError exception. + if (targetOffset < 0) { + return surroundingAgent.Throw('RangeError', 'NegativeIndex', 'Offset'); + } + // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then + if (source instanceof ObjectValue && 'TypedArrayName' in source) { + // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). + Q(yield* SetTypedArrayFromTypedArray(target, targetOffset, source as TypedArrayObject)); + } else { // 7. Else, + // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). + Q(yield* SetTypedArrayFromArrayLike(target, targetOffset, source)); + } + // 8. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice */ +function* TypedArrayProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue as TypedArrayObject; + let taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + const srcArrayLength = TypedArrayLength(taRecord); + const relativeStart = Q(yield* ToIntegerOrInfinity(start)); + let startIndex; + if (relativeStart === -Infinity) { + startIndex = 0; + } else if (relativeStart < 0) { + startIndex = Math.max(srcArrayLength + relativeStart, 0); + } else { + startIndex = Math.min(relativeStart, srcArrayLength); + } + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = srcArrayLength; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + let endIndex; + if (relativeEnd === -Infinity) { + endIndex = 0; + } else if (relativeEnd < 0) { + endIndex = Math.max(srcArrayLength + relativeEnd, 0); + } else { + endIndex = Math.min(relativeEnd, srcArrayLength); + } + let countBytes = Math.max(endIndex - startIndex, 0); + const A = Q(yield* TypedArraySpeciesCreate(O, [F(countBytes)])); + if (countBytes > 0) { + taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOOB'); + } + endIndex = Math.min(endIndex, TypedArrayLength(taRecord)); + countBytes = Math.max(endIndex - startIndex, 0); + const srcType = TypedArrayElementType(O); + const targetType = TypedArrayElementType(A); + if (srcType === targetType) { + const srcBuffer = O.ViewedArrayBuffer as ArrayBufferObject; + const targetBuffer = A.ViewedArrayBuffer as ArrayBufferObject; + const elementSize = TypedArrayElementSize(O); + const srcByteOffset = O.ByteOffset; + let srcByteIndex = (startIndex * elementSize) + srcByteOffset; + let targetByteIndex = A.ByteOffset; + const endByteIndex = targetByteIndex + (countBytes * elementSize); + while (targetByteIndex < endByteIndex) { + const value = GetValueFromBuffer(srcBuffer, srcByteIndex, 'Uint8', true, 'unordered'); + Q(yield* SetValueInBuffer(targetBuffer, targetByteIndex, 'Uint8', value, true, 'unordered')); + srcByteIndex += 1; + targetByteIndex += 1; + } + } else { + let n = 0; + let k = startIndex; + while (k < endIndex) { + const Pk = X(ToString(F(k))); + const kValue = X(Get(O, Pk)); + X(Set(A, X(ToString(F(n))), kValue, Value.true)); + k += 1; + n += 1; + } + } + } + return A; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort */ +function* TypedArrayProto_sort([comparator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + if (comparator !== Value.undefined && !IsCallable(comparator)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', comparator); + } + const obj = thisValue as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(obj, 'seq-cst')); + const len = TypedArrayLength(taRecord); + const SortCompare = function* SortCompare(x: Value, y: Value): ValueEvaluator { + Assert(x instanceof NumberValue || x instanceof BigIntValue); + Assert(y instanceof NumberValue || y instanceof BigIntValue); + return yield* CompareTypedArrayElements(x, y, comparator); + }; + const sortedList = Q(yield* SortIndexedProperties(obj, len, SortCompare, 'read-through-holes')); + let j = 0; + while (j < len) { + X(Set(obj, X(ToString(F(j))), sortedList[j], Value.true)); + j += 1; + } + return obj; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.tosorted */ +function* TypedArrayProto_toSorted([comparator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + if (comparator !== Value.undefined && !IsCallable(comparator)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', comparator); + } + const O = thisValue as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + const len = TypedArrayLength(taRecord); + const A = Q(yield* TypedArrayCreateSameType(O, len)); + const SortCompare = function* SortCompare(x: Value, y: Value): ValueEvaluator { + Assert(x instanceof NumberValue || x instanceof BigIntValue); + Assert(y instanceof NumberValue || y instanceof BigIntValue); + return yield* CompareTypedArrayElements(x, y, comparator); + }; + const sortedList = Q(yield* SortIndexedProperties(O, len, SortCompare, 'read-through-holes')); + let j = 0; + while (j < len) { + X(Set(A, X(ToString(F(j))), sortedList[j], Value.true)); + j += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray */ +function* TypedArrayProto_subarray([begin = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue as TypedArrayObject; + Q(RequireInternalSlot(O, 'TypedArrayName')); + Assert('ViewedArrayBuffer' in O); + const buffer = O.ViewedArrayBuffer as ArrayBufferObject; + const srcRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst'); + let srcLength; + if (IsTypedArrayOutOfBounds(srcRecord)) { + srcLength = 0; + } else { + srcLength = TypedArrayLength(srcRecord); + } + const relativeStart = Q(yield* ToIntegerOrInfinity(begin)); + let startIndex; + if (relativeStart === -Infinity) { + startIndex = 0; + } else if (relativeStart < 0) { + startIndex = Math.max(srcLength + relativeStart, 0); + } else { + startIndex = Math.min(relativeStart, srcLength); + } + const elementSize = TypedArrayElementSize(O); + const srcByteOffset = O.ByteOffset; + const beginByteOffset = srcByteOffset + (startIndex * elementSize); + let argumentsList; + if (O.ArrayLength === 'auto' && end === Value.undefined) { + argumentsList = [buffer, F(beginByteOffset)]; + } else { + let relativeEnd; + if (end === Value.undefined) { + relativeEnd = srcLength; + } else { + relativeEnd = Q(yield* ToIntegerOrInfinity(end)); + } + let endIndex; + if (relativeEnd === -Infinity) { + endIndex = 0; + } else if (relativeEnd < 0) { + endIndex = Math.max(srcLength + relativeEnd, 0); + } else { + endIndex = Math.min(relativeEnd, srcLength); + } + const newLength = Math.max(endIndex - startIndex, 0); + argumentsList = [buffer, F(beginByteOffset), F(newLength)]; + } + return Q(yield* TypedArraySpeciesCreate(O, argumentsList)); +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.values */ +function TypedArrayProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let o be the this value. + const O = thisValue as TypedArrayObject; + // 2. Perform ? ValidateTypedArray(O). + Q(ValidateTypedArray(O, 'seq-cst')); + // Return CreateArrayIterator(O, value). + return CreateArrayIterator(O, 'value'); +} + +/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag */ +function TypedArrayProto_toStringTag(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let O be the this value. + const O = thisValue as TypedArrayObject; + // 2. If Type(O) is not Object, return undefined. + if (!(O instanceof ObjectValue)) { + 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(name instanceof JSStringValue); + // 6. Return name. + return name; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.at */ +function* TypedArrayProto_at([index = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + const len = TypedArrayLength(taRecord); + const relativeIndex = Q(yield* ToIntegerOrInfinity(index)); + let k; + if (relativeIndex >= 0) { + k = relativeIndex; + } else { + k = len + relativeIndex; + } + if (k < 0 || k >= len) { + return Value.undefined; + } + return X(Get(O, X(ToString(F(k))))); +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.with */ +function* TypedArrayProto_with([index = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue; + const taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + __ts_cast__(O); + const len = TypedArrayLength(taRecord); + const relativeIndex = Q(yield* ToIntegerOrInfinity(index)); + let actualIndex; + if (relativeIndex >= 0) { + actualIndex = relativeIndex; + } else { + actualIndex = len + relativeIndex; + } + let numericValue; + if (O.ContentType === 'BigInt') { + numericValue = Q(yield* ToBigInt(value)); + } else { + numericValue = Q(yield* ToNumber(value)); + } + if (IsValidIntegerIndex(O, F(actualIndex)) === Value.false) { + return surroundingAgent.Throw('RangeError', 'TypedArrayOOB'); + } + const A = Q(yield* TypedArrayCreateSameType(O, len)); + let k = 0; + while (k < len) { + const Pk = X(ToString(F(k))); + let fromValue; + if (k === actualIndex) { + fromValue = numericValue; + } else { + fromValue = X(Get(O, Pk)); + } + X(Set(A, Pk, fromValue, Value.true)); + k += 1; + } + return A; +} + +/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.toreversed */ +function* TypedArrayProto_toReversed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const O = thisValue as TypedArrayObject; + const taRecord = Q(ValidateTypedArray(O, 'seq-cst')); + const len = TypedArrayLength(taRecord); + const A = Q(yield* TypedArrayCreateSameType(O, len)); + let k = 0; + while (k < len) { + const from = X(ToString(F(len - k - 1))); + const Pk = X(ToString(F(k))); + const fromValue = X(Get(O, from)); + X(Set(A, Pk, fromValue, Value.true)); + k += 1; + } + return A; +} + +export function bootstrapTypedArrayPrototype(realmRec: Realm) { + const ArrayProto_toString = X(Get(realmRec.Intrinsics['%Array.prototype%'], Value('toString'))); + Assert(ArrayProto_toString instanceof ObjectValue); + + 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], + ['at', TypedArrayProto_at, 1], + ['keys', TypedArrayProto_keys, 0], + ['length', [TypedArrayProto_length]], + ['map', TypedArrayProto_map, 1], + ['set', TypedArrayProto_set, 1], + ['slice', TypedArrayProto_slice, 2], + ['sort', TypedArrayProto_sort, 1], + ['toSorted', TypedArrayProto_toSorted, 1], + ['subarray', TypedArrayProto_subarray, 2], + ['values', TypedArrayProto_values, 0], + ['with', TypedArrayProto_with, 2], + ['toReversed', TypedArrayProto_toReversed, 0], + ['toString', ArrayProto_toString], + [wellKnownSymbols.toStringTag, [TypedArrayProto_toStringTag]], + ], realmRec.Intrinsics['%Object.prototype%']); + + bootstrapArrayPrototypeShared(realmRec, proto, 'TypedArray'); + + /** https://tc39.es/ecma262/#sec-%typedarray%.prototype-@@iterator */ + { + const fn = X(Get(proto, 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/src/intrinsics/TypedArrayPrototypes.mts b/src/intrinsics/TypedArrayPrototypes.mts new file mode 100644 index 0000000..f83b6f7 --- /dev/null +++ b/src/intrinsics/TypedArrayPrototypes.mts @@ -0,0 +1,17 @@ +import { Value } from '../value.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { typedArrayInfoByName, type TypedArrayConstructorNames } from './TypedArray.mts'; +import { F, Realm } from '#self'; + +/** https://tc39.es/ecma262/#sec-properties-of-typedarray-prototype-objects */ +export function bootstrapTypedArrayPrototypes(realmRec: Realm) { + Object.entries(typedArrayInfoByName).forEach(([TypedArray, info]) => { + const proto = bootstrapPrototype(realmRec, [ + ['BYTES_PER_ELEMENT', F(info.ElementSize), undefined, { + Writable: Value.false, + Configurable: Value.false, + }], + ], realmRec.Intrinsics['%TypedArray.prototype%']); + realmRec.Intrinsics[`%${TypedArray as TypedArrayConstructorNames}.prototype%`] = proto; + }); +} diff --git a/src/intrinsics/TypedArray_Uint8Array.mts b/src/intrinsics/TypedArray_Uint8Array.mts new file mode 100644 index 0000000..3f9af31 --- /dev/null +++ b/src/intrinsics/TypedArray_Uint8Array.mts @@ -0,0 +1,443 @@ +import { __ts_cast__ } from '../helpers.mts'; +import { Value, type Arguments, type FunctionCallContext } from '../value.mts'; +import { + AllocateTypedArray, type TypedArrayObject, +} from './TypedArray.mts'; +import { assignProps } from './bootstrap.mts'; +import { F } from '#self'; +import { + Assert, CodePointsToString, CreateDataPropertyOrThrow, EnsureCompletion, Get, GetValueFromBuffer, IsTypedArrayOutOfBounds, JSStringValue, MakeTypedArrayWithBufferWitnessRecord, NumberValue, ObjectValue, OrdinaryObjectCreate, Q, R, Realm, RequireInternalSlot, SetValueInBuffer, StringPad, surroundingAgent, ThrowCompletion, ToBoolean, TypedArrayLength, UndefinedValue, X, type ArrayBufferObject, type PlainCompletion, type ValueCompletion, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-uint8array.prototype.tobase64 */ +function* Uint8Array_prototype_toBase64([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const O = thisValue; + Q(ValidateUint8Array(O)); + __ts_cast__(O); + const opts = Q(GetOptionsObject(options)); + let alphabet = Q(yield* Get(opts, Value('alphabet'))); + if (alphabet instanceof UndefinedValue) { + alphabet = Value('base64'); + } + if (!(alphabet instanceof JSStringValue) || (alphabet.stringValue() !== 'base64' && alphabet.stringValue() !== 'base64url')) { + return surroundingAgent.Throw('TypeError', 'InvalidAlphabet'); + } + const omitPadding = ToBoolean(Q(yield* Get(opts, Value('omitPadding')))); + const toEncode = Q(GetUint8ArrayBytes(O)); + let outAscii: string; + if (alphabet.stringValue() === 'base64') { + // Let outAscii be the sequence of code points which results from encoding toEncode according to the base64 encoding specified in section 4 of RFC 4648. Padding is included if and only if omitPadding is false. + outAscii = btoa(String.fromCharCode(...toEncode)); + if (omitPadding !== Value.false) { + outAscii = outAscii.replace(/=/g, ''); + } + } else { + Assert(alphabet.stringValue() === 'base64url'); + // Let outAscii be the sequence of code points which results from encoding toEncode according to the base64url encoding specified in section 5 of RFC 4648. Padding is included if and only if omitPadding is false. + outAscii = btoa(String.fromCharCode(...toEncode)).replace(/\+/g, '-').replace(/\//g, '_'); + if (omitPadding !== Value.false) { + outAscii = outAscii.replace(/=/g, ''); + } + } + return Value(CodePointsToString(outAscii)); +} + +/** https://tc39.es/ecma262/#sec-uint8array.prototype.tohex */ +function Uint8Array_prototype_toHex(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + const O = thisValue; + Q(ValidateUint8Array(O)); + __ts_cast__(O); + const toEncode = Q(GetUint8ArrayBytes(O)); + let out = ''; + for (const byte of toEncode) { + let hex = NumberValue.toString(F(byte), 16); + hex = X(StringPad(hex, Value(2), Value('0'), 'start')); + out += hex.stringValue(); + } + return Value(out); +} + +/** https://tc39.es/ecma262/#sec-uint8array.frombase64 */ +function* Uint8Array_fromBase64([string = Value.undefined, options = Value.undefined]: Arguments) { + if (!(string instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', string); + } + const opts = Q(GetOptionsObject(options)); + let alphabet = Q(yield* Get(opts, Value('alphabet'))); + if (alphabet instanceof UndefinedValue) { + alphabet = Value('base64'); + } + if (!(alphabet instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'InvalidAlphabet'); + } + const alphabetStr = alphabet.stringValue(); + if (alphabetStr !== 'base64' && alphabetStr !== 'base64url') { + return surroundingAgent.Throw('TypeError', 'InvalidAlphabet'); + } + let lastChunkHandling = Q(yield* Get(opts, Value('lastChunkHandling'))); + if (lastChunkHandling instanceof UndefinedValue) { + lastChunkHandling = Value('loose'); + } + if (!(lastChunkHandling instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling'); + } + const lastChunkHandlingStr = lastChunkHandling.stringValue(); + if ((lastChunkHandlingStr !== 'loose' && lastChunkHandlingStr !== 'strict' && lastChunkHandlingStr !== 'stop-before-partial')) { + return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling'); + } + const result = FromBase64(string.stringValue(), alphabetStr, lastChunkHandlingStr); + if (result.Error) { + return ThrowCompletion(result.Error); + } + const resultLength = result.Bytes.length; + const ta = Q(yield* AllocateTypedArray(Value('Uint8Array'), surroundingAgent.intrinsic('%Uint8Array%'), '%Uint8Array.prototype%', resultLength)); + + // TODO: Assert: ta.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is the number of elements in result.[[Bytes]]. + + // Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]]. + for (let i = 0; i < resultLength; i += 1) { + const byte = result.Bytes[i]; + yield* SetValueInBuffer(ta.ViewedArrayBuffer as ArrayBufferObject, ta.ByteOffset + i, 'Uint8', F(byte), true, 'unordered'); + } + return ta; +} + +/** https://tc39.es/ecma262/#sec-uint8array.prototype.setfrombase64 */ +function* Uint8Array_prototype_setFromBase64([string = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const into = thisValue; + Q(ValidateUint8Array(into)); + __ts_cast__(into); + if (!(string instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', string); + } + const opts = Q(GetOptionsObject(options)); + let alphabet = Q(yield* Get(opts, Value('alphabet'))); + if (alphabet instanceof UndefinedValue) { + alphabet = Value('base64'); + } + if (!(alphabet instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'InvalidAlphabet'); + } + const alphabetStr = alphabet.stringValue(); + if (alphabetStr !== 'base64' && alphabetStr !== 'base64url') { + return surroundingAgent.Throw('TypeError', 'InvalidAlphabet'); + } + let lastChunkHandling = Q(yield* Get(opts, Value('lastChunkHandling'))); + if (lastChunkHandling instanceof UndefinedValue) { + lastChunkHandling = Value('loose'); + } + if (!(lastChunkHandling instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling'); + } + const lastChunkHandlingStr = lastChunkHandling.stringValue(); + if ((lastChunkHandlingStr !== 'loose' && lastChunkHandlingStr !== 'strict' && lastChunkHandlingStr !== 'stop-before-partial')) { + return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling'); + } + const taRecord = MakeTypedArrayWithBufferWitnessRecord(into, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds'); + } + const byteLength = TypedArrayLength(taRecord); + const result = FromBase64(string.stringValue(), alphabetStr, lastChunkHandlingStr, byteLength); + const bytes = result.Bytes; + const written = bytes.length; + Assert(written <= byteLength); + yield* SetUint8ArrayBytes(into, bytes); + if (result.Error) { + return ThrowCompletion(result.Error); + } + const resultObject = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataPropertyOrThrow(resultObject, Value('read'), F(result.Read))); + X(CreateDataPropertyOrThrow(resultObject, Value('written'), F(written))); + return resultObject; +} + +/** https://tc39.es/ecma262/#sec-uint8array.fromhex */ +function* Uint8Array_fromHex([string = Value.undefined]: Arguments) { + if (!(string instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', string); + } + const result = FromHex(string.stringValue()); + if (result.Error) { + return ThrowCompletion(result.Error); + } + const resultLength = result.Bytes.length; + const ta = Q(yield* AllocateTypedArray(Value('Uint8Array'), surroundingAgent.intrinsic('%Uint8Array%'), '%Uint8Array.prototype%', resultLength)); + // TODO Assert: ta.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is the number of elements in result.[[Bytes]]. + + // Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]]. + for (let i = 0; i < resultLength; i += 1) { + const byte = result.Bytes[i]; + yield* SetValueInBuffer(ta.ViewedArrayBuffer as ArrayBufferObject, ta.ByteOffset + i, 'Uint8', F(byte), true, 'unordered'); + } + return ta; +} + +/** https://tc39.es/ecma262/#sec-uint8array.prototype.setfromhex */ +function* Uint8Array_prototype_setFromHex([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) { + const into = thisValue; + Q(ValidateUint8Array(into)); + __ts_cast__(into); + if (!(string instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', string); + } + const taRecord = MakeTypedArrayWithBufferWitnessRecord(into, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds'); + } + const byteLength = TypedArrayLength(taRecord); + const result = FromHex(string.stringValue(), byteLength); + const bytes = result.Bytes; + const written = bytes.length; + Assert(written <= byteLength); + yield* SetUint8ArrayBytes(into, bytes); + if (result.Error) { + return ThrowCompletion(result.Error); + } + const resultObject = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataPropertyOrThrow(resultObject, Value('read'), F(result.Read))); + X(CreateDataPropertyOrThrow(resultObject, Value('written'), F(written))); + return resultObject; +} + +/** https://tc39.es/ecma262/#sec-validateuint8array */ +function ValidateUint8Array(ta: Value) { + Q(RequireInternalSlot(ta, 'TypedArrayName')); + __ts_cast__(ta); + if (ta.TypedArrayName.stringValue() !== 'Uint8Array') { + return surroundingAgent.Throw('TypeError', 'NotUint8Array'); + } + return undefined; +} + +/** https://tc39.es/ecma262/#sec-getuint8arraybytes */ +function GetUint8ArrayBytes(ta: TypedArrayObject): PlainCompletion { + const buffer = ta.ViewedArrayBuffer; + const taRecord = MakeTypedArrayWithBufferWitnessRecord(ta, 'seq-cst'); + if (IsTypedArrayOutOfBounds(taRecord)) { + return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds'); + } + const len = TypedArrayLength(taRecord); + const byteOffset = ta.ByteOffset; + const bytes = []; + let index = 0; + while (index < len) { + const byteIndex = byteOffset + index; + const byte = R(GetValueFromBuffer(buffer as ArrayBufferObject, byteIndex, 'Uint8', true, 'unordered')); + Assert(typeof byte === 'number'); + bytes.push(byte); + index += 1; + } + return bytes; +} + +/** https://tc39.es/ecma262/#sec-setuint8arraybytes */ +function* SetUint8ArrayBytes(into: TypedArrayObject, bytes: readonly number[]) { + const offset = into.ByteOffset; + const len = bytes.length; + let index = 0; + while (index < len) { + const byte = bytes[index]; + const byteIndexInBuffer = index + offset; + yield* SetValueInBuffer(into.ViewedArrayBuffer as ArrayBufferObject, byteIndexInBuffer, 'Uint8', F(byte), true, 'unordered'); + index += 1; + } +} + +/** https://tc39.es/ecma262/#sec-skipasciiwhitespace */ +function SkipAsciiWhitespace(string: string, index: number) { + const length = string.length; + while (index < length) { + const char = string.charCodeAt(index); + if (char !== 0x09 && char !== 0x0A && char !== 0x0C && char !== 0x0D && char !== 0x20) { + return index; + } + index += 1; + } + return index; +} + +/** https://tc39.es/ecma262/#sec-decodefinalbase64chunk */ +function DecodeFinalBase64Chunk(chunk: string, throwOnExtraBits: boolean): PlainCompletion { + const chunkLength = chunk.length; + if (chunkLength === 2) { + chunk += 'AA'; + } else { + Assert(chunkLength === 3); + chunk += 'A'; + } + const bytes = DecodeFullLengthBase64Chunk(chunk); + if (chunkLength === 2) { + if (throwOnExtraBits && bytes[1] !== 0) { + return surroundingAgent.Throw('SyntaxError', 'InvalidBase64String'); + } + return [bytes[0]]; + } else { + if (throwOnExtraBits && (bytes[2] !== 0)) { + return surroundingAgent.Throw('SyntaxError', 'InvalidBase64String'); + } + return [bytes[0], bytes[1]]; + } +} + +/** https://tc39.es/ecma262/#sec-decodefulllengthbase64chunk */ +function DecodeFullLengthBase64Chunk(chunk: string): number[] { + // 1. Let byteSequence be the unique sequence of 3 bytes resulting from decoding chunk as base64 (i.e., the sequence such that applying the base64 encoding specified in section 4 of RFC 4648 to byteSequence would produce chunk). + // 2. Return a List whose elements are the elements of byteSequence, in order. + const byteSequence = [...atob(chunk)].map((c) => c.charCodeAt(0)); + return byteSequence; +} + +interface Record { + Read: number; + Bytes: number[]; + Error: undefined | Value; +} +/** https://tc39.es/ecma262/#sec-frombase64 */ +function FromBase64(string: string, alphabet: 'base64' | 'base64url', lastChunkHandling: 'loose' | 'strict' | 'stop-before-partial', maxLength = 2 ** 53 - 1): Record { + if (maxLength === 0) { + return { Read: 0, Bytes: [], Error: undefined }; + } + let read = 0; + const bytes: number[] = []; + let chunk = ''; + let chunkLength = 0; + let index = 0; + const length = string.length; + while (true) { + index = SkipAsciiWhitespace(string, index); + if (index === length) { + if (chunkLength > 0) { + if (lastChunkHandling === 'stop-before-partial') { + return { Read: read, Bytes: bytes, Error: undefined }; + } else if (lastChunkHandling === 'loose') { + if (chunkLength === 1) { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + bytes.push(...X(DecodeFinalBase64Chunk(chunk, false))); + } else { + Assert(lastChunkHandling === 'strict'); + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + } + return { Read: length, Bytes: bytes, Error: undefined }; + } + let char = string.substring(index, index + 1); + index += 1; + if (char === '=') { + if (chunkLength < 2) { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + index = SkipAsciiWhitespace(string, index); + if (chunkLength === 2) { + if (index === length) { + if (lastChunkHandling === 'stop-before-partial') { + return { Read: read, Bytes: bytes, Error: undefined }; + } + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + char = string.substring(index, index + 1); + if (char === '=') { + index = SkipAsciiWhitespace(string, index + 1); + } + } + if (index < length) { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + let throwOnExtraBits; + if (lastChunkHandling === 'strict') { + throwOnExtraBits = true; + } else { + throwOnExtraBits = false; + } + const decodeResult = EnsureCompletion(DecodeFinalBase64Chunk(chunk, throwOnExtraBits)); + if (decodeResult instanceof ThrowCompletion) { + return { Read: read, Bytes: bytes, Error: decodeResult.Value }; + } + bytes.push(...X(decodeResult)); + return { Read: length, Bytes: bytes, Error: undefined }; + } + if (alphabet === 'base64url') { + if (char === '+' || char === '/') { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } else if (char === '-') { + char = '+'; + } else if (char === '_') { + char = '/'; + } + } + if (!/[A-Za-z0-9+/]/.test(char)) { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + const remaining = maxLength - bytes.length; + if ((remaining === 1 && chunkLength === 2) || (remaining === 2 && chunkLength === 3)) { + return { Read: read, Bytes: bytes, Error: undefined }; + } + chunk += char; + chunkLength = chunk.length; + if (chunkLength === 4) { + bytes.push(...X(DecodeFullLengthBase64Chunk(chunk))); + chunk = ''; + chunkLength = 0; + read = index; + if (bytes.length === maxLength) { + return { Read: read, Bytes: bytes, Error: undefined }; + } + } + } +} + +/** https://tc39.es/ecma262/#sec-fromhex */ +function FromHex(string: string, maxLength = 2 ** 53 - 1): Record { + const length = string.length; + const bytes: number[] = []; + let read = 0; + if (length % 2 !== 0) { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidHexString').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + while (read < length && bytes.length < maxLength) { + const hexits = string.substring(read, read + 2); + if ([...hexits].some((c) => !/[0-9a-fA-F]/.test(c))) { + const error = surroundingAgent.Throw('SyntaxError', 'InvalidHexString').Value; + return { Read: read, Bytes: bytes, Error: error }; + } + read += 2; + const byte = parseInt(hexits, 16); + bytes.push(byte); + } + return { Read: read, Bytes: bytes, Error: undefined }; +} + +/** https://tc39.es/ecma262/#sec-getoptionsobject */ +function GetOptionsObject(options: Value) { + if (options instanceof UndefinedValue) { + return OrdinaryObjectCreate(Value.null); + } + if (options instanceof ObjectValue) { + return options; + } + return surroundingAgent.Throw('TypeError', 'NotAnObject', options); +} + +export function bootstrapUint8Array(realmRec: Realm) { + const proto = realmRec.Intrinsics['%Uint8Array.prototype%']; + const constructor = realmRec.Intrinsics['%Uint8Array%']; + assignProps(realmRec, proto, [ + ['toBase64', Uint8Array_prototype_toBase64, 0], + ['setFromBase64', Uint8Array_prototype_setFromBase64, 1], + ['toHex', Uint8Array_prototype_toHex, 0], + ['setFromHex', Uint8Array_prototype_setFromHex, 1], + ]); + assignProps(realmRec, constructor, [ + ['fromBase64', Uint8Array_fromBase64, 1], + ['fromHex', Uint8Array_fromHex, 1], + ]); +} diff --git a/src/intrinsics/URIHandling.mts b/src/intrinsics/URIHandling.mts new file mode 100644 index 0000000..5febfc7 --- /dev/null +++ b/src/intrinsics/URIHandling.mts @@ -0,0 +1,277 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { JSStringValue, Value, type Arguments } from '../value.mts'; +import { CodePointAt, UTF16EncodeCodePoint } from '../static-semantics/all.mts'; +import { Q, type ValueEvaluator } from '../completion.mts'; +import { + Assert, + CreateBuiltinFunction, + Realm, + ToString, +} from '#self'; +import type { CodePoint } from '#self'; + +function utf8Encode(codepoint: CodePoint) { + if (codepoint <= 0x7F) { + return [codepoint]; + } + if (codepoint <= 0x07FF) { + return [ + (((codepoint >> 6) & 0x1F) | 0xC0), + (((codepoint >> 0) & 0x3F) | 0x80), + ]; + } + if (codepoint <= 0xFFFF) { + return [ + (((codepoint >> 12) & 0x0F) | 0xE0), + (((codepoint >> 6) & 0x3F) | 0x80), + (((codepoint >> 0) & 0x3F) | 0x80), + ]; + } + if (codepoint <= 0x10FFFF) { + return [ + (((codepoint >> 18) & 0x07) | 0xF0), + (((codepoint >> 12) & 0x3F) | 0x80), + (((codepoint >> 6) & 0x3F) | 0x80), + (((codepoint >> 0) & 0x3F) | 0x80), + ]; + } + return null; +} + +/** https://encoding.spec.whatwg.org/#utf-8-decoder */ +function utf8Decode(bytes: readonly number[]): CodePoint | null { + let codepoint = 0; + let index = 0; + let bytes_seen = 0; + let bytes_needed = 0; + let lower_boundary = 0x80; + let upper_boundary = 0xBF; + + while (true) { + // If byte is end-of-queue and UTF-8 bytes needed is not 0, then set UTF-8 bytes needed to 0 and return error. + // If byte is end-of-queue, then return finished. + if (!bytes.length) { + if (bytes_needed === 0) { + return null; + } + return codepoint as CodePoint; + } + + const byte = bytes[index]; + if (bytes_needed === 0) { + if (byte >= 0x00 && byte <= 0x7F) { + return byte as CodePoint; + } else if (byte >= 0xC2 && byte <= 0xDF) { + bytes_needed = 1; + codepoint = byte & 0x1F; + } else if (byte >= 0xE0 && byte <= 0xEF) { + if (byte === 0xE0) { + lower_boundary = 0xA0; + } + if (byte === 0xED) { + upper_boundary = 0x9F; + } + bytes_needed = 2; + codepoint = byte & 0xF; + } else if (byte >= 0xF0 && byte <= 0xF4) { + if (byte === 0xF0) { + lower_boundary = 0x90; + } + if (byte === 0xF4) { + upper_boundary = 0x8F; + } + bytes_needed = 3; + codepoint = byte & 0x7; + } else { + return null; + } + index += 1; + continue; + } + + if (byte < lower_boundary || byte > upper_boundary) { + return null; + } + + lower_boundary = 0x80; + upper_boundary = 0xBF; + + codepoint = (codepoint << 6) | (byte & 0x3F); + bytes_seen += 1; + index += 1; + + if (bytes_seen === bytes_needed) { + return codepoint as CodePoint; + } + } +} + +/** https://tc39.es/ecma262/#sec-encode */ +function Encode(_string: JSStringValue, extraUnescaped: string) { + const string = _string.stringValue(); + const len = string.length; + let R = ''; + const alwaysUnescaped = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.!~*\'()'; + const unescapedSet = alwaysUnescaped + extraUnescaped; + let k = 0; + while (k < len) { + // Let C be the code unit at index k within string. + const C = string[k]; + if (unescapedSet.includes(C)) { + k += 1; + R += C; + } else { + const cp = CodePointAt(string, k); + if (cp.IsUnpairedSurrogate) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + k += cp.CodeUnitCount; + // Let Octets be the List of octets resulting by applying the UTF-8 transformation to cp.[[CodePoint]]. + const Octets = utf8Encode(cp.CodePoint)!; + Octets.forEach((octet) => { + const hex = octet.toString(16).toUpperCase().padStart(2, '0'); + R = `${R}%${hex}`; + }); + } + } + return Value(R); +} + +/** https://tc39.es/ecma262/#sec-decode */ +function Decode(_string: JSStringValue, preserveEscapeSet: string) { + const string = _string.stringValue(); + const len = string.length; + let R = ''; + let k = 0; + while (k < len) { + // Let C be the code unit at index k within string. + const C = string[k]; + let S = C; + if (C === '\u{0025}') { + if (k + 3 > len) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + const escape = string.substring(k, k + 3); + const B = ParseHexOctet(string, k + 1); + if (typeof B !== 'number') { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + k += 2; + // Let n be the number of leading 1 bits in B. + const n = B.toString(2).padStart(8, '0').match(/^1+/)?.[0].length || 0; + if (n === 0) { + // Let asciiChar be the code unit whose numeric value is B. + const asciiChar = String.fromCharCode(B); + if (preserveEscapeSet.includes(asciiChar)) { + S = escape; + } else { + S = asciiChar; + } + } else { + if (n === 1 || n > 4) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + const Octets = [B]; + let j = 1; + while (j < n) { + k += 1; + if (k + 3 > len) { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + // If the code unit at index k within string is not U+0025 PERCENT SIGN (%), + if (string[k] !== '\u{0025}') { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + const continuationByte = ParseHexOctet(string, k + 1); + if (typeof continuationByte !== 'number') { + return surroundingAgent.Throw('URIError', 'URIMalformed'); + } + Octets.push(continuationByte); + k += 2; + j += 1; + } + Assert(Octets.length === n); + // If Octets does not contain a valid UTF-8 encoding of a Unicode code point, ... + // Let V be the code point 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'); + } + S = UTF16EncodeCodePoint(V); + } + } + R += S; + k += 1; + } + return Value(R); +} + +function ParseHexOctet(string: string, position: number): number | string[] { + const len = string.length; + Assert(position + 2 <= len); + const hexDigits = string.substring(position, position + 2); + // Let parseResult be ParseText(hexDigits, HexDigits[~Sep]). + // If parseResult is not a Parse Node, return parseResult. + if (!/^[0-9A-Fa-f]{2}$/.test(hexDigits)) { + return []; + } + const parseResult = parseInt(hexDigits, 16); + if (Number.isNaN(parseResult)) { + return []; + } + const n = parseResult; + // eslint-disable-next-line yoda + Assert(0 <= n && n <= 255); + return n; +} + +/** https://tc39.es/ecma262/#sec-decodeuri-encodeduri */ +function* decodeURI([encodedURI = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let uriString be ? ToString(encodedURI). + const uriString = Q(yield* ToString(encodedURI)); + // 2. Let preserveEscapeSet be ";/?:@&=+$,#". + const preserveEscapeSet = ';/?:@&=+$,#'; + // 3. Return ? Decode(uriString, reservedURISet). + return Q(Decode(uriString, preserveEscapeSet)); +} + +/** https://tc39.es/ecma262/#sec-decodeuricomponent-encodeduricomponent */ +function* decodeURIComponent([encodedURIComponent = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let componentString be ? ToString(encodedURIComponent). + const componentString = Q(yield* ToString(encodedURIComponent)); + // 2. Let preserveEscapeSet be the empty String. + const preserveEscapeSet = ''; + // 3. Return ? Decode(componentString, reservedURIComponentSet). + return Q(Decode(componentString, preserveEscapeSet)); +} + +/** https://tc39.es/ecma262/#sec-encodeuri-uri */ +function* encodeURI([uri = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let uriString be ? ToString(uri). + const uriString = Q(yield* ToString(uri)); + // 2. Let extraUnescaped be ";/?:@&=+$,#". + const extraUnescaped = ';/?:@&=+$,#'; + // 3. Return ? Encode(uriString, unescapedURISet). + return Q(Encode(uriString, extraUnescaped)); +} + +/** https://tc39.es/ecma262/#sec-encodeuricomponent-uricomponent */ +function* encodeURIComponent([uriComponent = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let componentString be ? ToString(uriComponent). + const componentString = Q(yield* ToString(uriComponent)); + // 2. Let extraUnescaped be the empty String. + const extraUnescaped = ''; + // 3. Return ? Encode(componentString, unescapedURIComponentSet). + return Q(Encode(componentString, extraUnescaped)); +} + +export function bootstrapURIHandling(realmRec: Realm) { + ([ + ['decodeURI', decodeURI, 1], + ['decodeURIComponent', decodeURIComponent, 1], + ['encodeURI', encodeURI, 1], + ['encodeURIComponent', encodeURIComponent, 1], + ] as const).forEach(([name, f, length]) => { + realmRec.Intrinsics[`%${name}%`] = CreateBuiltinFunction(f, length, Value(name), [], realmRec); + }); +} diff --git a/src/intrinsics/WeakMap.mts b/src/intrinsics/WeakMap.mts new file mode 100644 index 0000000..1dba494 --- /dev/null +++ b/src/intrinsics/WeakMap.mts @@ -0,0 +1,56 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + UndefinedValue, + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { + Q, +} from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { AddEntriesFromIterable } from './Map.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + Get, + IsCallable, + OrdinaryCreateFromConstructor, + Realm, + type FunctionObject, + type OrdinaryObject, +} from '#self'; + +export interface WeakMapObject extends OrdinaryObject { + readonly WeakMapData: { Key: Value | undefined; Value: Value | undefined; }[]; +} +export function isWeakMapObject(object: object): object is WeakMapObject { + return 'WeakMapData' in object; +} +/** https://tc39.es/ecma262/#sec-weakmap-constructor */ +function* WeakMapConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »). + const map = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%WeakMap.prototype%', ['WeakMapData'])) as Mutable; + // 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(yield* Get(map, Value('set'))); + if (!IsCallable(adder)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + // 6. Return ? AddEntriesFromIterable(map, iterable, adder). + return Q(yield* AddEntriesFromIterable(map, iterable, adder)); +} + +export function bootstrapWeakMap(realmRec: Realm) { + const c = bootstrapConstructor(realmRec, WeakMapConstructor, 'WeakMap', 0, realmRec.Intrinsics['%WeakMap.prototype%'], []); + + realmRec.Intrinsics['%WeakMap%'] = c; +} diff --git a/src/intrinsics/WeakMapPrototype.mts b/src/intrinsics/WeakMapPrototype.mts new file mode 100644 index 0000000..5860a18 --- /dev/null +++ b/src/intrinsics/WeakMapPrototype.mts @@ -0,0 +1,203 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { Q, type ValueCompletion, type ValueEvaluator } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { WeakMapObject } from './WeakMap.mts'; +import { + Call, + IsCallable, + SameValue, + RequireInternalSlot, + CanBeHeldWeakly, + Realm, + Throw, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-weakmap.prototype.delete */ +function WeakMapProto_delete([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as WeakMapObject; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. If CanBeHeldWeakly(key) is false, return false. + if (!CanBeHeldWeakly(key)) { + return Value.false; + } + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + const entries = M.WeakMapData; + 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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(M)); + p.Key = undefined; + // ii. Set p.[[Value]] to empty. + p.Value = undefined; + // iii. return true. + return Value.true; + } + } + // 5. Return false. + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-weakmap.prototype.get */ +function WeakMapProto_get([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let m be the this value. + const M = thisValue as WeakMapObject; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. If CanBeHeldWeakly(key) is false, return false. + if (!CanBeHeldWeakly(key)) { + return Value.undefined; + } + // 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do + const entries = M.WeakMapData; + 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; +} + +/** https://tc39.es/proposal-upsert/#sec-weakmap.prototype.getOrInsert */ +function WeakMapProto_getOrInsert([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let m be the this value. + const M = thisValue as WeakMapObject; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(key)) { + return Throw.TypeError('$1 cannot be used as a WeakMap key', key); + } + // 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do + const entries = M.WeakMapData; + 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!; + } + } + // 5. Let p be the Record { [[Key]]: key, [[Value]]: value }. + const p = { Key: key, Value: value }; + // 6. Append p to M.[[WeakMapData]]. + entries.push(p); + // 7. Return value. + return value; +} + +/** https://tc39.es/proposal-upsert/#sec-weakmap.prototype.getOrInsertComputed */ +function* WeakMapProto_getOrInsertComputed([key = Value.undefined, callbackfn = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let m be the this value. + const M = thisValue as WeakMapObject; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(key)) { + return surroundingAgent.Throw('TypeError', 'NotAWeakKey', key); + } + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!IsCallable(callbackfn)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn); + } + // 5. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do + const entries = M.WeakMapData; + 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. Let value be ? Call(callbackfn, undefined, « key »). + const value = Q(yield* Call(callbackfn, Value.undefined, [key])); + // 7. NOTE: The Map may have been modified during execution of callbackfn. + // 8. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], 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 value. + return value; + } + } + // 9. Let p be the Record { [[Key]]: key, [[Value]]: value }. + const p = { Key: key, Value: value }; + // 10. Append p to M.[[WeakMapData]]. + entries.push(p); + // 11. Return value. + return value; +} + +/** https://tc39.es/ecma262/#sec-weakmap.prototype.has */ +function WeakMapProto_has([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as WeakMapObject; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. If CanBeHeldWeakly(key) is false, return false. + if (!CanBeHeldWeakly(key)) { + return Value.false; + } + // 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do + const entries = M.WeakMapData; + 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; +} + +/** https://tc39.es/ecma262/#sec-weakmap.prototype.set */ +function WeakMapProto_set([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let M be the this value. + const M = thisValue as WeakMapObject; + // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). + Q(RequireInternalSlot(M, 'WeakMapData')); + // 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(key)) { + return surroundingAgent.Throw('TypeError', 'WeakCollectionNotObject', key); + } + // 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do + const entries = M.WeakMapData; + 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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(M)); + p.Value = value; + // ii. Return M. + return M; + } + } + // 5. 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); + // 7. Return M. + return M; +} + +export function bootstrapWeakMapPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['delete', WeakMapProto_delete, 1], + ['get', WeakMapProto_get, 1], + ['getOrInsert', WeakMapProto_getOrInsert, 2], + ['getOrInsertComputed', WeakMapProto_getOrInsertComputed, 2], + ['has', WeakMapProto_has, 1], + ['set', WeakMapProto_set, 2], + ], realmRec.Intrinsics['%Object.prototype%'], 'WeakMap'); + + realmRec.Intrinsics['%WeakMap.prototype%'] = proto; +} diff --git a/src/intrinsics/WeakRef.mts b/src/intrinsics/WeakRef.mts new file mode 100644 index 0000000..1e48a91 --- /dev/null +++ b/src/intrinsics/WeakRef.mts @@ -0,0 +1,44 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, + SymbolValue, + UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + AddToKeptObjects, CanBeHeldWeakly, OrdinaryCreateFromConstructor, Realm, type FunctionObject, type OrdinaryObject, +} from '#self'; + +export interface WeakRefObject extends OrdinaryObject { + WeakRefTarget: ObjectValue | SymbolValue | undefined; +} +export function isWeakRef(object: object): object is WeakRefObject { + return 'WeakRefTarget' in object && !('HeldValue' in object); +} +/** https://tc39.es/ecma262/#sec-weak-ref-target */ +function* WeakRefConstructor(this: FunctionObject, [target = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. If CanBeHeldWeakly(target) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(target)) { + return surroundingAgent.Throw('TypeError', 'NotAWeakKey', target); + } + // 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRefPrototype%", « [[WeakRefTarget]] »). + const weakRef = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%WeakRef.prototype%', ['WeakRefTarget'])) as Mutable; + // 4. Perform ! AddToKeptObjects(target). + AddToKeptObjects(target); + // 5. Set weakRef.[[WeakRefTarget]] to target. + weakRef.WeakRefTarget = target; + // 6. Return weakRef + return weakRef; +} + +export function bootstrapWeakRef(realmRec: Realm) { + const weakRefConstructor = bootstrapConstructor(realmRec, WeakRefConstructor, 'WeakRef', 1, realmRec.Intrinsics['%WeakRef.prototype%'], []); + + realmRec.Intrinsics['%WeakRef%'] = weakRefConstructor; +} diff --git a/src/intrinsics/WeakRefPrototype.mts b/src/intrinsics/WeakRefPrototype.mts new file mode 100644 index 0000000..93a87f7 --- /dev/null +++ b/src/intrinsics/WeakRefPrototype.mts @@ -0,0 +1,23 @@ +import { Q, X } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { WeakRefObject } from './WeakRef.mts'; +import { Realm, RequireInternalSlot, WeakRefDeref } from '#self'; +import type { Arguments, ValueCompletion, FunctionCallContext } from '#self'; + +/** https://tc39.es/ecma262/#sec-weak-ref.prototype.deref */ +function WeakRefProto_deref(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let weakRef be the this value. + const weakRef = thisValue as WeakRefObject; + // 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]). + Q(RequireInternalSlot(weakRef, 'WeakRefTarget')); + // 3. Return ! WeakRefDeref(weakRef). + return X(WeakRefDeref(weakRef)); +} + +export function bootstrapWeakRefPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['deref', WeakRefProto_deref, 0], + ], realmRec.Intrinsics['%Object.prototype%'], 'WeakRef'); + + realmRec.Intrinsics['%WeakRef.prototype%'] = proto; +} diff --git a/src/intrinsics/WeakSet.mts b/src/intrinsics/WeakSet.mts new file mode 100644 index 0000000..7ac578b --- /dev/null +++ b/src/intrinsics/WeakSet.mts @@ -0,0 +1,66 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + UndefinedValue, Value, type Arguments, type FunctionCallContext, +} from '../value.mts'; +import { IfAbruptCloseIterator, Q } from '../completion.mts'; +import type { Mutable } from '../helpers.mts'; +import { bootstrapConstructor } from './bootstrap.mts'; +import { + IsCallable, + OrdinaryCreateFromConstructor, + Call, + Get, + GetIterator, + type OrdinaryObject, + Realm, + type FunctionObject, + IteratorStepValue, +} from '#self'; + +export interface WeakSetObject extends OrdinaryObject { + readonly WeakSetData: (Value | undefined)[]; +} +export function isWeakSetObject(object: object): object is WeakSetObject { + return 'WeakSetData' in object; +} +/** https://tc39.es/ecma262/#sec-weakset-iterable */ +function* WeakSetConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) { + // 1. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this); + } + // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakSet.prototype%", « [[WeakSetData]] »). + const set = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%WeakSet.prototype%', ['WeakSetData'])) as Mutable; + // 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(yield* Get(set, Value('add'))); + // 6. If IsCallable(adder) is false, throw a TypeError exception. + if (!IsCallable(adder)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', adder); + } + // 7. Let iteratorRecord be ? GetIterator(iterable). + const iteratorRecord = Q(yield* GetIterator(iterable, 'sync')); + // 8. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // b. If next is false, return set. + if (next === 'done') { + return set; + } + // d. Let status be Call(adder, set, « next »). + const status = yield* Call(adder, set, [next]); + // e. IfAbruptCloseIterator(status, iteratorRecord). + IfAbruptCloseIterator(status, iteratorRecord); + } +} + +export function bootstrapWeakSet(realmRec: Realm) { + const c = bootstrapConstructor(realmRec, WeakSetConstructor, 'WeakSet', 0, realmRec.Intrinsics['%WeakSet.prototype%'], []); + realmRec.Intrinsics['%WeakSet%'] = c; +} diff --git a/src/intrinsics/WeakSetPrototype.mts b/src/intrinsics/WeakSetPrototype.mts new file mode 100644 index 0000000..9620d0f --- /dev/null +++ b/src/intrinsics/WeakSetPrototype.mts @@ -0,0 +1,99 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, + type Arguments, + type FunctionCallContext, +} from '../value.mts'; +import { Q, type ValueCompletion } from '../completion.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import type { WeakSetObject } from './WeakSet.mts'; +import { + SameValue, + RequireInternalSlot, + CanBeHeldWeakly, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-weakset.prototype.add */ +function WeakSetProto_add([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be this value. + const S = thisValue as WeakSetObject; + // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). + Q(RequireInternalSlot(S, 'WeakSetData')); + // 3. If CanBeHeldWeakly(value) is false, throw a TypeError exception. + if (!CanBeHeldWeakly(value)) { + return surroundingAgent.Throw('TypeError', 'WeakCollectionNotObject', value); + } + // 4. For each e that is an element of entries, do + const entries = S.WeakSetData; + 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); + // 6. Return S. + return S; +} + +/** https://tc39.es/ecma262/#sec-weakset.prototype.delete */ +function WeakSetProto_delete([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value.` + const S = thisValue as WeakSetObject; + // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). + Q(RequireInternalSlot(S, 'WeakSetData')); + // 3. If CanBeHeldWeakly(value) is false, return false. + if (!CanBeHeldWeakly(value)) { + return Value.false; + } + // 4. For each element e of S.[[WeakSetData]], do + const entries = S.WeakSetData; + 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. + Q(surroundingAgent.debugger_tryTouchDuringPreview(S)); + entries[i] = undefined; + // ii. Return true. + return Value.true; + } + } + // 5. Return false. + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-weakset.prototype.has */ +function WeakSetProto_has([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion { + // 1. Let S be the this value. + const S = thisValue as WeakSetObject; + // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). + Q(RequireInternalSlot(S, 'WeakSetData')); + // 3. If CanBeHeldWeakly(value) is false, return false. + if (!CanBeHeldWeakly(value)) { + return Value.false; + } + // 4. For each element e of S.[[WeakSetData]], do + const entries = S.WeakSetData; + 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; + } + } + // 5. Return false. + return Value.false; +} + +export function bootstrapWeakSetPrototype(realmRec: Realm) { + 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/src/intrinsics/WrapForValidIteratorPrototype.mts b/src/intrinsics/WrapForValidIteratorPrototype.mts new file mode 100644 index 0000000..fc6f7e2 --- /dev/null +++ b/src/intrinsics/WrapForValidIteratorPrototype.mts @@ -0,0 +1,65 @@ +import { __ts_cast__ } from '../helpers.mts'; +import { bootstrapPrototype } from './bootstrap.mts'; +import { + type IteratorObject, + + Assert, + Call, + CreateIteratorResultObject, + GetMethod, + ObjectValue, + Q, + RequireInternalSlot, + UndefinedValue, + Value, + type Arguments, + type FunctionCallContext, + type IteratorRecord, + type Realm, + type ValueEvaluator, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-%wrapforvaliditeratorprototype%.next */ +function* WrapForValidIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[Iterated]]). + Q(RequireInternalSlot(O, 'Iterated')); + // 3. Let iteratorRecord be O.[[Iterated]]. + __ts_cast__(O); + const iteratorRecord: IteratorRecord = O.Iterated; + // 4. Return ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + return Q(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator)); +} + +/** https://tc39.es/ecma262/#sec-%wrapforvaliditeratorprototype%.return */ +function* WrapForValidIteratorPrototype_return(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + // 1. Let O be this value. + const O = thisValue; + // 2. Perform ? RequireInternalSlot(O, [[Iterated]]). + Q(RequireInternalSlot(O, 'Iterated')); + // 3. Let iterator be O.[[Iterated]].[[Iterator]]. + __ts_cast__(O); + const iteratorRecord: IteratorRecord = O.Iterated; + const iterator = iteratorRecord.Iterator; + // 4. Assert: iterator is an Object. + Assert(iterator instanceof ObjectValue); + // 5. Let returnMethod be ? GetMethod(iterator, "return"). + const returnMethod = Q(yield* GetMethod(iterator, Value('return'))); + // 6. If returnMethod is undefined, then + if (returnMethod instanceof UndefinedValue) { + // a. Return CreateIteratorResultObject(undefined, true). + return CreateIteratorResultObject(Value.undefined, Value.true); + } + // 7. Return ? Call(returnMethod, iterator). + return Q(yield* Call(returnMethod, iterator)); +} + +export function bootstrapWrapForValidIteratorPrototype(realmRec: Realm) { + const proto = bootstrapPrototype(realmRec, [ + ['next', WrapForValidIteratorPrototype_next, 0], + ['return', WrapForValidIteratorPrototype_return, 0], + ], realmRec.Intrinsics['%Iterator.prototype%']); + + realmRec.Intrinsics['%WrapForValidIteratorPrototype%'] = proto; +} diff --git a/src/intrinsics/bootstrap.mts b/src/intrinsics/bootstrap.mts new file mode 100644 index 0000000..3f7175e --- /dev/null +++ b/src/intrinsics/bootstrap.mts @@ -0,0 +1,153 @@ +import { + Descriptor, + JSStringValue, + NullValue, + ObjectValue, + SymbolValue, + UndefinedValue, + Value, + wellKnownSymbols, + type DescriptorInit, + type NativeSteps, +} from '../value.mts'; +import { X } from '../completion.mts'; +import { + Assert, + CreateBuiltinFunction, + markBuiltinFunctionAsConstructor, + OrdinaryObjectCreate, + Realm, + type FunctionObject, +} from '#self'; + +type Accessor = [ + getter: NativeSteps | UndefinedValue | FunctionObject, + setter?: NativeSteps | UndefinedValue | FunctionObject, +]; + +type Props = [ + name: string | JSStringValue | SymbolValue, + value: Accessor | NativeSteps | Value, + fnLength?: number, + desc?: DescriptorInit, + async?: boolean +]; +/** https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects */ +export function assignProps(realmRec: Realm, obj: ObjectValue, props: readonly (Props | undefined)[]) { + for (const item of props) { + if (item === undefined) { + continue; + } + const [n, v, len, descriptor, async] = item; + const name = n instanceof Value ? n : 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, + 0, + name, + [], + realmRec, + undefined, + Value('get'), + async, + ); + } + if (typeof setter === 'function') { + setter = CreateBuiltinFunction( + setter, + 1, + name, + [], + realmRec, + undefined, + Value('set'), + async, + ); + } + 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, len, name, [], realmRec, undefined, undefined, async); + } else { + value = v; + } + obj.properties.set(name, Descriptor({ + Value: value, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + ...descriptor, + })); + } + } +} + +export function bootstrapPrototype(realmRec: Realm, props: readonly (Props | undefined)[], Prototype: ObjectValue | NullValue, stringTag?: string) { + Assert(Prototype !== undefined); + const proto = OrdinaryObjectCreate(Prototype); + + assignProps(realmRec, proto, props); + + if (stringTag !== undefined) { + X(proto.DefineOwnProperty(wellKnownSymbols.toStringTag, Descriptor({ + Value: Value(stringTag), + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + + return proto; +} + +export function bootstrapConstructor(realmRec: Realm, Constructor: NativeSteps, name: string, length: number, Prototype: ObjectValue, props: readonly Props[] = []) { + const cons = CreateBuiltinFunction( + markBuiltinFunctionAsConstructor(Constructor), + length, + Value(name), + [], + realmRec, + ); + + X(cons.DefineOwnProperty(Value('prototype'), Descriptor({ + Value: Prototype, + Writable: Value.false, + Enumerable: Value.false, + Configurable: Value.false, + }))); + + if (!Prototype.properties.has('constructor')) { + X(Prototype.DefineOwnProperty(Value('constructor'), Descriptor({ + Value: cons, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.true, + }))); + } + + assignProps(realmRec, cons, props); + + return cons; +} diff --git a/src/intrinsics/eval.mts b/src/intrinsics/eval.mts new file mode 100644 index 0000000..b1b4a98 --- /dev/null +++ b/src/intrinsics/eval.mts @@ -0,0 +1,16 @@ +import { Q, type ValueEvaluator } from '../completion.mts'; +import { Value, type Arguments } from '../value.mts'; +import { + CreateBuiltinFunction, + PerformEval, +} from '#self'; +import type { Realm } from '#self'; + +/** https://tc39.es/ecma262/#sec-eval-x */ +function* Eval([x = Value.undefined]: Arguments): ValueEvaluator { + return Q(yield* PerformEval(x, false, false)); +} + +export function bootstrapEval(realmRec: Realm) { + realmRec.Intrinsics['%eval%'] = CreateBuiltinFunction(Eval, 1, Value('eval'), [], realmRec); +} diff --git a/src/intrinsics/isFinite.mts b/src/intrinsics/isFinite.mts new file mode 100644 index 0000000..63e2a16 --- /dev/null +++ b/src/intrinsics/isFinite.mts @@ -0,0 +1,23 @@ +import { Value, type Arguments } from '../value.mts'; +import { Q, type ValueEvaluator } from '../completion.mts'; +import { + ToNumber, + CreateBuiltinFunction, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-isfinite-number */ +function* IsFinite([number = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let num be ? ToNumber(number). + const num = Q(yield* 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: Realm) { + realmRec.Intrinsics['%isFinite%'] = CreateBuiltinFunction(IsFinite, 1, Value('isFinite'), [], realmRec); +} diff --git a/src/intrinsics/isNaN.mts b/src/intrinsics/isNaN.mts new file mode 100644 index 0000000..ff2049d --- /dev/null +++ b/src/intrinsics/isNaN.mts @@ -0,0 +1,23 @@ +import { Value, type Arguments } from '../value.mts'; +import { Q, type ValueEvaluator } from '../completion.mts'; +import { + ToNumber, + CreateBuiltinFunction, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-isnan-number */ +function* IsNaN([number = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let num be ? ToNumber(number). + const num = Q(yield* 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: Realm) { + realmRec.Intrinsics['%isNaN%'] = CreateBuiltinFunction(IsNaN, 1, Value('isNaN'), [], realmRec); +} diff --git a/src/intrinsics/parseFloat.mts b/src/intrinsics/parseFloat.mts new file mode 100644 index 0000000..8f84e92 --- /dev/null +++ b/src/intrinsics/parseFloat.mts @@ -0,0 +1,78 @@ +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { Value, type Arguments } from '../value.mts'; +import { + TrimString, +} from '../runtime-semantics/all.mts'; +import { + CreateBuiltinFunction, + ToString, + F, + Realm, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-parsefloat-string */ +function* ParseFloat([string = Value.undefined]: Arguments): ValueEvaluator { + // 1. Let inputString be ? ToString(string). + const inputString = Q(yield* 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 F(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 F(+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 F(parseFloat(numberString.slice(0, index)) * multiplier); +} + +export function bootstrapParseFloat(realmRec: Realm) { + realmRec.Intrinsics['%parseFloat%'] = CreateBuiltinFunction(ParseFloat, 1, Value('parseFloat'), [], realmRec); +} diff --git a/src/intrinsics/parseInt.mts b/src/intrinsics/parseInt.mts new file mode 100644 index 0000000..e36613b --- /dev/null +++ b/src/intrinsics/parseInt.mts @@ -0,0 +1,101 @@ +import { TrimString } from '../runtime-semantics/all.mts'; +import { Q, X, type ValueEvaluator } from '../completion.mts'; +import { Value, type Arguments } from '../value.mts'; +import { + Assert, + CreateBuiltinFunction, + ToInt32, + ToString, + F, R as MathematicalValue, + Realm, +} from '#self'; + +function digitToNumber(_digit: string) { + let 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: string, R: number) { + 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: string, R: number) { + 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; +} + +/** https://tc39.es/ecma262/#sec-parseint-string-radix */ +function* ParseInt([string = Value.undefined, radix = Value.undefined]: Arguments): ValueEvaluator { + const inputString = Q(yield* 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 = MathematicalValue(Q(yield* ToInt32(radix))); + let stripPrefix = true; + if (R !== 0) { + if (R < 2 || R > 36) { + return F(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 F(NaN); + } + const mathInt = stringToRadixNumber(Z, R); + if (mathInt === 0) { + if (sign === -1) { + return F(-0); + } + return F(+0); + } + const number = mathInt; + return F(sign * number); +} + +export function bootstrapParseInt(realmRec: Realm) { + realmRec.Intrinsics['%parseInt%'] = CreateBuiltinFunction(ParseInt, 2, Value('parseInt'), [], realmRec); +} diff --git a/src/messages.mts b/src/messages.mts new file mode 100644 index 0000000..8f438a3 --- /dev/null +++ b/src/messages.mts @@ -0,0 +1,209 @@ +import type { AbstractModuleRecord } from './modules.mts'; +import type { + BooleanValue, JSStringValue, NumberValue, ObjectValue, PropertyKeyValue, +} from './value.mts'; +import { inspect, PrivateName, Value } from './index.mts'; +import type { ArrayBufferObject, FunctionObject } from '#self'; + +function i(V: unknown) { + if (V instanceof Value) { + return inspect(V); + } + if (V instanceof PrivateName) { + return `${V.Description.stringValue()}`; + } + return `${V}`; +} + +export const Raw = (s: S) => s; + +export const AlreadyDeclared = (n: JSStringValue | PrivateName | string) => `${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: JSStringValue) => `Assignment to constant variable ${i(n)}`; +export const AwaitInFormalParameters = () => 'await is not allowed in function parameters'; +export const AwaitInClassStaticBlock = () => 'await is not allowed in class static blocks'; +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 BigIntLiteralCannotLeadingZero = () => 'BigInt literal cannot have leading zero.'; +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: Value, b: ArrayBufferObject) => `${i(k)} is not the [[ArrayBufferDetachKey]] of ${i(b)}`; +export const CannotAllocateDataBlock = () => 'Cannot allocate memory'; +export const CannotCreateProxyWith = (x: string, y: string) => `Cannot create a proxy with a ${x} as ${y}`; +export const CannotConstructAbstractFunction = (c: FunctionObject) => `Cannot construct abstract ${i(c)}`; +export const CannotConvertDecimalToBigInt = (n: NumberValue) => `Cannot convert ${i(n)} to a BigInt because it is not an integer`; +export const CannotConvertSymbol = (t: string) => `Cannot convert a Symbol value to a ${t}`; +export const CannotConvertToBigInt = (v: Value) => `Cannot convert ${i(v)} to a BigInt`; +export const CannotConvertToObject = (t: 'null' | 'undefined') => `Cannot convert ${t} to object`; +export const CannotConvertToTemporalDuration = (t: Value) => `Cannot convert ${i(t)} to Temporal.Duration`; +export const CannotDefineProperty = (p: PropertyKeyValue) => `Cannot define property ${i(p)}`; +export const CannotDeleteProperty = (p: PropertyKeyValue) => `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: PropertyKeyValue, o: unknown) => `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: Value) => `${i(f)} cannot be invoked without new`; +export const CouldNotResolveModule = (s: JSStringValue | string, from?: string) => `Could not resolve module ${i(s)} from ${from ? i(from) : 'no referrer'}`; +export const DataViewOOB = () => 'Offset is outside the bounds of the DataView'; +export const DeferredModuleNotReady = (m: AbstractModuleRecord) => `Module ${m.HostDefined?.specifier ?? ''} is not ready for synchronous execution`; +export const DeleteIdentifier = () => 'Delete of identifier in strict mode'; +export const DeletePrivateName = () => 'Private fields cannot be deleted'; +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 DuplicateImportAttribute = (a: string) => `Duplicate import attribute ${i(a)}`; +export const DuplicateProto = () => 'An object literal may only have one __proto__ property'; +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 LegacyOctalLiteralInStrictMode = () => 'Legacy octal literals are not allowed in strict mode'; +export const IllegalBreakContinue = (isBreak: boolean) => `Illegal ${isBreak ? 'break' : 'continue'} statement`; +export const IllegalOctalEscape = () => 'Illegal octal escape'; +export const InternalSlotMissing = (_o: ObjectValue, s: string) => `Internal slot ${s} is missing`; +export const InvalidArrayLength = (l: Value | number) => `Invalid array length: ${i(l)}`; +export const InvalidAssignmentTarget = () => 'Invalid assignment target'; +export const InvalidCalendar = (id: string) => `Invalid calendar: ${i(id)}`; +export const InvalidMonth = () => 'Invalid month'; +export const InvalidLeapMonth = () => 'Invalid leap month'; +export const InvalidCodePoint = () => 'Not a valid code point'; +export const InvalidHint = (v: Value) => `Invalid hint: ${i(v)}`; +export const InvalidMethodName = (name: string) => `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: string, v: Value) => `${f} called on invalid receiver: ${i(v)}`; +export const InvalidRegExpFlags = (f: string) => `Invalid RegExp flags: ${f}`; +export const RegExpFlagsCannotUseTogether = (f1: string, f2: string) => `Cannot use RegExp flags ${f1} and ${f2} together`; +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 InvalidAlphabet = () => 'Invalid alphabet'; +export const InvalidDate = () => 'Invalid date'; +export const InvalidDuration = () => 'Invalid duration'; +export const InvalidLastChunkHandling = () => 'Invalid lastChunkHandling'; +export const InvalidBase64String = () => 'Invalid base64 string'; +export const InvalidHexString = () => 'Invalid hex string'; +export const IteratorCompleted = () => 'The iterator is already complete.'; +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: string | null) => `Unexpected character ${c} in JSON`; +export const JSONExpected = (e: string | readonly string[], a: string | null) => `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 MissingRequiredField = (f: string) => `Missing required field: ${f}`; +export const ModuleExportNameInvalidUnicode = () => 'Export name is not valid unicode'; +export const ModuleUndefinedExport = (n: string) => `Export '${i(n)}' is not defined in module`; +export const NegativeIndex = (n: string) => `${n} cannot be negative`; +export const NewlineAfterThrow = () => 'Illegal newline after throw'; +export const NoFieldsPresent = () => 'No fields present'; +export const NormalizeInvalidForm = () => 'Invalid normalization form'; +export const NotAConstructor = (v: Value | string) => `${i(v)} is not a constructor`; +export const NotAFunction = (v: Value | string) => `${i(v)} is not a function`; +export const NotATypeObject = (t: string, v: Value) => `${i(v)} is not a ${t} object`; +export const NotAnObject = (v: Value) => `${i(v)} is not an object`; +export const NotASymbol = (v: Value) => `${i(v)} is not a symbol`; +export const NotAWeakKey = (v: Value) => `${i(v)} is not an object or a symbol`; +export const NotAString = (v: Value) => `${i(v)} is not a string`; +export const NotANumber = (v: Value) => `${i(v)} is not a number`; +export const NotAnInteger = (v: Value) => `${i(v)} is not an integer`; +export const NotDefined = (n: PrivateName | Value | string) => `${i(n)} is not defined`; +export const NotEnoughArguments = (numArgs: number, minArgs: number) => `${minArgs} argument${minArgs !== 1 ? 's' : ''} required, but only ${numArgs} present`; +export const NotInitialized = (n: JSStringValue) => `${i(n)} cannot be used before initialization`; +export const NotIterable = (n: Value) => `${i(n)} is not iterable`; +export const NotPropertyName = (p: Value) => `${i(p)} is not a valid property name`; +export const NotUint8Array = () => 'Not a Uint8Array'; +export const NumberFormatRange = (m: string) => `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: Value | string | number) => `${i(n)} is out of range`; +export const PrivateNameNoGetter = (p: PrivateName) => `${i(p)} was defined without a getter`; +export const PrivateNameNoSetter = (p: PrivateName) => `${i(p)} was defined without a setter`; +export const PrivateNameIsMethod = (p: PrivateName) => `Private method ${i(p)} is not writable`; +export const PromiseAnyRejected = () => 'No promises passed to Promise.any were fulfilled'; +export const PromiseCapabilityFunctionAlreadySet = (f: 'resolve' | 'reject') => `Promise ${f} function already set`; +export const PromiseRejectFunction = (v: Value) => `Promise reject function ${i(v)} is not callable`; +export const PromiseResolveFunction = (v: Value) => `Promise resolve function ${i(v)} is not callable`; +export const ProxyRevoked = (n: string) => `Cannot perform '${n}' on a proxy that has been revoked`; +export const ProxyDefinePropertyNonConfigurable = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for defining non-configurable property ${i(p)} which is either non-existent or configurable in the proxy target`; +export const ProxyDefinePropertyNonConfigurableWritable = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy 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: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for adding property ${i(p)} to the non-extensible proxy target`; +export const ProxyDefinePropertyIncompatible = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for adding property ${i(p)} that is incompatible with the existing property in the proxy target`; +export const ProxyDeletePropertyNonConfigurable = (p: PropertyKeyValue) => `'deleteProperty' on proxy: trap returned truthy for property ${i(p)} which is non-configurable in the proxy target`; +export const ProxyDeletePropertyNonExtensible = (p: PropertyKeyValue) => `'deleteProperty' on proxy: trap returned truthy for property ${i(p)} but the proxy target is non-extensible`; +export const ProxyGetNonConfigurableData = (p: PropertyKeyValue) => `'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: PropertyKeyValue) => `'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: PropertyKeyValue) => `'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: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined for property ${i(p)}`; +export const ProxyGetOwnPropertyDescriptorUndefined = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned undefined for property ${i(p)} which is non-configurable in the proxy target`; +export const ProxyGetOwnPropertyDescriptorNonExtensible = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned undefined for property ${i(p)} which exists in the non-extensible target`; +export const ProxyGetOwnPropertyDescriptorNonConfigurable = (p: PropertyKeyValue) => `'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: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property ${i(p)} which is writable or configurable in the proxy target`; +export const ProxyHasNonConfigurable = (p: PropertyKeyValue) => `'has' on proxy: trap returned falsy for property ${i(p)} which exists in the proxy target as non-configurable`; +export const ProxyHasNonExtensible = (p: PropertyKeyValue) => `'has' on proxy: trap returned falsy for property ${i(p)} but the proxy target is not extensible`; +export const ProxyIsExtensibleInconsistent = (e: BooleanValue) => `'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is ${i(e)})`; +export const ProxyOwnKeysMissing = (p: string) => `'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: PropertyKeyValue) => `'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: PropertyKeyValue) => `'set' on proxy: trap returned truthy for property ${i(p)} which exists in the proxy target as a non-configurable and non-writable accessor property without a setter`; +export const PropertyIsRequired = (p: string) => `Property ${i(p)} is required`; +export const PropertyCanOnlyBe = (p: string, valid: string, given: string) => `Property ${p} can only be ${valid}, but was given ${given}`; +export const RegExpArgumentNotAllowed = (m: string) => `First argument to ${m} must not be a regular expression`; +export const RegExpExecNotObject = (o: Value) => `${i(o)} is not object or null`; +export const ResizableBufferInvalidMaxByteLength = () => 'Invalid maxByteLength for resizable ArrayBuffer'; +export const ResolutionNullOrAmbiguous = (r: string | null, n: Value, m: AbstractModuleRecord) => (r === null + ? `Could not resolve import ${i(n)} from ${m.HostDefined.specifier}` + : `Star export ${i(n)} from ${m.HostDefined.specifier} is ambiguous`); +export const SizeIsNaN = () => 'size property must not be undefined, as it will be NaN'; +export const SeparatorIsNotAllowed = () => 'Numeric separators are not allowed here'; +export const SizeMustBePositiveInteger = () => 'size property must be a positive integer'; +export const SpeciesNotConstructor = () => 'object.constructor[Symbol.species] is not a constructor'; +export const StrictModeDelete = (n: PropertyKeyValue) => `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: number) => `Count ${i(v)} is invalid`; +export const StringCodePointInvalid = (n: Value) => `Invalid code point ${i(n)}`; +export const StringPrototypeMethodGlobalRegExp = (m: string) => `The RegExp passed to String.prototype.${m} must have the global flag`; +export const SubclassLengthTooSmall = (v: ArrayBufferObject) => `Subclass constructor returned a smaller-than-requested object ${i(v)}`; +export const SubclassSameValue = (v: ArrayBufferObject) => `Subclass constructor returned the same object ${i(v)}`; +export const TargetMatchesHeldValue = (v: Value) => `heldValue ${i(v)} matches target`; +export const TemplateInOptionalChain = () => 'Templates are not allowed in optional chains'; +export const ThisNotAFunction = (v: Value) => `Expected 'this' value to be a function but got ${i(v)}`; +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: number, m: number) => `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 TypedArrayOutOfBounds = () => 'TypedArray index out of bounds'; +export const TypedArrayOffsetAlignment = (n: number, m: number) => `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: ObjectValue) => `Unable to seal object ${i(o)}`; +export const UnableToFreeze = (o: ObjectValue) => `Unable to freeze object ${i(o)}`; +export const UnableToPreventExtensions = (o: ObjectValue) => `Unable to prevent extensions on object ${i(o)}`; +export const UnknownPrivateName = (o: ObjectValue, p: PrivateName) => `${i(p)} does not exist on object ${i(o)}`; +export const UnsupportedImportAttribute = (key: JSStringValue) => `Unsupported import attribute ${i(key)}`; +export const UnsupportedModuleType = (type: string) => `Unsupported module type ${i(type)} (only "json" is supported)`; +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: Value) => `${i(v)} is not a valid weak collection entry object`; +export const YieldInFormalParameters = () => 'yield is not allowed in function parameters'; +export const YieldNotInGenerator = () => 'yield is only valid in generators'; diff --git a/src/modules.mts b/src/modules.mts new file mode 100644 index 0000000..aa6bd76 --- /dev/null +++ b/src/modules.mts @@ -0,0 +1,771 @@ +import { + Value, JSStringValue, ObjectValue, UndefinedValue, BooleanValue, + NullValue, +} from './value.mts'; +import { surroundingAgent, type GCMarker } from './host-defined/engine.mts'; +import { ExecutionContext } from './execution-context/ExecutionContext.mts'; +import { + VarScopedDeclarations, + LexicallyScopedDeclarations, + BoundNames, + IsConstantDeclaration, + type ImportEntry, + type ExportEntry, +} from './static-semantics/all.mts'; +import { InstantiateFunctionObject } from './runtime-semantics/all.mts'; +import { + Completion, + NormalCompletion, + AbruptCompletion, + EnsureCompletion, + Q, X, ThrowCompletion, + IfAbruptRejectPromise, +} from './completion.mts'; +import { JSStringSet, type Mutable } from './helpers.mts'; +import { + Evaluate, type Evaluator, type PlainEvaluator, type ValueEvaluator, +} from './evaluator.mts'; +import type { ParseNode } from './parser/ParseNode.mts'; +import { + Assert, + Call, + NewPromiseCapability, + GetImportedModule, + GetModuleNamespace, + InnerModuleEvaluation, + InnerModuleLinking, + InnerModuleLoading, + SameValue, + AsyncBlockStart, + PromiseCapabilityRecord, + GraphLoadingState, + Realm, +} from '#self'; +import { + type ImportAttributeRecord, + type ModuleRequestRecord, + type PlainCompletion, type PromiseObject, ModuleEnvironmentRecord, +} from '#self'; + +// https://tc39.es/ecma262/#loadedmodulerequest-record +export interface LoadedModuleRequestRecord { + readonly Specifier: JSStringValue; + readonly Attributes: ImportAttributeRecord[]; + readonly Module: AbstractModuleRecord +} + +// #resolvedbinding-record +export class ResolvedBindingRecord { + readonly Module: AbstractModuleRecord; + + readonly BindingName: 'namespace' | JSStringValue; + + constructor({ Module, BindingName }: Pick) { + Assert(Module instanceof AbstractModuleRecord); + Assert(BindingName === 'namespace' || BindingName instanceof JSStringValue); + this.Module = Module; + this.BindingName = BindingName; + } + + mark(m: GCMarker) { + m(this.Module); + } +} + +export type ModuleRecordHostDefinedPublic = unknown; +export type ModuleRecordHostDefined = { + public?: ModuleRecordHostDefinedPublic; + specifier?: string | undefined; + readonly SourceTextModuleRecord?: typeof SourceTextModuleRecord; + scriptId?: string; + readonly doNotTrackScriptId?: boolean; +}; +export type AbstractModuleInit = Pick; + +interface ResolveSetItem { + readonly Module: AbstractModuleRecord; + readonly ExportName: JSStringValue; +} + +/** https://tc39.es/ecma262/#sec-abstract-module-records */ +export abstract class AbstractModuleRecord { + abstract LoadRequestedModules(hostDefined?: ModuleRecordHostDefined): PromiseObject; + + abstract GetExportedNames(exportStarSet?: AbstractModuleRecord[]): readonly JSStringValue[]; + + abstract ResolveExport(exportName: JSStringValue, resolveSet?: ResolveSetItem[]): 'ambiguous' | ResolvedBindingRecord | null; + + abstract Link(): PlainCompletion; + + abstract Evaluate(): Evaluator; + + readonly Realm: Realm; + + readonly Environment: ModuleEnvironmentRecord | undefined; + + readonly Namespace: ObjectValue | undefined = undefined; + + readonly DeferredNamespace: ObjectValue | undefined = undefined; + + readonly HostDefined: ModuleRecordHostDefined; + + constructor(init: AbstractModuleInit) { + this.Realm = init.Realm; + this.Environment = init.Environment; + this.HostDefined = init.HostDefined; + } + + mark(m: GCMarker) { + m(this.Realm); + m(this.Environment); + m(this.Namespace); + m(this.DeferredNamespace); + } +} + +export { AbstractModuleRecord as ModuleRecord }; + +export type CyclicModuleRecordInit = AbstractModuleInit & Readonly>; +export type CyclicModuleRecordStatus = 'new' | 'unlinked' | 'linking' | 'linked' | 'evaluating' | 'evaluating-async' | 'evaluated'; +/** https://tc39.es/ecma262/#sec-cyclic-module-records */ +export abstract class CyclicModuleRecord extends AbstractModuleRecord { + Status: CyclicModuleRecordStatus; + + EvaluationError: ThrowCompletion | undefined; + + DFSAncestorIndex: number | undefined; + + readonly RequestedModules: readonly ModuleRequestRecord[]; + + readonly LoadedModules: LoadedModuleRequestRecord[]; + + readonly HasTLA: BooleanValue; + + AsyncEvaluationOrder: 'unset' | number | 'done'; + + AsyncParentModules: CyclicModuleRecord[]; + + CycleRoot: CyclicModuleRecord | undefined; + + TopLevelCapability: PromiseCapabilityRecord | undefined; + + PendingAsyncDependencies: number | undefined; + + constructor(init: CyclicModuleRecordInit) { + super(init); + this.Status = init.Status; + this.EvaluationError = init.EvaluationError; + this.DFSAncestorIndex = init.DFSAncestorIndex; + this.RequestedModules = init.RequestedModules; + this.LoadedModules = init.LoadedModules; + this.CycleRoot = init.CycleRoot; + this.HasTLA = init.HasTLA; + this.AsyncEvaluationOrder = init.AsyncEvaluationOrder; + this.TopLevelCapability = init.TopLevelCapability; + this.AsyncParentModules = init.AsyncParentModules; + this.PendingAsyncDependencies = init.PendingAsyncDependencies; + } + + abstract ExecuteModule(capability?: PromiseCapabilityRecord): ValueEvaluator; + + /** https://tc39.es/ecma262/#sec-LoadRequestedModules */ + LoadRequestedModules(hostDefined?: ModuleRecordHostDefined) { + const module = this; + + // 2. Let pc be ! NewPromiseCapability(%Promise%). + const pc = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 3. Let state be a new GraphLoadingState Record { [[IsLoading]]: true, [[PendingModulesCount]]: 1, [[Visited]]: « », [[PromiseCapability]]: pc, [[HostDefined]]: hostDefined }. + const state = new GraphLoadingState({ + PromiseCapability: pc, + HostDefined: hostDefined, + }); + // 4. Perform InnerModuleLoading(state, module). + InnerModuleLoading(state, module); + // 5. Return pc.[[Promise]]. + return pc.Promise; + } + + /** https://tc39.es/ecma262/#sec-moduledeclarationlinking */ + Link() { + const module = this; + // 1. Assert: module.[[Status]] is unlinked, linked, evaluating-async, or evaluated. + Assert(module.Status === 'unlinked' || module.Status === 'linked' || module.Status === 'evaluating-async' || module.Status === 'evaluated'); + // 2. Let stack be a new empty List. + const stack: CyclicModuleRecord[] = []; + // 3. Let result be Completion(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 of 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'; + } + // b. Assert: module.[[Status]] is unlinked. + Assert(module.Status === 'unlinked'); + // c. Return result. + return result; + } + // 6. Assert: module.[[Status]] is linked, evaluating-async, or evaluated. + Assert(module.Status === 'linked' || module.Status === 'evaluating-async' || module.Status === 'evaluated'); + // 7. Assert: stack is empty. + Assert(stack.length === 0); + // 8. Return unused. + return NormalCompletion(undefined); + } + + /** https://tc39.es/ecma262/#sec-moduleevaluation */ + * Evaluate(): Evaluator { + let module: CyclicModuleRecord = this; + + // 1. Assert: None of module or any of its recursive dependencies have [[Status]] set to evaluating, linking, unlinked, or new. + Assert((function getModules(module: AbstractModuleRecord, list: CyclicModuleRecord[]) { + if (!(module instanceof CyclicModuleRecord) || list.includes(module)) { + return list; + } + list.push(module); + for (const r of module.RequestedModules) { + getModules(GetImportedModule(module, r), list); + } + return list; + }(this, [])).every((m) => m.Status !== 'evaluating' && m.Status !== 'linking' && m.Status !== 'unlinked' && m.Status !== 'new')); + + // 3. Assert: module.[[Status]] is linked or evaluated. + Assert(module.Status === 'linked' || module.Status === 'evaluating-async' || module.Status === 'evaluated'); + // 3. If module.[[Status]] is evaluating-async or evaluated, then + if (module.Status === 'evaluating-async' || module.Status === 'evaluated') { + if (module.CycleRoot !== undefined) { + module = module.CycleRoot; + } else { + Assert(module.Status === 'evaluated' && module.EvaluationError !== undefined); + } + } + // 4. If module.[[TopLevelCapability]] is not ~empty~, then + if (module.TopLevelCapability !== undefined) { + // a. Return module.[[TopLevelCapability]].[[Promise]]. + return module.TopLevelCapability.Promise; + } + // 4. Let stack be a new empty List. + const stack: CyclicModuleRecord[] = []; + // (*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 = yield* 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. Assert: m.[[AsyncEvaluationOrder]] is unset. + Assert(m.AsyncEvaluationOrder === 'unset'); + // iii. Set m.[[Status]] to evaluated. + m.Status = 'evaluated'; + // iv. Set m.[[EvaluationError]] to result. + m.EvaluationError = result; + // v. Set _m_.[[CycleRoot]] to _m_. + m.CycleRoot = m; + } + // 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 evaluating-async or evaluated. + Assert(module.Status === 'evaluating-async' || module.Status === 'evaluated'); + // b. Assert: module.[[EvaluationError]] is ~empty~. + Assert(module.EvaluationError === undefined); + // c. If module.[[Status]] is evaluated, then + if (module.Status === 'evaluated') { + // i. Assert: module.[[AsyncEvaluationOrder]] is not an integer. + Assert(typeof module.AsyncEvaluationOrder !== 'number'); + // ii. Perform ! Call(capability.[[Resolve]], undefined, «undefined»). + X(Call(capability.Resolve, Value.undefined, [Value.undefined])); + } + // d. Assert: stack is empty. + Assert(stack.length === 0); + } + // 9. Return undefined. + // (*TopLevelAwait) 11. Return capability.[[Promise]]. + return capability.Promise; + } + + override mark(m: GCMarker) { + super.mark(m); + m(this.EvaluationError); + for (const v of this.LoadedModules) { + m(v.Module); + } + } +} + +export type SourceTextModuleRecordInit = CyclicModuleRecordInit & Pick; +/** https://tc39.es/ecma262/#sec-source-text-module-records */ +export class SourceTextModuleRecord extends CyclicModuleRecord { + ImportMeta: ObjectValue | undefined; + + readonly ECMAScriptCode: ParseNode.Module; + + readonly Context: ExecutionContext | undefined; + + readonly ImportEntries: readonly ImportEntry[]; + + readonly LocalExportEntries: readonly ExportEntry[]; + + readonly IndirectExportEntries: readonly ExportEntry[]; + + readonly StarExportEntries: readonly ExportEntry[]; + + constructor(init: SourceTextModuleRecordInit) { + 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; + } + + /** https://tc39.es/ecma262/#sec-getexportednames */ + GetExportedNames(exportStarSet: AbstractModuleRecord[]) { + const module = this; + // 1. Assert: module.[[Status]] is not new. + Assert(module.Status !== 'new'); + // 2. If exportStarSet is not present, set exportStarSet to a new empty List. + if (!exportStarSet) { + exportStarSet = []; + } + // 3. 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 []; + } + // 4. Append module to exportStarSet. + exportStarSet.push(module); + // 5. Let exportedNames be a new empty List. + const exportedNames: JSStringValue[] = []; + // 6. 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. Assert: e.[[ExportName]] is not null. + Assert(!(e.ExportName instanceof NullValue)); + // c. Append e.[[ExportName]] to exportedNames. + exportedNames.push(e.ExportName); + } + // 7. 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. Assert: e.[[ExportName]] is not null. + Assert(!(e.ExportName instanceof NullValue)); + // c. Append e.[[ExportName]] to exportedNames. + exportedNames.push(e.ExportName); + } + // 8. For each ExportEntry Record e in module.[[StarExportEntries]], do + for (const e of module.StarExportEntries) { + // a. Let requestedModule be GetImportedModule(module, e.[[ModuleRequest]]). + const requestedModule = GetImportedModule(module, e.ModuleRequest as ModuleRequestRecord); + // b. Let starNames be requestedModule.GetExportedNames(exportStarSet). + const starNames = 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, 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); + } + } + } + } + // 9. Return exportedNames. + return exportedNames; + } + + /** https://tc39.es/ecma262/#sec-resolveexport */ + ResolveExport(exportName: JSStringValue, resolveSet?: ResolveSetItem[]) { + const module = this; + // 1. Assert: module.[[Status]] is not new. + Assert(module.Status !== 'new'); + // 2. If resolveSet is not present, set resolveSet to a new empty List. + if (!resolveSet) { + resolveSet = []; + } + // 3. 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; + } + } + // 4. Append the Record { [[Module]]: module, [[ExportName]]: exportName } to resolveSet. + resolveSet.push({ Module: module, ExportName: exportName }); + // 5. 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 as JSStringValue, + }); + } + } + // 6. 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 GetImportedModule(module, e.[[ModuleRequest]]). + const importedModule = GetImportedModule(module, e.ModuleRequest as ModuleRequestRecord); + // ii. If e.[[ImportName]] is ~all~, then + if (e.ImportName === 'all') { + // 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 { // iv. Else, + // 1. Assert: module imports a specific binding for this export. + // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). + return importedModule.ResolveExport(e.ImportName as JSStringValue, resolveSet); + } + } + } + // 7. If SameValue(exportName, "default") is true, then + if (SameValue(exportName, 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. + } + // 8. Let starResolution be null. + let starResolution = null; + // 9. For each ExportEntry Record e in module.[[StarExportEntries]], do + for (const e of module.StarExportEntries) { + // a. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). + const importedModule = GetImportedModule(module, e.ModuleRequest as ModuleRequestRecord); + // b. Let resolution be importedModule.ResolveExport(exportName, resolveSet). + const resolution = 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 * export that includes the requested name. + // 2. If _resolution_.[[Module]] and _starResolution_.[[Module]] are not the same Module Record, return ~ambiguous~. + if (resolution.Module !== starResolution.Module) { + return 'ambiguous'; + } + // 3. If _resolution_.[[BindingName]] is not _starResolution_.[[BindingName]], return ~ambiguous~. + if (SameValue(resolution.BindingName as JSStringValue, starResolution.BindingName as JSStringValue) === Value.false) { + return 'ambiguous'; + } + } + } + } + // 11. Return starResolution. + return starResolution; + } + + /** https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment */ + InitializeEnvironment() { + const module = this as Mutable; + // 1. 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 = module.ResolveExport(e.ExportName as JSStringValue); + // 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); + } + // 2. Assert: All named exports from module are resolvable. + // 3. Let realm be module.[[Realm]]. + const realm = module.Realm; + // 4. Assert: realm is not undefined. + Assert(!(realm instanceof UndefinedValue)); + // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). + const env = new ModuleEnvironmentRecord(realm.GlobalEnv); + // 6. Set module.[[Environment]] to env. + module.Environment = env; + // 7. For each ImportEntry Record in in module.[[ImportEntries]], do + for (const ie of module.ImportEntries) { + // a. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]). + const importedModule = GetImportedModule(module, ie.ModuleRequest); + // b. If in.[[ImportName]] is ~namespace-object~, then + if (ie.ImportName === 'namespace-object') { + // i. Let namespace be GetModuleNamespace(importedModule). + const namespace = GetModuleNamespace(importedModule, ie.ModuleRequest.Phase); + // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + X(env.CreateImmutableBinding(ie.LocalName, Value.true)); + // iii. Call env.InitializeBinding(in.[[LocalName]], namespace). + X(env.InitializeBinding(ie.LocalName, namespace)); + } else { // c. Else, + // i. Let resolution be importedModule.ResolveExport(in.[[ImportName]]). + const resolution = 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 = GetModuleNamespace(resolution.Module, 'evaluation'); + // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + X(env.CreateImmutableBinding(ie.LocalName, Value.true)); + // 3. Call env.InitializeBinding(in.[[LocalName]], namespace). + X(env.InitializeBinding(ie.LocalName, namespace)); + } else { // iv. Else, + // 1. Call env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]). + X(env.CreateImportBinding(ie.LocalName, resolution.Module, resolution.BindingName)); + } + } + } + // 8. Let moduleContext be a new ECMAScript code execution context. + const moduleContext = new ExecutionContext(); + // 9. Set the Function of moduleContext to null. + moduleContext.Function = Value.null; + // 10. Assert: module.[[Realm]] is not undefined. + Assert(!(module.Realm instanceof UndefinedValue)); + // 11. Set the Realm of moduleContext to module.[[Realm]]. + moduleContext.Realm = module.Realm; + // 12. Set the ScriptOrModule of moduleContext to module. + moduleContext.ScriptOrModule = module; + // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + moduleContext.VariableEnvironment = module.Environment!; + // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + moduleContext.LexicalEnvironment = module.Environment!; + // 15. Set the PrivateEnvironment of moduleContext to null. + moduleContext.PrivateEnvironment = Value.null; + // 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 JSStringSet(); + // 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). + X(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); + // 24. 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). + X(env.CreateImmutableBinding(dn, Value.true)); + } else { // ii. Else, + // 1. Perform ! env.CreateMutableBinding(dn, false). + X(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, Value.null); + // 2. Call env.InitializeBinding(dn, fo). + X(env.InitializeBinding(dn, fo)); + } + } + } + // 25. Remove moduleContext from the execution context stack. + surroundingAgent.executionContextStack.pop(moduleContext); + // 26. Return unused. + return NormalCompletion(undefined); + } + + /** https://tc39.es/ecma262/#sec-source-text-module-record-execute-module */ + * ExecuteModule(capability?: PromiseCapabilityRecord): ValueEvaluator { + // 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.HasTLA === 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(yield* (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 Q(result); + } else { // (*TopLevelAwait) + // a. Assert: capability is a PromiseCapability Record. + Assert(capability instanceof PromiseCapabilityRecord); + // b. Perform ! AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleCxt). + X(yield* AsyncBlockStart(capability, module.ECMAScriptCode, moduleContext)); + // c. Return. + return Value.undefined; + } + } + + override mark(m: GCMarker) { + super.mark(m); + m(this.ImportMeta); + m(this.Context); + } +} + +export type SyntheticModuleRecordInit = AbstractModuleInit & Pick; +/** https://tc39.es/ecma262/#sec-synthetic-module-records */ +export class SyntheticModuleRecord extends AbstractModuleRecord { + override LoadRequestedModules(): PromiseObject { + const promise = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + X(Call(promise.Resolve, Value.undefined, [Value.undefined])); + return promise.Promise; + } + + readonly ExportNames: readonly JSStringValue[]; + + readonly EvaluationSteps: (module: SyntheticModuleRecord) => PlainEvaluator | Completion | void; + + constructor(init: SyntheticModuleRecordInit) { + super(init); + + this.ExportNames = init.ExportNames; + this.EvaluationSteps = init.EvaluationSteps; + } + + /** https://tc39.es/ecma262/#sec-synthetic-module-record-getexportednames */ + GetExportedNames() { + const module = this; + // 1. Return module.[[ExportNames]]. + return module.ExportNames; + } + + /** https://tc39.es/ecma262/#sec-synthetic-module-record-resolveexport */ + ResolveExport(exportName: JSStringValue): ResolvedBindingRecord | null { + const module = this; + // 1. If module.[[ExportNames]] does not contain exportName, return null. + // 2. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: exportName }. + for (const e of module.ExportNames) { + if (SameValue(e, exportName) === Value.true) { + return new ResolvedBindingRecord({ Module: module, BindingName: exportName }); + } + } + return null; + } + + /** https://tc39.es/ecma262/#sec-synthetic-module-record-link */ + Link() { + const module = this; + // 1. Let realm be module.[[Realm]]. + const realm = module.Realm; + // 2. Assert: realm is not undefined. + Assert(!(realm instanceof UndefinedValue)); + // 3. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). + const env = new ModuleEnvironmentRecord(realm.GlobalEnv); + // 4. Set module.[[Environment]] to env. + (module as Mutable).Environment = env; + // 5. For each exportName in module.[[ExportNames]], + for (const exportName of module.ExportNames) { + // a. Perform ! env.CreateMutableBinding(exportName, false). + X(env.CreateMutableBinding(exportName, Value.false)); + // b. Perform ! env.InitializeBinding(exportName, undefined). + X(env.InitializeBinding(exportName, Value.undefined)); + } + // 8. Return undefined. + return undefined; + } + + /** https://tc39.es/ecma262/#sec-synthetic-module-record-evaluate */ + * Evaluate(): Evaluator { + const module = this; + // 1. Suspend the currently running execution context. + // 2. Let moduleContext be a new ECMAScript code execution context. + const moduleContext = new ExecutionContext(); + // 3. Set the Function of moduleContext to null. + moduleContext.Function = Value.null; + // 4. Set the Realm of moduleContext to module.[[Realm]]. + moduleContext.Realm = module.Realm; + // 5. Set the ScriptOrModule of moduleContext to module. + moduleContext.ScriptOrModule = module; + // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + moduleContext.VariableEnvironment = module.Environment!; + // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + moduleContext.LexicalEnvironment = module.Environment!; + moduleContext.PrivateEnvironment = Value.null; + // 8. Push moduleContext on to the execution context stack; moduleContext is now the running execution context. + surroundingAgent.executionContextStack.push(moduleContext); + // 9. Let steps be module.[[EvaluationSteps]]. + const steps = module.EvaluationSteps; + // 10. Let result be Completion(steps(module)). + let result = steps(module); + if (result && 'next' in result) { + result = yield* result; + } + // 11. Suspend moduleContext and remove it from the execution context stack. + // 12. Resume the context that is now on the top of the execution context stack as the running execution context. + surroundingAgent.executionContextStack.pop(moduleContext); + // 13. Let pc be ! NewPromiseCapability(%Promise%). + const pc = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 14. IfAbruptRejectPromise(result, pc). + IfAbruptRejectPromise(result, pc); + // 15. Perform ! Call(pc.[[Resolve]], undefined, « undefined »). + X(Call(pc.Resolve, Value.undefined, [Value.undefined])); + // 16. Return pc.[[Promise]]. + return pc.Promise; + } + + * SetSyntheticExport(name: JSStringValue, value: Value): PlainEvaluator { + const module = this; + // 1. Return module.[[Environment]].SetMutableBinding(name, value, true). + return yield* (module.Environment as ModuleEnvironmentRecord).SetMutableBinding(name, value, Value.true); + } +} diff --git a/src/parse.mts b/src/parse.mts new file mode 100644 index 0000000..a32945a --- /dev/null +++ b/src/parse.mts @@ -0,0 +1,266 @@ +import { Parser, type ParserOptions } from './parser/Parser.mts'; +import { RegExpParser, type RegExpParserContext } from './parser/RegExpParser.mts'; +import { surroundingAgent, type GCMarker } from './host-defined/engine.mts'; +import { + SourceTextModuleRecord, SyntheticModuleRecord, type LoadedModuleRequestRecord, type ModuleRecordHostDefined, +} from './modules.mts'; +import { JSStringValue, ObjectValue, Value } from './value.mts'; +import { Q, X, type PlainCompletion } from './completion.mts'; +import { + ModuleRequests, + ImportEntries, + ExportEntries, + ImportedLocalNames, +} from './static-semantics/all.mts'; +import { + JSStringSet, kInternal, skipDebugger, type Mutable, +} from './helpers.mts'; +import type { ParseNode } from './parser/ParseNode.mts'; +import { ParseJSON } from './intrinsics/JSON.mts'; +import { avoid_using_children } from './parser/utils.mts'; +import { + Get, + Set, + CreateDefaultExportSyntheticModule, + ToString, + Throw, +} from '#self'; +import type { Realm } from '#self'; + +export { Parser, RegExpParser }; + +function handleError(e: unknown) { + if (e instanceof SyntaxError) { + const v = surroundingAgent.Throw('SyntaxError', 'Raw', e.message).Value as ObjectValue; + if (e.decoration) { + const stackString = Value('stack'); + const stack = X(Get(v, stackString)); + // Note: in many cases the output will be padded by space or text like "Uncaught", + // insert a new line allow decoration lines get the same padding. + const newStackString = `\n${e.decoration}\n${stack instanceof JSStringValue ? stack.stringValue() : ''}`; + X(Set(v, stackString, Value(newStackString), Value.true)); + } + return v; + } else { + throw e; + } +} + +export function wrappedParse(init: ParserOptions, f: (parser: Parser) => T) { + const p = new Parser(init); + + try { + const r = f(p); + const errors = []; + for (const error of p.earlyErrors) { + errors.push(handleError(error)); + } + for (const error of p.earlyErrors2) { + errors.push(error); + } + if (errors.length > 0) { + return errors; + } + return r; + } catch (e) { + return [handleError(e)]; + } +} + +export class ScriptRecord { + readonly Realm: Realm; + + readonly ECMAScriptCode: ParseNode.Script; + + readonly LoadedModules: LoadedModuleRequestRecord[]; + + readonly HostDefined: ParseScriptHostDefined; + + mark(m: GCMarker) { + m(this.Realm); + } + + constructor(record: Omit) { + this.ECMAScriptCode = record.ECMAScriptCode; + this.Realm = record.Realm; + this.LoadedModules = record.LoadedModules; + this.HostDefined = record.HostDefined; + } +} +export interface ParseScriptHostDefined { + readonly specifier?: string | undefined; + readonly [kInternal]?: { + json?: boolean; + /** only used in inspector.compileScript */ allowAllPrivateNames?: boolean; + }; + scriptId?: string; + readonly doNotTrackScriptId?: boolean; +} +export function ParseScript(sourceText: string, realm: Realm, hostDefined: ParseScriptHostDefined = {}): ScriptRecord | ObjectValue[] { + // 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, + json: hostDefined[kInternal]?.json, + allowAllPrivateNames: hostDefined[kInternal]?.allowAllPrivateNames, + }, (p) => p.parseScript()); + // 3. If body is a List of errors, return body. + if (Array.isArray(body)) { + const scriptId = hostDefined.doNotTrackScriptId ? undefined : surroundingAgent.addDynamicParsedSource(realm, sourceText); + body.forEach((error) => Parser.decorateSyntaxErrorWithScriptId(error, scriptId)); + return body; + } + setNodeParent(body, undefined); + // 4. Return Script Record { [[Realm]]: realm, [[ECMAScriptCode]]: body, [[HostDefined]]: hostDefined }. + const script = new ScriptRecord({ + Realm: realm, + ECMAScriptCode: body, + LoadedModules: [], + HostDefined: hostDefined, + }); + if (!hostDefined.doNotTrackScriptId) { + surroundingAgent.addParsedSource(script); + } + return script; +} + +export function ParseModule(sourceText: string, realm: Realm, hostDefined: ModuleRecordHostDefined = {}) { + // 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)) { + const scriptId = hostDefined.doNotTrackScriptId ? undefined : surroundingAgent.addDynamicParsedSource(realm, sourceText); + body.forEach((error) => Parser.decorateSyntaxErrorWithScriptId(error, scriptId)); + return body; + } + setNodeParent(body, undefined); + // 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 JSStringSet(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 as JSStringValue).stringValue()); + // 2. If ie.[[ImportName]] is ~namespace-object~, then + if (ie!.ImportName === 'namespace-object') { + // 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 === 'all-but-default' && ee.ExportName === Value.null) { // b. Else if ee.[[ImportName]] is ~all-but-default~ 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, [[DFSAncestorIndex]]: undefined }. + const module = new (hostDefined.SourceTextModuleRecord || SourceTextModuleRecord)({ + Realm: realm, + Environment: undefined, + Namespace: undefined, + Status: 'new', + EvaluationError: undefined, + HostDefined: hostDefined, + ECMAScriptCode: body, + Context: undefined, + ImportMeta: undefined, + RequestedModules: requestedModules, + LoadedModules: [], + ImportEntries: importEntries, + LocalExportEntries: localExportEntries, + IndirectExportEntries: indirectExportEntries, + StarExportEntries: starExportEntries, + CycleRoot: undefined, + HasTLA: body.hasTopLevelAwait ? Value.true : Value.false, + AsyncEvaluationOrder: 'unset', + TopLevelCapability: undefined, + AsyncParentModules: [], + DFSAncestorIndex: undefined, + PendingAsyncDependencies: undefined, + }); + if (!hostDefined.doNotTrackScriptId) { + surroundingAgent.addParsedSource(module); + } + return module; +} + +/** https://tc39.es/ecma262/#sec-parsejsonmodule */ +export function ParseJSONModule(sourceText: Value, realm: Realm, hostDefined: ModuleRecordHostDefined): PlainCompletion { + const string = Q(skipDebugger(ToString(sourceText))); + const result = Q(ParseJSON(string.stringValue())); + return CreateDefaultExportSyntheticModule(result.Value, realm, hostDefined); +} + +function setNodeParent(node: ParseNode, parent: ParseNode | undefined) { + (node as Mutable).parent = parent; + for (const child of avoid_using_children(node)) { + if (!child.parent) { + setNodeParent(child, node); + } + } +} + +/** https://tc39.es/ecma262/#sec-parsepattern */ +export function ParsePattern(patternText: string, u: boolean, v: boolean) { + const parse = (flags: RegExpParserContext) => { + try { + const p = new RegExpParser(patternText); + return p.scope(flags, () => p.parsePattern()); + } catch (e) { + return [handleError(e)]; + } + }; + if (v && u) { + return [Throw.SyntaxError('RegExp flags "v" and "u" cannot be used together').Value]; + } else if (v) { + return parse({ UnicodeMode: true, UnicodeSetsMode: true, NamedCaptureGroups: true }); + } else if (u) { + return parse({ UnicodeMode: true, NamedCaptureGroups: true }); + } else { + return parse({ NamedCaptureGroups: true }); + } +} diff --git a/src/parser/BaseParser.mts b/src/parser/BaseParser.mts new file mode 100644 index 0000000..78be93a --- /dev/null +++ b/src/parser/BaseParser.mts @@ -0,0 +1,36 @@ +import { Lexer } from './Lexer.mts'; +import type { ParseNode, ParseNodesByType } from './ParseNode.mts'; +import type { Scope } from './Scope.mts'; + +export abstract class BaseParser extends Lexer { + protected abstract scope: Scope; + + abstract startNode(inheritStart?: ParseNode): ParseNode.Unfinished; + + abstract finishNode(node: T, type: K): ParseNodesByType[K]; + + /** + * Repurpose a {@link ParseNode} of one type as a {@link ParseNode} of another type. + * @param node The node to repurpose. + * @param type The name of the new node type. + * @param update an optional callback that can be used to mutate {@link node} to match the new node type. + */ + protected repurpose( + node: T, + type: K, + update?: ( + /** The same value as {@link node}, but cast to an unfinished node of the provided type */ + asNewNode: ParseNode.Unfinished, + /** The same value as {@link node} */ + asOldNode: T, + /** The same value as {@link node}, but cast to a partial, mutable type so that excess properties can be removed. */ + asPartialNode: { -readonly [P in keyof T]?: T[P] }, + ) => void, + ): ParseNodesByType[K] { + // NOTE: must down-cast to `ParseNode` before up-casting to `Unfinished` due to the incompatbile `type` discriminant. + const unfinished = node as ParseNode.Unfinished; + unfinished.type = type; + update?.(unfinished, node, node); + return unfinished as ParseNode as ParseNodesByType[K]; + } +} diff --git a/src/parser/ExpressionParser.mts b/src/parser/ExpressionParser.mts new file mode 100644 index 0000000..5dfafaa --- /dev/null +++ b/src/parser/ExpressionParser.mts @@ -0,0 +1,1607 @@ +import { + TV, + PropName, + StringValue, + IsComputedPropertyKey, + ContainsArguments, +} from '../static-semantics/all.mts'; +import type { Mutable } from '../helpers.mts'; +import { surroundingAgent, type Feature } from '../host-defined/engine.mts'; +import { + Token, TokenPrecedence, + isPropertyOrCall, + isMember, + isKeywordRaw, + isAutomaticSemicolon, +} from './tokens.mts'; +import { isLineTerminator, type TokenData } from './Lexer.mts'; +import { FunctionParser, FunctionKind } from './FunctionParser.mts'; +import { RegExpParser, type RegExpParserContext } from './RegExpParser.mts'; +import type { ParseNode } from './ParseNode.mts'; + +export abstract class ExpressionParser extends FunctionParser { + protected abstract readonly state: { + hasTopLevelAwait: boolean; + strict: boolean; + json: boolean; + }; + + abstract parseBindingPattern(): ParseNode.BindingPattern; + + abstract markNodeStart(node: ParseNode.BaseParseNode | ParseNode.Unfinished): void; + + abstract parseInitializerOpt(): ParseNode.Initializer | null; + + abstract semicolon(): void; + + abstract feature(name: Feature): boolean; + + // Expression : + // AssignmentExpression + // Expression `,` AssignmentExpression + parseExpression(): ParseNode.Expression { + const AssignmentExpression = this.parseAssignmentExpression(); + if (this.eat(Token.COMMA)) { + const CommaOperator = this.startNode(AssignmentExpression); + const ExpressionList = [AssignmentExpression]; + do { + ExpressionList.push(this.parseAssignmentExpression()); + } while (this.eat(Token.COMMA)); + CommaOperator.ExpressionList = ExpressionList; + return this.finishNode(CommaOperator, 'CommaOperator'); + } + return AssignmentExpression; + } + + // AssignmentExpression : + // ConditionalExpression + // [+Yield] YieldExpression + // ArrowFunction + // AsyncArrowFunction + // LeftHandSideExpression `=` AssignmentExpression + // LeftHandSideExpression AssignmentOperator AssignmentExpression + // LeftHandSideExpression LogicalAssignmentOperator AssignmentExpression + // + // AssignmentOperator : one of + // *= /= %= += -= <<= >>= >>>= &= ^= |= **= + // + // LogicalAssignmentOperator : one of + // &&= ||= ??= + parseAssignmentExpression(): ParseNode.AssignmentExpressionOrHigher { + if (this.test(Token.YIELD) && this.scope.hasYield()) { + return this.parseYieldExpression(); + } + + 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' + && !left.escaped + && this.test(Token.IDENTIFIER) + && !this.peek().hadLineTerminatorBefore + && this.testAhead(Token.ARROW) + && !this.peekAhead().hadLineTerminatorBefore) { + assignmentInfo.clear(); + const node = this.startNode(left); + return this.parseArrowFunction(node, { + Arguments: [this.parseIdentifierReference()], + }, FunctionKind.ASYNC); + } + // IdentifierReference [no LineTerminator here] `=>` + if (this.test(Token.ARROW) && !this.peek().hadLineTerminatorBefore) { + assignmentInfo.clear(); + const node = this.startNode(left); + 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.hasTrailingComma || (last && last.type !== 'AssignmentRestElement')) { + assignmentInfo.clear(); + const node = this.startNode(left); + return this.parseArrowFunction(node, left, FunctionKind.ASYNC); + } + } + + if (left.type === 'CoverParenthesizedExpressionAndArrowParameterList') { + assignmentInfo.clear(); + const node = this.startNode(left); + 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(); + const node = this.startNode(left); + this.validateAssignmentTarget(left); + node.LeftHandSideExpression = left; + // NOTE: This cast isn't strictly sound as it depends on an expectation that `this.next.value` is correlated + // to `this.peek().type` which cannot be verified by the type system. + node.AssignmentOperator = this.next().value as ParseNode.AssignmentExpression['AssignmentOperator']; + node.AssignmentExpression = this.parseAssignmentExpression(); + return this.finishNode(node, 'AssignmentExpression'); + } + default: + return left; + } + } + + validateAssignmentTarget(node: ParseNode) { + 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': + if (node.Expression.type === 'ObjectLiteral' || node.Expression.type === 'ArrayLiteral') { + break; + } + 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); + } + if (p.type === 'AssignmentExpression') { + this.validateAssignmentTarget(p.LeftHandSideExpression); + } else { + 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': + if (node.AssignmentExpression.type === 'AssignmentExpression') { + this.validateAssignmentTarget(node.AssignmentExpression.LeftHandSideExpression); + } else { + this.validateAssignmentTarget(node.AssignmentExpression); + } + 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(): ParseNode.YieldExpression { + 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(); + } + } + } + this.scope.arrowInfo?.yieldExpressions.push(node as ParseNode.YieldExpression); + return this.finishNode(node, 'YieldExpression'); + } + + // ConditionalExpression : + // ShortCircuitExpression + // ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression + parseConditionalExpression(): ParseNode.ConditionalExpressionOrHigher { + const ShortCircuitExpression = this.parseShortCircuitExpression(); + if (this.eat(Token.CONDITIONAL)) { + const node = this.startNode(ShortCircuitExpression); + 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(): ParseNode.ShortCircuitExpressionOrHigher { + // Start parse at BIT_OR, right above AND/OR/NULLISH + const expression = this.parseBinaryExpression(TokenPrecedence[Token.BIT_OR]) as ParseNode.BitwiseORExpressionOrHigher; + 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) as ParseNode.LogicalORExpressionOrHigher; + case Token.NULLISH: { + let x: ParseNode.CoalesceExpressionHead = expression; + while (this.eat(Token.NULLISH)) { + const node = this.startNode(); + node.CoalesceExpressionHead = x; + node.BitwiseORExpression = this.parseBinaryExpression(TokenPrecedence[Token.BIT_OR]) as ParseNode.BitwiseORExpressionOrHigher; + x = this.finishNode(node, 'CoalesceExpression'); + } + return x; + } + default: + return expression; + } + } + + parseBinaryExpression(precedence: number, x?: ParseNode.BinaryExpressionOrHigher | ParseNode.PrivateIdentifier): ParseNode.BinaryExpressionOrHigher | ParseNode.PrivateIdentifier { + if (!x) { + if (this.test(Token.PRIVATE_IDENTIFIER)) { + x = this.parsePrivateIdentifier(); + const p = TokenPrecedence[this.peek().type]; + if (!this.test(Token.IN) || p < precedence) { + this.raise('UnexpectedToken'); + } + this.scope.checkUndefinedPrivate(x); + return this.parseBinaryExpression(p, x); + } else { + x = this.parseUnaryExpression(); + } + } + + // NOTE: While the algorithm may be efficient, many casts below are inherently unsound as they depend on assumptions + // that cannot be proven in the type system without runtime assertions. + let p = TokenPrecedence[this.peek().type]; + if (p >= precedence) { + do { + while (TokenPrecedence[this.peek().type] === p) { + const left = x; + if (p === TokenPrecedence[Token.EXP] && (left.type === 'UnaryExpression' || left.type === 'AwaitExpression')) { + return left; + } + let node: ParseNode.Unfinished; + if (this.peek().type === Token.IN && !this.scope.hasIn()) { + return left; + } + const op = this.next(); + const right = this.parseBinaryExpression(op.type === Token.EXP ? p : p + 1); + let name: 'ExponentiationExpression' + | 'MultiplicativeExpression' + | 'AdditiveExpression' + | 'ShiftExpression' + | 'RelationalExpression' + | 'EqualityExpression' + | 'BitwiseANDExpression' + | 'BitwiseXORExpression' + | 'BitwiseORExpression' + | 'LogicalANDExpression' + | 'LogicalORExpression'; + switch (op.type) { + case Token.EXP: + name = 'ExponentiationExpression'; + node = this.startNode(left); + node.UpdateExpression = left as ParseNode.UpdateExpressionOrHigher; // NOTE: unsound cast + node.ExponentiationExpression = right as ParseNode.ExponentiationExpressionOrHigher; // NOTE: unsound cast + break; + case Token.MUL: + case Token.DIV: + case Token.MOD: + name = 'MultiplicativeExpression'; + node = this.startNode(left); + node.MultiplicativeExpression = left as ParseNode.MultiplicativeExpressionOrHigher; // NOTE: unsound cast + node.MultiplicativeOperator = op.value as ParseNode.MultiplicativeOperator; // NOTE: unsound cast + node.ExponentiationExpression = right as ParseNode.ExponentiationExpressionOrHigher; // NOTE: unsound cast + break; + case Token.ADD: + case Token.SUB: + name = 'AdditiveExpression'; + node = this.startNode(left); + node.AdditiveExpression = left as ParseNode.AdditiveExpressionOrHigher; // NOTE: unsound cast + node.MultiplicativeExpression = right as ParseNode.MultiplicativeExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.AdditiveExpression['operator']; // NOTE: unsound cast + break; + case Token.SHL: + case Token.SAR: + case Token.SHR: + name = 'ShiftExpression'; + node = this.startNode(left); + node.ShiftExpression = left as ParseNode.ShiftExpressionOrHigher; // NOTE: unsound cast + node.AdditiveExpression = right as ParseNode.AdditiveExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.ShiftExpression['operator']; // NOTE: unsound cast + break; + case Token.LT: + case Token.GT: + case Token.LTE: + case Token.GTE: + case Token.INSTANCEOF: + case Token.IN: + name = 'RelationalExpression'; + node = this.startNode(left); + if (left.type === 'PrivateIdentifier') { + node.PrivateIdentifier = left; + } else { + node.RelationalExpression = left as ParseNode.RelationalExpressionOrHigher; // NOTE: unsound cast + } + node.ShiftExpression = right as ParseNode.ShiftExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.RelationalExpression['operator']; // NOTE: unsound cast + break; + case Token.EQ: + case Token.NE: + case Token.EQ_STRICT: + case Token.NE_STRICT: + name = 'EqualityExpression'; + node = this.startNode(left); + node.EqualityExpression = left as ParseNode.EqualityExpressionOrHigher; // NOTE: unsound cast + node.RelationalExpression = right as ParseNode.RelationalExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.EqualityExpression['operator']; // NOTE: unsound cast + break; + case Token.BIT_AND: + name = 'BitwiseANDExpression'; + node = this.startNode(left); + node.A = left as ParseNode.BitwiseANDExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.BitwiseANDExpression['operator']; // NOTE: unsound cast + node.B = right as ParseNode.EqualityExpressionOrHigher; // NOTE: unsound cast + break; + case Token.BIT_XOR: + name = 'BitwiseXORExpression'; + node = this.startNode(left); + node.A = left as ParseNode.BitwiseXORExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.BitwiseXORExpression['operator']; // NOTE: unsound cast + node.B = right as ParseNode.BitwiseANDExpressionOrHigher; // NOTE: unsound cast + break; + case Token.BIT_OR: + name = 'BitwiseORExpression'; + node = this.startNode(left); + node.A = left as ParseNode.BitwiseORExpressionOrHigher; // NOTE: unsound cast + node.operator = op.value as ParseNode.BitwiseORExpression['operator']; // NOTE: unsound cast + node.B = right as ParseNode.BitwiseXORExpressionOrHigher; // NOTE: unsound cast + break; + case Token.AND: + name = 'LogicalANDExpression'; + node = this.startNode(left); + node.LogicalANDExpression = left as ParseNode.LogicalANDExpressionOrHigher; // NOTE: unsound cast + node.BitwiseORExpression = right as ParseNode.BitwiseORExpressionOrHigher; // NOTE: unsound cast + break; + case Token.OR: + name = 'LogicalORExpression'; + node = this.startNode(left); + node.LogicalORExpression = left as ParseNode.LogicalORExpressionOrHigher; // NOTE: unsound cast + node.LogicalANDExpression = right as ParseNode.LogicalANDExpressionOrHigher; // NOTE: unsound cast + 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(): ParseNode.UnaryExpressionOrHigher { + return this.scope.with({ in: true }, () => { + if (this.test(Token.AWAIT) && this.scope.hasAwait()) { + return this.parseAwaitExpression(); + } + 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: { + const node = this.startNode(); + node.operator = this.next().value as ParseNode.UnaryExpression['operator']; // NOTE: unsound cast + node.UnaryExpression = this.parseUnaryExpression(); + if (node.operator === 'delete') { + let target: ParseNode.Expression = node.UnaryExpression; + while (target.type === 'ParenthesizedExpression') { + target = target.Expression; + } + if (this.isStrictMode() && target.type === 'IdentifierReference') { + this.raiseEarly('DeleteIdentifier', target); + } + if (target.type === 'MemberExpression' && target.PrivateIdentifier) { + this.raiseEarly('DeletePrivateName', target); + } + } + return this.finishNode(node, 'UnaryExpression'); + } + default: + return this.parseUpdateExpression(); + } + }); + } + + // AwaitExpression : `await` UnaryExpression + parseAwaitExpression(): ParseNode.AwaitExpression { + if (this.scope.inParameters()) { + this.raiseEarly('AwaitInFormalParameters'); + } else if (this.scope.inClassStaticBlock()) { + this.raiseEarly('AwaitInClassStaticBlock'); + } + const node = this.startNode(); + this.expect(Token.AWAIT); + node.UnaryExpression = this.parseUnaryExpression(); + this.scope.arrowInfo?.awaitExpressions.push(node as ParseNode.AwaitExpression); + 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(): ParseNode.UpdateExpressionOrHigher { + if (this.test(Token.INC) || this.test(Token.DEC)) { + const node = this.startNode(); + node.operator = this.next().value as ParseNode.UpdateExpression['operator']; // NOTE: unsound cast + 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(argument); + node.operator = this.next().value as ParseNode.UpdateExpression['operator']; // NOTE: unsound cast + node.LeftHandSideExpression = argument; + node.UnaryExpression = null; + return this.finishNode(node, 'UpdateExpression'); + } + } + return argument; + } + + // LeftHandSideExpression + parseLeftHandSideExpression(allowCalls = true): ParseNode.LeftHandSideExpression { + let result: ParseNode.LeftHandSideExpression; + 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.eat(Token.PERIOD)) { + if (this.scope.hasImportMeta() && this.eat('meta')) { + result = this.finishNode(node, 'ImportMeta'); + break; + } + if (this.eat('defer')) { + node.Phase = 'defer'; + } else { + this.unexpected(); + } + } else { + node.Phase = 'evaluation'; + } + if (!allowCalls) { + this.unexpected(); + } + this.expect(Token.LPAREN); + node.AssignmentExpression = this.parseAssignmentExpression(); + if (this.eat(Token.COMMA) && !this.test(Token.RPAREN)) { + node.OptionsExpression = this.parseAssignmentExpression(); + this.eat(Token.COMMA); + } + 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)) { + let finished: ParseNode.LeftHandSideExpression; + switch (this.peek().type) { + case Token.LBRACK: { + const node = this.startNode(result); + this.next(); + node.MemberExpression = result; + node.IdentifierName = null; + node.Expression = this.parseExpression(); + this.expect(Token.RBRACK); + finished = this.finishNode(node, 'MemberExpression'); + break; + } + case Token.PERIOD: { + const node = this.startNode(result); + this.next(); + node.MemberExpression = result; + if (this.test(Token.PRIVATE_IDENTIFIER)) { + node.PrivateIdentifier = this.parsePrivateIdentifier(); + this.scope.checkUndefinedPrivate(node.PrivateIdentifier); + node.IdentifierName = null; + } else { + node.IdentifierName = this.parseIdentifierName(); + node.PrivateIdentifier = null; + } + node.Expression = null; + finished = this.finishNode(node, 'MemberExpression'); + break; + } + case Token.LPAREN: { + const node = this.startNode(result); + // `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.hasTrailingComma = trailingComma; + } + finished = this.finishNode(node, 'CallExpression'); + break; + } + case Token.OPTIONAL: { + const node = this.startNode(result); + node.MemberExpression = result; + node.OptionalChain = this.parseOptionalChain(); + finished = this.finishNode(node, 'OptionalExpression'); + break; + } + case Token.TEMPLATE: { + const node = this.startNode(result); + node.MemberExpression = result; + node.TemplateLiteral = this.parseTemplateLiteral(true); + finished = this.finishNode(node, 'TaggedTemplateExpression'); + break; + } + default: + this.unexpected(); + } + // NOTE: unwinds ParseNode.Finish type alias to avoid circularity issues in type checker + result = finished as ParseNode.CallExpressionOrHigher | ParseNode.MemberExpressionOrHigher; + } + return result; + } + + // OptionalChain + parseOptionalChain(): ParseNode.OptionalChain { + this.expect(Token.OPTIONAL); + const 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 if (this.test(Token.PRIVATE_IDENTIFIER)) { + base.PrivateIdentifier = this.parsePrivateIdentifier(); + this.scope.checkUndefinedPrivate(base.PrivateIdentifier); + } else { + base.IdentifierName = this.parseIdentifierName(); + } + + let chain = this.finishNode(base, 'OptionalChain'); + while (true) { + const node = this.startNode(); + if (this.test(Token.LPAREN)) { + node.OptionalChain = chain; + node.Arguments = this.parseArguments().Arguments; + chain = this.finishNode(node, 'OptionalChain'); + } else if (this.eat(Token.LBRACK)) { + node.OptionalChain = chain; + node.Expression = this.parseExpression(); + this.expect(Token.RBRACK); + chain = this.finishNode(node, 'OptionalChain'); + } else if (this.test(Token.TEMPLATE)) { + this.raise('TemplateInOptionalChain'); + } else if (this.eat(Token.PERIOD)) { + node.OptionalChain = chain; + if (this.test(Token.PRIVATE_IDENTIFIER)) { + node.PrivateIdentifier = this.parsePrivateIdentifier(); + this.scope.checkUndefinedPrivate(node.PrivateIdentifier); + } else { + node.IdentifierName = this.parseIdentifierName(); + } + chain = this.finishNode(node, 'OptionalChain'); + } else { + return chain; + } + } + } + + // NewExpression + parseNewExpression(): ParseNode.NewExpressionOrHigher { + const node = this.startNode(); + this.expect(Token.NEW); + if (this.scope.hasNewTarget() && this.eat(Token.PERIOD)) { + this.expect('target'); + return this.finishNode(node as ParseNode.NewTarget, 'NewTarget'); + } + node.MemberExpression = this.parseLeftHandSideExpression(false); + if (this.test(Token.LPAREN)) { + node.Arguments = this.parseArguments().Arguments; + } else { + node.Arguments = null; + } + return this.finishNode(node as ParseNode.NewExpression, 'NewExpression'); + } + + // PrimaryExpression : + // ... + parsePrimaryExpression(): ParseNode.PrimaryExpression { + 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.AT: + return surroundingAgent.feature('decorators') ? this.parseClassExpression() : this.unexpected(); + 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(): ParseNode.NumericLiteral { + const node = this.startNode(); + if (!this.test(Token.NUMBER) && !this.test(Token.BIGINT)) { + this.unexpected(); + } + node.value = this.next().valueAsNumeric(); + return this.finishNode(node, 'NumericLiteral'); + } + + // StringLiteral + parseStringLiteral(): ParseNode.StringLiteral { + const node = this.startNode(); + if (!this.test(Token.STRING)) { + this.unexpected(); + } + node.value = this.next().valueAsString(); + return this.finishNode(node, 'StringLiteral'); + } + + // BooleanLiteral : + // `true` + // `false` + parseBooleanLiteral(): ParseNode.BooleanLiteral { + 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(): ParseNode.ArrayLiteral { + const node = this.startNode(); + this.expect(Token.LBRACK); + const ElementList: Mutable = []; + node.ElementList = ElementList; + node.hasTrailingComma = false; + while (true) { + while (this.test(Token.COMMA)) { + const elision = this.startNode(); + this.next(); + 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(); + ElementList.push(this.finishNode(spread, 'SpreadElement')); + } else { + 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(): ParseNode.ObjectLiteral { + const node = this.startNode(); + this.expect(Token.LBRACE); + const PropertyDefinitionList: Mutable = []; + node.PropertyDefinitionList = PropertyDefinitionList; + let hasProto = false; + while (true) { + if (this.eat(Token.RBRACE)) { + break; + } + const PropertyDefinition = this.parsePropertyDefinition(); + if (!this.state.json + && PropertyDefinition.type === 'PropertyDefinition' + && PropertyDefinition.PropertyName + && !IsComputedPropertyKey(PropertyDefinition.PropertyName) + && PropertyDefinition.PropertyName.type !== 'NumericLiteral' + && StringValue(PropertyDefinition.PropertyName).stringValue() === '__proto__') { + if (hasProto) { + this.scope.registerObjectLiteralEarlyError(this.raiseEarly('DuplicateProto', PropertyDefinition.PropertyName)); + } else { + hasProto = true; + } + } + PropertyDefinitionList.push(PropertyDefinition); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'ObjectLiteral'); + } + + parsePropertyDefinition(): ParseNode.PropertyDefinitionLike { + return this.parseBracketedDefinition('property'); + } + + parseFunctionExpression(kind: FunctionKind): ParseNode.FunctionExpressionLike { + return this.parseFunction(true, kind) as ParseNode.FunctionExpressionLike; + } + + parseArguments(): { Arguments: ParseNode.Arguments, trailingComma: boolean } { + this.expect(Token.LPAREN); + if (this.eat(Token.RPAREN)) { + return { Arguments: [], trailingComma: false }; + } + const Arguments: Mutable = []; + 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 }; + } + + /** https://tc39.es/ecma262/#sec-class-definitions */ + // ClassDeclaration : + // DecoratorList? `class` BindingIdentifier ClassTail + // DecoratorList? [+Default] `class` ClassTail + // + // ClassExpression : + // DecoratorList? `class` BindingIdentifier? ClassTail + parseClass(decoratorsAttachedToClassDeclaration: null | readonly ParseNode.Decorator[], isExpression: boolean): ParseNode.ClassLike { + const node = this.startNode(); + + const decorators = decoratorsAttachedToClassDeclaration || this.parseDecorators(); + 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()); + }); + node.Decorators = decorators; + + return this.finishNode(node, isExpression ? 'ClassExpression' : 'ClassDeclaration'); + } + + // ClassTail : ClassHeritage? `{` ClassBody? `}` + // ClassHeritage : `extends` LeftHandSideExpression + // ClassBody : ClassElementList + parseClassTail(): ParseNode.ClassTail { + 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 { + node.ClassBody = this.scope.with({ + superCall: !!node.ClassHeritage, + private: true, + }, () => { + const ClassBody: Mutable = []; + let hasConstructor = false; + while (this.eat(Token.SEMICOLON)) { + // nothing + } + const staticPrivates = new Set(); + const instancePrivates = new Set(); + while (!this.eat(Token.RBRACE)) { + const m = this.parseClassElement(); + ClassBody.push(m); + while (this.eat(Token.SEMICOLON)) { + // nothing + } + if (m.type === 'ClassStaticBlock') { + continue; + } + + if (m.ClassElementName?.type === 'PrivateIdentifier') { + let type: 'field' | 'method' | 'set' | 'get'; + if (m.type === 'FieldDefinition') { + type = 'field'; + } else if (m.UniqueFormalParameters) { + type = 'method'; + } else if (m.PropertySetParameterList) { + type = 'set'; + } else { + type = 'get'; + } + if (type === 'get' || type === 'set') { + if (m.static) { + if (instancePrivates.has(m.ClassElementName.name)) { + this.raiseEarly('InvalidMethodName', m, m.ClassElementName.name); + } else { + staticPrivates.add(m.ClassElementName.name); + } + } else { + if (staticPrivates.has(m.ClassElementName.name)) { + this.raiseEarly('InvalidMethodName', m, m.ClassElementName.name); + } else { + instancePrivates.add(m.ClassElementName.name); + } + } + } + this.scope.declare(m.ClassElementName, 'private', type); + if (m.ClassElementName.name === 'constructor') { + this.raiseEarly('InvalidMethodName', m, m.ClassElementName.name); + } + } + + const name = PropName(m); + const isActualConstructor = !m.static + && m.type === 'MethodDefinition' + && !!m.UniqueFormalParameters + && 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); + } + if (m.static && m.type === 'FieldDefinition' && name === 'constructor') { + this.raiseEarly('InvalidMethodName', m, name); + } + } + return ClassBody; + }); + } + + return this.finishNode(node, 'ClassTail'); + } + + parseClassElement(): ParseNode.ClassElement { + let element; + if (this.test('static') && this.testAhead(Token.LBRACE)) { + const node = this.startNode(); + this.expect('static'); + node.static = true; + this.expect(Token.LBRACE); + const ClassStaticBlockBody = this.startNode(); + ClassStaticBlockBody.ClassStaticBlockStatementList = this.scope.with( + { + lexical: true, + yield: false, + await: true, + return: false, + superProperty: true, + superCall: false, + newTarget: true, + label: 'boundary', + classStaticBlock: true, + }, + () => this.parseStatementList(Token.RBRACE), + ); + node.ClassStaticBlockBody = this.finishNode(ClassStaticBlockBody, 'ClassStaticBlockBody'); + element = this.finishNode(node, 'ClassStaticBlock'); + } else { + element = this.parseBracketedDefinition('class element'); + } + return element; + } + + parseClassExpression(): ParseNode.ClassExpression { + return this.parseClass(null, true) as ParseNode.ClassExpression; + } + + parseTemplateLiteral(tagged = false): ParseNode.TemplateLiteral { + const node = this.startNode(); + const TemplateSpanList: string[] = []; + const ExpressionList: ParseNode.Expression[] = []; + 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; + TemplateSpanList.push(buffer); + this.next(); + if (!tagged) { + TemplateSpanList.forEach((s) => { + if (TV(s) === undefined) { + this.raise('InvalidTemplateEscape'); + } + }); + } + node.TemplateSpanList = TemplateSpanList; + node.ExpressionList = ExpressionList; + return this.finishNode(node, 'TemplateLiteral'); + case '$': + this.position += 1; + if (this.source[this.position] === '{') { + this.position += 1; + TemplateSpanList.push(buffer); + buffer = ''; + this.next(); + 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(): ParseNode.RegularExpressionLiteral { + const node = this.startNode(); + this.scanRegularExpressionBody(); + const body = this.scannedValue as string; // NOTE: unsound cast + node.RegularExpressionBody = body; + const flagPosition = this.position; + this.scanRegularExpressionFlags(); + node.RegularExpressionFlags = this.scannedValue as string; // NOTE: unsound cast + if (node.RegularExpressionFlags.includes('v') && node.RegularExpressionFlags.includes('u')) { + this.raise('InvalidRegExpFlags', flagPosition, 'u and v cannot be used together'); + } + try { + const parse = (flags: RegExpParserContext) => { + const p = new RegExpParser(body); + return p.scope(flags, () => p.parsePattern()); + }; + if (node.RegularExpressionFlags.includes('u')) { + parse({ UnicodeMode: true, NamedCaptureGroups: true }); + } else if (node.RegularExpressionFlags.includes('v')) { + parse({ UnicodeMode: true, UnicodeSetsMode: true, NamedCaptureGroups: true }); + } else { + // NOTE: this part is modified by Annex B (but we're not applying it for now) + // NamedCaptureGroups: false breaks for RegExp /\k(?b)/ + parse({ NamedCaptureGroups: 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, + } as TokenData; // NOTE: unsound cast + this.next(); + this.currentToken = fakeToken; + return this.finishNode(node, 'RegularExpressionLiteral'); + } + + // CoverParenthesizedExpressionAndArrowParameterList : + // `(` Expression `)` + // `(` Expression `,` `)` + // `(` `)` + // `(` `...` BindingIdentifier `)` + // `(` `...` BindingPattern `)` + // `(` Expression `,` `...` BindingIdentifier `)` + // `(` Expression `.` `...` BindingPattern `)` + parseCoverParenthesizedExpressionAndArrowParameterList(): ParseNode.CoverParenthesizedExpressionAndArrowParameterList | ParseNode.ParenthesizedExpression { + 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: (ParseNode.ArgumentListElement | ParseNode.BindingRestElement)[] = []; + 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'); + } else { + this.scope.arrowInfo?.merge(arrowInfo); + } + + // 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] as ParseNode.Expression; // NOTE: unsound cast due to potential BindingRestElement + } else { + commaOp.ExpressionList = expressions as ParseNode.AssignmentExpressionOrHigher[]; // NOTE: unsound cast + node.Expression = this.finishNode(commaOp, 'CommaOperator'); + } + return this.finishNode(node, 'ParenthesizedExpression'); + } + + // PropertyName : + // LiteralPropertyName + // ComputedPropertyName + // LiteralPropertyName : + // IdentifierName + // StringLiteral + // NumericLiteral + // ComputedPropertyName : + // `[` AssignmentExpression `]` + parsePropertyName(): ParseNode.PropertyNameLike { + 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(); + } + + // ClassElementName : + // PropertyName + // PrivateIdentifier + parseClassElementName(): ParseNode.ClassElementName { + if (this.test(Token.PRIVATE_IDENTIFIER)) { + return this.parsePrivateIdentifier(); + } + return this.parsePropertyName(); + } + + // PropertyDefinition : + // IdentifierReference + // CoverInitializedName + // PropertyName `:` AssignmentExpression + // MethodDefinition + // `...` AssignmentExpression + // MethodDefinition : + // ClassElementName `(` UniqueFormalParameters `)` `{` FunctionBody `}` + // GeneratorMethod + // AsyncMethod + // AsyncGeneratorMethod + // `get` ClassElementName `(` `)` `{` FunctionBody `}` + // `set` ClassElementName `(` PropertySetParameterList `)` `{` FunctionBody `}` + // GeneratorMethod : + // `*` ClassElementName `(` UniqueFormalParameters `)` `{` GeneratorBody `}` + // AsyncMethod : + // `async` [no LineTerminator here] ClassElementName `(` UniqueFormalParameters `)` `{` AsyncBody `}` + // AsyncGeneratorMethod : + // `async` [no LineTerminator here] `*` ClassElementName `(` UniqueFormalParameters `)` `{` AsyncGeneratorBody `}` + parseBracketedDefinition(type: 'class element'): ParseNode.ClassElement; + + parseBracketedDefinition(type: 'property'): ParseNode.PropertyDefinitionLike; + + parseBracketedDefinition(type: 'property' | 'class element'): ParseNode.PropertyDefinitionLike | ParseNode.ClassElement; + + parseBracketedDefinition(type: 'property' | 'class element'): ParseNode.PropertyDefinitionLike | ParseNode.ClassElement { + const node = this.startNode(); + + if (type === 'property' && this.eat(Token.ELLIPSIS)) { + node.PropertyName = null; + node.AssignmentExpression = this.parseAssignmentExpression(); + return this.finishNode(node, 'PropertyDefinition'); + } + + let firstFirstName; + let isAccessor = false; + if (type === 'class element') { + node.Decorators = this.parseDecorators(); + const parseAccessorKeyword = surroundingAgent.feature('decorators') ? () => this.try(() => { + this.expect('accessor'); + const next = this.peek(); + if ((next.type === Token.IDENTIFIER || next.type === Token.STRING || next.type === Token.LBRACK || next.type === Token.PRIVATE_IDENTIFIER || next.type === Token.NUMBER || next.type === Token.BIGINT) && !next.hadLineTerminatorBefore) { + isAccessor = true; + return true; + } + return false; + }) : () => false; + if (this.test('static') && ( + this.testAhead(Token.ASSIGN) + || this.testAhead(Token.SEMICOLON) + || this.peekAhead().hadLineTerminatorBefore + || isAutomaticSemicolon(this.peekAhead().type) + )) { + node.static = false; + node.accessor = parseAccessorKeyword(); + firstFirstName = this.parseIdentifierName(); + } else { + node.static = this.eat('static'); + node.accessor = parseAccessorKeyword(); + this.markNodeStart(node); + } + } + + let isGenerator = this.eat(Token.MUL); + let isGetter = false; + let isSetter = false; + let isAsync = false; + if (!isGenerator && !isAccessor) { + 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 = firstFirstName || (type === 'property' + ? this.parsePropertyName() + : this.parseClassElementName()); + + if (!isGenerator && isAsync) { + isGenerator = this.eat(Token.MUL); + } + + const isSpecialMethod = isGenerator || ((isSetter || isGetter || isAsync) && !this.test(Token.LPAREN)); + + if (!isGenerator) { + if (type === 'property' && this.eat(Token.COLON)) { + node.PropertyName = firstName as ParseNode.PropertyName; // NOTE: unsound cast + node.AssignmentExpression = this.parseAssignmentExpression(); + return this.finishNode(node, 'PropertyDefinition'); + } + + if (type === 'class element' && ( + this.test(Token.ASSIGN) + || this.test(Token.SEMICOLON) + || this.peek().hadLineTerminatorBefore + || isAutomaticSemicolon(this.peek().type) + )) { + node.accessor = isAccessor; + node.ClassElementName = firstName; + node.Initializer = this.scope.with({ superProperty: true }, () => this.parseInitializerOpt()); + const argumentNode = node.Initializer && ContainsArguments(node.Initializer); + if (argumentNode) { + this.raiseEarly('UnexpectedToken', argumentNode); + } + const finished = this.finishNode(node, 'FieldDefinition'); + this.semicolon(); + return finished; + } + + if (type === 'property' && this.scope.assignmentInfoStack.length > 0 && this.test(Token.ASSIGN)) { + // NOTE: The next line is unsafe because firstName could be something other than IdentifierName + node.IdentifierReference = this.repurpose(firstName, 'IdentifierReference'); + node.Initializer = this.parseInitializerOpt(); + const finished = this.finishNode(node, 'CoverInitializedName'); + this.scope.registerObjectLiteralEarlyError(this.raiseEarly('UnexpectedToken', finished)); + return finished; + } + + if (type === 'property' + && !isSpecialMethod + && firstName.type === 'IdentifierName' + && !this.test(Token.LPAREN) + && (!isKeywordRaw(firstName.name) + || (firstName.name === 'yield' && !this.scope.hasYield()) + || (firstName.name === 'await' && !this.scope.hasAwait()))) { + const IdentifierReference = this.repurpose(firstName, 'IdentifierReference'); + this.validateIdentifierReference(firstName.name, firstName); + return IdentifierReference; + } + } + + if (isSpecialMethod && (!isGenerator || isAsync)) { + if (type === 'property') { + node.ClassElementName = this.parsePropertyName(); + } else { + node.ClassElementName = this.parseClassElementName(); + } + } else { + node.ClassElementName = firstName; + } + + this.scope.with({ + lexical: true, + variable: true, + superProperty: true, + await: isAsync, + yield: isGenerator, + classStaticBlock: false, + }, () => { + 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 + && !node.static + && node.ClassElementName + && ((node.ClassElementName.type === 'IdentifierName' && node.ClassElementName.name === 'constructor') + || (node.ClassElementName.type === 'StringLiteral' && node.ClassElementName.value === 'constructor')) + && this.scope.hasSuperCall(), + }, () => { + const body = this.parseFunctionBody(isAsync, isGenerator, false); + // Unsafe cast below + if (!isAsync && !isGenerator) { + (node as ParseNode.Unfinished).FunctionBody = body as ParseNode.FunctionBody; + } else if (isAsync && !isGenerator) { + (node as ParseNode.Unfinished).AsyncBody = body as ParseNode.AsyncBody; + } else if (!isAsync && isGenerator) { + (node as ParseNode.Unfinished).GeneratorBody = body as ParseNode.GeneratorBody; + } else if (isAsync && isGenerator) { + (node as ParseNode.Unfinished).AsyncGeneratorBody = body as ParseNode.AsyncGeneratorBody; + } + if (node.UniqueFormalParameters || node.PropertySetParameterList) { + this.validateFormalParameters(node.UniqueFormalParameters || node.PropertySetParameterList!, body, true); + } + }); + }); + + let name: ParseNode.MethodDefinitionLike['type']; + if (isAsync) { + name = isGenerator ? 'AsyncGeneratorMethod' : 'AsyncMethod'; + } else { + name = isGenerator ? 'GeneratorMethod' : 'MethodDefinition'; + } + return this.finishNode(node, name); + } + + parseDecorators(): ParseNode.Decorator[] | null { + if (!surroundingAgent.feature('decorators')) { + return null; + } + const Decorators: ParseNode.Decorator[] = []; + while (true) { + const decorator = this.parseDecorator(); + if (!decorator) { + return Decorators.length ? Decorators : null; + } + Decorators.push(decorator); + } + } + + parseDecorator(): ParseNode.Decorator | undefined { + if (!this.eat(Token.AT)) { + return undefined; + } + // @ DecoratorParenthesizedExpression : `(` Expression[+In] `)` + if (this.eat(Token.LPAREN)) { + const node = this.startNode(); + node.subtype = 'ParenthesizedExpression'; + node.ParenthesizedExpression = this.scope.with({ in: true }, () => this.parseExpression()); + this.expect(Token.RPAREN); + return this.finishNode(node, 'Decorator'); + } + + let result: ParseNode.MemberExpression | ParseNode.IdentifierReference = this.parseIdentifierReference(); + + while (isPropertyOrCall(this.peek().type)) { + let finished: ParseNode.MemberExpression | ParseNode.CallExpression; + const next = this.peek().type; + if (next === Token.PERIOD) { + const node = this.startNode(result); + this.next(); + node.MemberExpression = result; + if (this.test(Token.PRIVATE_IDENTIFIER)) { + node.PrivateIdentifier = this.parsePrivateIdentifier(); + this.scope.checkUndefinedPrivate(node.PrivateIdentifier); + node.IdentifierName = null; + } else { + node.IdentifierName = this.parseIdentifierName(); + node.PrivateIdentifier = null; + } + node.Expression = null; + finished = this.finishNode(node, 'MemberExpression'); + } else if (next === Token.LPAREN) { + const node = this.startNode(result); + const { Arguments } = this.parseArguments(); + node.CallExpression = result; + node.Arguments = Arguments; + finished = this.finishNode(node, 'CallExpression'); + const finishedNode = finished; + + const outerNode = this.startNode(finishedNode); + outerNode.subtype = 'CallExpression'; + outerNode.CallExpression = finishedNode; + return this.finishNode(outerNode, 'Decorator'); + } else { + this.unexpected(); + } + // NOTE: unwinds ParseNode.Finish type alias to avoid circularity issues in type checker + result = finished as ParseNode.MemberExpression; + } + const outerNode = this.startNode(result); + outerNode.subtype = 'MemberExpression'; + outerNode.MemberExpression = result; + return this.finishNode(outerNode, 'Decorator'); + } +} diff --git a/src/parser/FunctionParser.mts b/src/parser/FunctionParser.mts new file mode 100644 index 0000000..12b9bc4 --- /dev/null +++ b/src/parser/FunctionParser.mts @@ -0,0 +1,389 @@ +import { IsSimpleParameterList } from '../static-semantics/all.mts'; +import { type Mutable } from '../helpers.mts'; +import { getDeclarations, type ArrowInfo } from './Scope.mts'; +import { Token } from './tokens.mts'; +import { IdentifierParser } from './IdentifierParser.mts'; +import type { ParseNode, ParseNodesByType } from './ParseNode.mts'; + +export enum FunctionKind { + NORMAL = 0, + ASYNC = 1, +} + +interface ArrowParameterConversions { + 'IdentifierReference': ParseNode.SingleNameBinding; + 'BindingRestElement': ParseNode.BindingRestElement; + 'Elision': ParseNode.Elision; + 'ArrayLiteral': ParseNode.BindingElement; + 'ObjectLiteral': ParseNode.BindingElement; + 'AssignmentExpression': ParseNode.SingleNameBinding | ParseNode.BindingElement; + 'CoverInitializedName': ParseNode.SingleNameBinding; + 'PropertyDefinition': ParseNode.BindingRestProperty | ParseNode.BindingProperty; + 'SpreadElement': ParseNode.BindingRestElement; + 'AssignmentRestElement': ParseNode.BindingRestElement; +} + +type ConvertArrowParameterResult = + T extends keyof ArrowParameterConversions ? ArrowParameterConversions[T] : never; + +interface ConciseBodyInfo { + 'ConciseBody': ParseNode.ConciseBodyLike; + 'AsyncConciseBody': ParseNode.AsyncConciseBodyLike; +} + +export abstract class FunctionParser extends IdentifierParser { + abstract parseStatementList(token: string | Token, directives?: readonly string[]): ParseNode.StatementList; + + abstract parseAssignmentExpression(): ParseNode.AssignmentExpressionOrHigher; + + abstract parseBindingElement(): ParseNode.BindingElementLike; + + abstract parseBindingRestElement(): ParseNode.BindingRestElement; + + // 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 `)` `{` AsyncBody `}` + // Async`FunctionExpression : + // `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncBody `}` + parseFunction(isExpression: boolean, kind: FunctionKind) { + 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)) { + node.BindingIdentifier = this.scope.with({ + await: isExpression ? false : undefined, + yield: isExpression ? false : undefined, + }, () => 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, + parameters: false, + classStaticBlock: false, + }, () => { + this.scope.arrowInfoStack.push(null); + + node.FormalParameters = this.parseFormalParameters(); + + const body = this.parseFunctionBody(isAsync, isGenerator, false); + this.setFunctionBodyGeneric(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); + + this.scope.arrowInfoStack.pop(); + }); + + const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}${isExpression ? 'Expression' : 'Declaration'}` as const; + return this.finishNode(node, name); + } + + private setFunctionBodyGeneric(node: { [P in T]?: ParseNodesByType[T] }, type: T, body: ParseNodesByType[T]) { + node[type] = body; + } + + validateFormalParameters(parameters: ParseNode.FormalParameters, body: ParseNode.FunctionBodyLike | ParseNode.ConciseBody | ParseNode.AsyncConciseBody, 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: T): ConvertArrowParameterResult; + + convertArrowParameter(node: ParseNode) { + switch (node.type) { + case 'IdentifierReference': { + const BindingIdentifier = this.repurpose(node, 'BindingIdentifier'); + const SingleNameBinding = this.startNode(node); + SingleNameBinding.BindingIdentifier = BindingIdentifier; + SingleNameBinding.Initializer = null; + this.scope.declare(node, 'parameter'); + return this.finishNode(SingleNameBinding, 'SingleNameBinding'); + } + case 'BindingRestElement': + this.scope.declare(node, 'parameter'); + return node; + case 'Elision': + return node; + case 'ArrayLiteral': { + const BindingPattern = this.repurpose(node, 'ArrayBindingPattern', (asNew, asOld, asPartial) => { + const BindingElementList: Mutable = []; + asNew.BindingElementList = BindingElementList; + for (const [i, p] of asOld.ElementList.entries()) { + const c = this.convertArrowParameter(p); + if (c.type === 'BindingRestElement') { + if (i !== asOld.ElementList.length - 1) { + this.raiseEarly('UnexpectedToken', c); + } + asNew.BindingRestElement = c; + } else { + BindingElementList.push(c); + } + } + delete asPartial.ElementList; + }); + const BindingElement = this.startNode(node); + BindingElement.BindingPattern = BindingPattern; + BindingElement.Initializer = null; + return this.finishNode(BindingElement, 'BindingElement'); + } + case 'ObjectLiteral': { + const BindingPattern = this.repurpose(node, 'ObjectBindingPattern', (asNew, asOld, asPartial) => { + const BindingPropertyList: Mutable = []; + asNew.BindingPropertyList = BindingPropertyList; + for (const p of asOld.PropertyDefinitionList) { + const c = this.convertArrowParameter(p); + if (c.type === 'BindingRestProperty') { + asNew.BindingRestProperty = c; + } else { + BindingPropertyList.push(c); + } + } + delete asPartial.PropertyDefinitionList; + }); + const BindingElement = this.startNode(node); + BindingElement.BindingPattern = BindingPattern; + BindingElement.Initializer = null; + return this.finishNode(BindingElement, 'BindingElement'); + } + case 'AssignmentExpression': { + const result = this.convertArrowParameter(node.LeftHandSideExpression) as ParseNode.Unfinished; + result.Initializer = node.AssignmentExpression; + return result as ParseNode.SingleNameBinding | ParseNode.BindingElement; + } + case 'CoverInitializedName': { + const SingleNameBinding = this.repurpose(node, 'SingleNameBinding', (asNew, asOld, asPartial) => { + asNew.BindingIdentifier = this.repurpose(asOld.IdentifierReference, 'BindingIdentifier'); + delete asPartial.IdentifierReference; + }); + this.scope.declare(SingleNameBinding, 'parameter'); + return SingleNameBinding; + } + case 'PropertyDefinition': { + let BindingProperty: ParseNode.BindingProperty | ParseNode.BindingRestProperty; + if (node.PropertyName === null) { + BindingProperty = this.repurpose(node, 'BindingRestProperty', (asNew, asOld, asPartial) => { + asNew.BindingIdentifier = this.repurpose(asOld.AssignmentExpression, 'BindingIdentifier'); + delete asPartial.AssignmentExpression; + }); + } else { + BindingProperty = this.repurpose(node, 'BindingProperty', (asNew, asOld, asPartial) => { + asNew.BindingElement = this.convertArrowParameter(asOld.AssignmentExpression); + delete asPartial.AssignmentExpression; + }); + } + this.scope.declare(node, 'parameter'); + return BindingProperty; + } + case 'SpreadElement': + case 'AssignmentRestElement': { + const BindingRestElement = this.repurpose(node, 'BindingRestElement', (asNew, asOld, asPartial) => { + const { AssignmentExpression } = asOld; + if (AssignmentExpression.type === 'AssignmentExpression') { + this.raiseEarly('UnexpectedToken', node); + } else if (AssignmentExpression.type === 'IdentifierReference') { + asNew.BindingIdentifier = this.repurpose(AssignmentExpression, 'BindingIdentifier'); + } else { + asNew.BindingPattern = this.convertArrowParameter(AssignmentExpression).BindingPattern; + } + delete asPartial.AssignmentExpression; + }); + this.scope.declare(BindingRestElement, 'parameter'); + return BindingRestElement; + } + default: + this.raiseEarly('UnexpectedToken', node); + return node; + } + } + + parseArrowFunction(node: ParseNode.Unfinished, { arrowInfo, Arguments }: { arrowInfo?: ArrowInfo, Arguments: ParseNode.CoverParenthesizedExpressionAndArrowParameterList['Arguments'] }, kind: FunctionKind): ParseNode.ArrowFunction | ParseNode.AsyncArrowFunction { + 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, + }, () => { + node.ArrowParameters = this.scope.with({ + parameters: true, + }, () => Arguments.map((p) => this.convertArrowParameter(p))); + const body = this.parseConciseBody(isAsync); + this.validateFormalParameters(node.ArrowParameters, body, true); + let bodyType: 'ConciseBody' | 'AsyncConciseBody'; + if (body.type === 'FunctionBody') { + bodyType = 'ConciseBody'; + } else if (body.type === 'AsyncBody') { + bodyType = 'AsyncConciseBody'; + } else { + bodyType = body.type; + } + this.setConciseBodyGeneric(node, bodyType, body); + }); + return this.finishNode(node, `${isAsync ? 'Async' : ''}ArrowFunction`); + } + + private setConciseBodyGeneric(node: { [P in T]?: ConciseBodyInfo[T] }, type: T, body: ConciseBodyInfo[T]) { + node[type] = body; + } + + parseConciseBody(isAsync: boolean): ParseNode.ConciseBody | ParseNode.FunctionBody | ParseNode.AsyncConciseBody | ParseNode.AsyncBody { + if (this.test(Token.LBRACE)) { + return this.parseFunctionBody(isAsync, false, true) as ParseNode.FunctionBody | ParseNode.AsyncBody; + } + 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(): ParseNode.FormalParameter { + return this.parseBindingElement(); + } + + parseFormalParameters(): ParseNode.FormalParameters { + this.expect(Token.LPAREN); + if (this.eat(Token.RPAREN)) { + return []; + } + const params: Mutable = []; + 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(): ParseNode.UniqueFormalParameters { + return this.parseFormalParameters(); + } + + parseFunctionBody(isAsync: boolean, isGenerator: boolean, isArrow: boolean): ParseNode.FunctionBodyLike { + 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'); + }); + let name: ParseNode.FunctionBodyLike['type']; + if (isAsync) { + name = isGenerator ? 'AsyncGeneratorBody' : 'AsyncBody'; + } else { + name = isGenerator ? 'GeneratorBody' : 'FunctionBody'; + } + return this.finishNode(node, name); + } +} diff --git a/src/parser/IdentifierParser.mts b/src/parser/IdentifierParser.mts new file mode 100644 index 0000000..c9bfcb2 --- /dev/null +++ b/src/parser/IdentifierParser.mts @@ -0,0 +1,144 @@ +import { + Token, + isKeyword, + isReservedWordStrict, + isKeywordRaw, +} from './tokens.mts'; +import { BaseParser } from './BaseParser.mts'; +import type { ParseNode } from './ParseNode.mts'; +import { type Locatable } from './Lexer.mts'; + +export abstract 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().valueAsString(); + } 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.valueAsString(); + break; + case Token.ESCAPED_KEYWORD: + node.name = token.valueAsString(); + 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) { + break; + } + if (arrowInfo.isAsync) { + arrowInfo.awaitIdentifiers.push(node as ParseNode.BindingIdentifier); + break; + } + } + break; + default: + this.unexpected(token); + } + if (this.isStrictMode() && (node.name === 'eval' || node.name === 'arguments')) { + this.raiseEarly('UnexpectedEvalOrArguments', token); + } + this.validateIdentifierReference(node.name, token); + return this.finishNode(node, 'BindingIdentifier'); + } + + // IdentifierReference : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + parseIdentifierReference() { + const node = this.startNode(); + const token = this.next(); + node.escaped = token.escaped; + switch (token.type) { + case Token.IDENTIFIER: + node.name = token.valueAsString(); + break; + case Token.ESCAPED_KEYWORD: + node.name = token.valueAsString(); + 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) { + break; + } + if (arrowInfo.isAsync) { + arrowInfo.awaitIdentifiers.push(node as ParseNode.IdentifierReference); + break; + } + } + node.name = 'await'; + break; + default: + this.unexpected(token); + } + this.validateIdentifierReference(node.name, token); + return this.finishNode(node, 'IdentifierReference'); + } + + validateIdentifierReference(name: string, token: Locatable) { + if (name === 'yield' && (this.scope.hasYield() || this.scope.isModule())) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (name === 'await' && (this.scope.hasAwait() || this.scope.isModule())) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (this.isStrictMode() && isReservedWordStrict(name)) { + this.raiseEarly('UnexpectedReservedWordStrict', token); + } + if (this.scope.inClassStaticBlock() && name === 'arguments') { + this.raiseEarly('UnexpectedEvalOrArguments', token); + } + if (name !== 'yield' && name !== 'await' && isKeywordRaw(name)) { + this.raiseEarly('UnexpectedToken', token); + } + } + + // LabelIdentifier : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + parseLabelIdentifier() { + const node = this.parseIdentifierReference(); + return this.repurpose(node, 'LabelIdentifier'); + } + + // PrivateIdentifier :: + // `#` IdentifierName + parsePrivateIdentifier() { + const node = this.startNode(); + node.name = this.expect(Token.PRIVATE_IDENTIFIER).valueAsString(); + return this.finishNode(node, 'PrivateIdentifier'); + } +} diff --git a/src/parser/LanguageParser.mts b/src/parser/LanguageParser.mts new file mode 100644 index 0000000..1d4ad0c --- /dev/null +++ b/src/parser/LanguageParser.mts @@ -0,0 +1,136 @@ +import type { Mutable } from '../helpers.mts'; +import { ModuleParser } from './ModuleParser.mts'; +import type { ParseNode } from './ParseNode.mts'; +import { Token } from './tokens.mts'; +import { Throw } from '#self'; + +export abstract class LanguageParser extends ModuleParser { + // Script : ScriptBody? + parseScript(): ParseNode.Script { + this.skipHashbangComment(); + const node = this.startNode(); + if (this.eat(Token.EOS)) { + node.ScriptBody = null; + } else { + node.ScriptBody = this.parseScriptBody(); + } + Object.defineProperty(node, 'sourceText', { + configurable: true, + get: () => this.source, + }); + return this.finishNode(node, 'Script'); + } + + // ScriptBody : StatementList + parseScriptBody(): ParseNode.ScriptBody { + const node = this.startNode(); + this.scope.with({ + in: true, + lexical: true, + variable: true, + variableFunctions: true, + }, () => { + const directives: string[] = []; + node.StatementList = this.parseStatementList(Token.EOS, directives); + node.strict = directives.includes('use strict'); + }); + Object.defineProperty(node, 'sourceText', { + configurable: true, + get: () => this.source, + }); + return this.finishNode(node, 'ScriptBody'); + } + + // Module : ModuleBody? + parseModule(): ParseNode.Module { + this.skipHashbangComment(); + return this.scope.with({ + module: true, + strict: true, + in: true, + importMeta: true, + await: true, + 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; + Object.defineProperty(node, 'sourceText', { + configurable: true, + get: () => this.source, + }); + return this.finishNode(node, 'Module'); + }); + } + + // ModuleBody : + // ModuleItemList + parseModuleBody(): ParseNode.ModuleBody { + const node = this.startNode(); + node.ModuleItemList = this.parseModuleItemList(); + Object.defineProperty(node, 'sourceText', { + configurable: true, + get: () => this.source, + }); + return this.finishNode(node, 'ModuleBody'); + } + + // ModuleItemList : + // ModuleItem + // ModuleItemList ModuleItem + // + // ModuleItem : + // ImportDeclaration + // ExportDeclaration + // StatementListItem + parseModuleItemList(): ParseNode.ModuleItemList { + const moduleItemList: Mutable = []; + while (!this.eat(Token.EOS)) { + switch (this.peek().type) { + case Token.IMPORT: + moduleItemList.push(this.parseImportDeclaration()); + break; + case Token.EXPORT: + moduleItemList.push(this.parseExportDeclaration(null)); + break; + case Token.AT: { + const decorators = this.parseDecorators(); + if (this.peek().type === Token.EXPORT) { + // ModuleItem: DecoratorList `export` Declaration + const exports = this.parseExportDeclaration(decorators); + // TODO(decorator): + // ExportDeclaration : DecoratorList? `export` Declaration + // It is a Syntax Error if DecoratorList is present and Declaration is not ClassDeclaration. + if (!exports.ClassDeclaration) { + this.addEarlyError(Throw.SyntaxError('Decorators can only be used to decorate classes'), exports.AssignmentExpression || exports.Declaration || exports.ExportFromClause || exports.FromClause || exports.HoistableDeclaration || exports.VariableStatement || exports.WithClause || exports); + } + // It is a Syntax Error if DecoratorList is present, Declaration is a ClassDeclaration, and the DecoratorList of that ClassDeclaration is present. + // ExportDeclaration : DecoratorList? export default ClassDeclaration + // It is a Syntax Error if DecoratorList is present and the DecoratorList of ClassDeclaration is present. + if (exports.ClassDeclaration && exports.ClassDeclaration.Decorators?.length) { + this.addEarlyError(Throw.SyntaxError('Decorators cannot appear on both sides of the export keyword'), exports.ClassDeclaration.Decorators[0]); + } + moduleItemList.push(exports); + } else { + // ModuleItem : DecoratorList ClassDeclaration + const classDecl = this.parseClassDeclaration(decorators); + moduleItemList.push(classDecl); + } + break; + } + default: + moduleItemList.push(this.parseStatementListItem()); + break; + } + } + return moduleItemList; + } +} diff --git a/src/parser/Lexer.mts b/src/parser/Lexer.mts new file mode 100644 index 0000000..2250ac7 --- /dev/null +++ b/src/parser/Lexer.mts @@ -0,0 +1,1195 @@ +import isUnicodeIDStartRegex from '@unicode/unicode-16.0.0/Binary_Property/ID_Start/regex.js'; +import isUnicodeIDContinueRegex from '@unicode/unicode-16.0.0/Binary_Property/ID_Continue/regex.js'; +import isSpaceSeparatorRegex from '@unicode/unicode-16.0.0/General_Category/Space_Separator/regex.js'; +import { UTF16SurrogatePairToCodePoint } from '../static-semantics/all.mts'; +import { + Assert, CallFrame, isLeadingSurrogate, isTrailingSurrogate, ObjectValue, surroundingAgent, + ThrowCompletion, +} from '../index.mts'; +import type { ErrorObject } from '../intrinsics/Error.mts'; +import { __ts_cast__, getHostDefinedErrorStack } from '../helpers.mts'; +import { + Token, + TokenNames, + TokenValues, + KeywordLookup, + isKeywordRaw, +} from './tokens.mts'; +import type { Location, Position } from './ParseNode.mts'; + +export type Locatable = + | TokenData + | Position + | Location + | { readonly location: Location }; + +const isUnicodeIDStart = (c: string) => c && isUnicodeIDStartRegex.test(c); +const isUnicodeIDContinue = (c: string) => c && isUnicodeIDContinueRegex.test(c); +export const isDecimalDigit = (c: string) => c && /\d/u.test(c); +export const isHexDigit = (c: string) => c && /[\da-f]/ui.test(c); +const isOctalDigit = (c: string) => c && /[0-7]/u.test(c); +const isBinaryDigit = (c: string) => (c === '0' || c === '1'); +export const isWhitespace = (c: string) => c && (/[\u0009\u000B\u000C\u0020\u00A0\uFEFF]/u.test(c) || isSpaceSeparatorRegex.test(c)); // eslint-disable-line no-control-regex +export const isLineTerminator = (c: string | number) => { + // Line Separator (U+2028) and Paragraph Separator (U+2029) + // Line Feed (U+000A) and Carriage Return (U+000D) + if (typeof c === 'string') { + return !!c && /[\r\n\u2028\u2029]/u.test(c); + } + return c === 0x2028 || c === 0x2029 || c === 0xa || c === 0xd; +}; +const isRegularExpressionFlagPart = (c: string) => c && (isUnicodeIDContinue(c) || c === '$'); +export const isIdentifierStart = (c: string) => SingleCharTokens[c] === Token.IDENTIFIER || isUnicodeIDStart(c); +export const isIdentifierPart = (c: string) => SingleCharTokens[c] === Token.IDENTIFIER || c === '\u{200C}' || c === '\u{200D}' || isUnicodeIDContinue(c); + +const SingleCharTokens: { [key: string]: number } = { + '__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, + '#': Token.PRIVATE_IDENTIFIER, + '@': Token.AT, +}; + +export class TokenData { + readonly type: Token; + + readonly startIndex: number; + + readonly endIndex: number; + + readonly line: number; + + readonly column: number; + + readonly hadLineTerminatorBefore: boolean; + + readonly name: string; + + readonly value: string | number | bigint | boolean | null; + + readonly escaped: boolean; + + constructor({ + type, + startIndex, + endIndex, + line, + column, + hadLineTerminatorBefore, + name, + value, + escaped, + }: Pick) { + this.type = type; + this.startIndex = startIndex; + this.endIndex = endIndex; + this.line = line; + this.column = column; + this.hadLineTerminatorBefore = hadLineTerminatorBefore; + this.name = name; + this.value = value; + this.escaped = escaped; + } + + valueAsString() { + Assert(typeof this.value === 'string'); + return this.value; + } + + valueAsNumeric() { + Assert(typeof this.value === 'number' || typeof this.value === 'bigint'); + return this.value; + } + + valueAsBoolean() { + Assert(typeof this.value === 'boolean'); + return this.value; + } +} + +export abstract class Lexer { + protected abstract readonly source: string; + + protected currentToken!: TokenData; // NOTE: unsound definite assignment operator (`!`) + + protected peekToken!: TokenData; // NOTE: unsound definite assignment operator (`!`) + + protected peekAheadToken: TokenData | undefined; + + protected position = 0; + + protected line = 1; + + protected columnOffset = 0; + + protected scannedValue!: string | number | Token | bigint | boolean; // NOTE: unsound definite assignment operator (`!`) + + protected lineTerminatorBeforeNextToken = false; + + protected positionForNextToken = 0; + + protected lineForNextToken = 0; + + protected columnForNextToken = 0; + + protected escapeIndex = -1; + + earlyErrors2 = new Set(); + + decorateSyntaxError(error: ErrorObject, location: number | Locatable) { + // if (template === 'UnexpectedToken' && typeof context !== 'number' && 'type' in context && context.type === Token.EOS) { + // return this.createSyntaxError(context, 'UnexpectedEOS', []); + // } + + let startIndex; + // @ts-ignore unused + let endIndex; + let line; + let column; + if (typeof location === 'number') { + line = this.line; + if (location === this.source.length) { + while (isLineTerminator(this.source[location - 1])) { + line -= 1; + location -= 1; + } + } + startIndex = location; + endIndex = location + 1; + } else if ('type' in location && location.type === Token.EOS) { + line = this.line; + startIndex = location.startIndex; + while (isLineTerminator(this.source[startIndex - 1])) { + line -= 1; + startIndex -= 1; + } + endIndex = startIndex + 1; + } else { + if ('location' in location && location.location) { + location = location.location; + } + ({ + startIndex, + endIndex, + start: { + line, + column, + } = location as Position, // NOTE: unsound cast + } = location as Location); // NOTE: unsound cast + } + + /* + * 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 callFrame = new CallFrame(); + callFrame.columnNumber = column; + callFrame.lineNumber = line; + error.HostDefinedErrorStack = [callFrame]; + } + + static decorateSyntaxErrorWithScriptId(error: ObjectValue, scriptId: string | undefined) { + const stack = getHostDefinedErrorStack(error); + if (stack?.[0] instanceof CallFrame) { + stack[0].scriptId = scriptId; + } + } + + // parseFailure(completion: ThrowCompletion): never; + + addEarlyError({ Value: error }: ThrowCompletion, location: Locatable): void { + __ts_cast__(error); + this.decorateSyntaxError(error, location); + this.earlyErrors2.add(error); + } + + abstract isStrictMode(): boolean; + + abstract createSyntaxError(context: number | Locatable | undefined, template: K, templateArgs: Parameters): SyntaxError; + + abstract raiseEarly(template: K, context?: number | Locatable, ...templateArgs: Parameters): SyntaxError; + + abstract raise(template: K, context?: number | Locatable, ...templateArgs: Parameters): never; + + abstract unexpected(...args: [(number | Locatable)?, ...Parameters]): never; + + try(callback: () => T): T | undefined { + const currentToken = this.currentToken; + const peekToken = this.peekToken; + const peekAheadToken = this.peekAheadToken; + const lineTerminatorBeforeNextToken = this.lineTerminatorBeforeNextToken; + const escapeIndex = this.escapeIndex; + const positionForNextToken = this.positionForNextToken; + const lineForNextToken = this.lineForNextToken; + const columnForNextToken = this.columnForNextToken; + const position = this.position; + const earlyErrors = [...this.earlyErrors2]; + let result: T | undefined; + try { + result = callback(); + } catch {} + if (!result) { + this.currentToken = currentToken; + this.peekToken = peekToken; + this.peekAheadToken = peekAheadToken; + this.lineTerminatorBeforeNextToken = lineTerminatorBeforeNextToken; + this.escapeIndex = escapeIndex; + this.positionForNextToken = positionForNextToken; + this.lineForNextToken = lineForNextToken; + this.columnForNextToken = columnForNextToken; + this.position = position; + this.earlyErrors2 = new Set(earlyErrors); + } + return result; + } + + advance(): TokenData { + this.lineTerminatorBeforeNextToken = false; + this.escapeIndex = -1; + const type = this.nextToken(); + return new TokenData({ + type, + startIndex: this.positionForNextToken, + endIndex: this.position, + line: this.lineForNextToken, + column: this.columnForNextToken, + hadLineTerminatorBefore: this.lineTerminatorBeforeNextToken, + name: TokenNames[type], + value: TokenValues[type] ?? this.scannedValue, + escaped: this.escapeIndex !== -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: string | Token, peek: TokenData) { + 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: string | Token) { + return this.matches(token, this.peek()); + } + + testAhead(token: string | Token) { + return this.matches(token, this.peekAhead()); + } + + eat(token: string | Token) { + if (this.test(token)) { + this.next(); + return true; + } + return false; + } + + expect(token: string | 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.AT: + if (surroundingAgent.feature('decorators')) { + return single; + } else { + return this.unexpected(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(); + + case Token.PRIVATE_IDENTIFIER: + return this.scanIdentifierOrKeyword(true); + + default: + this.unexpected(single); + } + } + + this.position -= 1; + + if (isLeadingSurrogate(c.charCodeAt(0)) || isIdentifierStart(c)) { + return this.scanIdentifierOrKeyword(); + } + + return this.unexpected(this.position); + } + + scanNumber() { + const start = this.position; + let base: 2 | 8 | 10 | 16 = 10; + let nonDecimalPrefixLength = 2; + let zeroLeading = false; + 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: { + if (!isDecimalDigit(this.source[this.position])) { + return Token.NUMBER; + } + // Legacy octal literal (0123) + if (this.isStrictMode()) { + this.raise('LegacyOctalLiteralInStrictMode', start); + } + this.position -= 1; + nonDecimalPrefixLength = 1; + zeroLeading = true; + const oldPos = this.position; + base = 8; + while (this.position < this.source.length) { + const c = this.source[this.position]; + if (isDecimalDigit(c) && !isOctalDigit(c)) { + base = 10; + break; + } else if (!isOctalDigit(c)) { + // A single 0 + break; + } else { + this.position += 1; + } + } + this.position = oldPos; + break; + } + } + 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)) { + this.position += 1; + } else if (c === '_') { + if (zeroLeading) { + this.raise('SeparatorIsNotAllowed', this.position); + } + if (!check(this.source[this.position + 1])) { + this.unexpected(this.position + 1); + } + this.position += 1; + } else { + break; + } + } + if (this.source[this.position] === 'n') { + if (zeroLeading) { + this.raise('BigIntLiteralCannotLeadingZero', this.position); + } + 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 (this.source[this.position] === '_') { + this.unexpected(this.position); + } + while (this.position < this.source.length) { + const c = this.source[this.position]; + if (isDecimalDigit(c)) { + this.position += 1; + } else if (c === '_') { + if (!isDecimalDigit(this.source[this.position + 1])) { + this.unexpected(this.position + 1); + } + this.position += 1; + } else { + break; + } + } + } + if (base === 10 && (this.source[this.position] === 'E' || this.source[this.position] === 'e')) { + this.position += 1; + if (this.source[this.position] === '_') { + this.unexpected(this.position); + } + if (this.source[this.position] === '-' || this.source[this.position] === '+') { + this.position += 1; + } + if (this.source[this.position] === '_') { + this.unexpected(this.position); + } + while (this.position < this.source.length) { + const c = this.source[this.position]; + if (isDecimalDigit(c)) { + this.position += 1; + } else if (c === '_') { + if (!isDecimalDigit(this.source[this.position + 1])) { + this.unexpected(this.position + 1); + } + this.position += 1; + } else { + break; + } + } + } + if (isIdentifierStart(this.source[this.position])) { + this.unexpected(this.position); + } + const buffer = this.source + .slice(base === 10 ? start : start + nonDecimalPrefixLength, this.position) + .replace(/_/g, ''); + this.scannedValue = base === 10 + ? Number.parseFloat(buffer) + : Number.parseInt(buffer, base); + return Token.NUMBER; + } + + scanString(char: string) { + 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; + } 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: { + const lookahead = this.source[this.position + 1]; + if (c === '0' && !isDecimalDigit(lookahead)) { + this.position += 1; + return '\u{0000}'; + } else if (isDecimalDigit(c)) { + if (this.isStrictMode()) { + this.raise('IllegalOctalEscape', this.position); + } + const lookahead2 = this.source[this.position + 2]; + if (c === '0' && (lookahead === '8' || lookahead === '9')) { + // LegacyOctalEscapeSequence :: 0 [lookahead ∈ { 8, 9 }] + // evaluates to \u0000 + 8 or 9 + this.position += 2; + return `\u{0000}${lookahead}`; + } else if (c !== '0' && isOctalDigit(c) && !isOctalDigit(lookahead)) { + // LegacyOctalEscapeSequence :: NonZeroOctalDigit [lookahead ∉ OctalDigit] + // \1 is \u{0001}, etc... + this.position += 1; + return String.fromCodePoint(parseInt(c, 8)); + } else if ((c === '0' || c === '1' || c === '2' || c === '3') && isOctalDigit(lookahead) && !isOctalDigit(lookahead2)) { + // LegacyOctalEscapeSequence :: ZeroToThree OctalDigit [lookahead ∉ OctalDigit] + this.position += 2; + return String.fromCodePoint(parseInt(c + lookahead, 8)); + } else if ((c === '4' || c === '5' || c === '6' || c === '7') && isOctalDigit(lookahead)) { + // LegacyOctalEscapeSequence :: FourToSeven OctalDigit + this.position += 2; + return String.fromCodePoint(parseInt(c + lookahead, 8)); + } else if ((c === '0' || c === '1' || c === '2' || c === '3') && isOctalDigit(lookahead) && isOctalDigit(lookahead2)) { + // LegacyOctalEscapeSequence :: ZeroToThree OctalDigit OctalDigit + this.position += 3; + return String.fromCodePoint(parseInt(c + lookahead + lookahead2, 8)); + } else if (c === '8' || c === '9') { + // NonOctalDecimalEscapeSequence + // \8 or \9 is 8 or 9 + this.position += 1; + return c; + } + } + 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: number) { + 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(isPrivate = false) { + 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 (!isPrivate && isKeywordRaw(buffer)) { + if (escapeIndex !== -1) { + this.scannedValue = buffer; + return Token.ESCAPED_KEYWORD; + } + return KeywordLookup[buffer]; + } else { + this.scannedValue = buffer; + this.escapeIndex = escapeIndex; + return isPrivate ? Token.PRIVATE_IDENTIFIER : 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) + && 'dgimsuyv'.includes(c) + && !buffer.includes(c)) { + this.position += 1; + buffer += c; + } else { + this.scannedValue = buffer; + return; + } + } + } +} diff --git a/src/parser/ModuleParser.mts b/src/parser/ModuleParser.mts new file mode 100644 index 0000000..1f885cb --- /dev/null +++ b/src/parser/ModuleParser.mts @@ -0,0 +1,336 @@ +import { IsStringWellFormedUnicode, StringValue } from '../static-semantics/all.mts'; +import type { Mutable } from '../helpers.mts'; +import { Token, isKeywordRaw } from './tokens.mts'; +import { StatementParser } from './StatementParser.mts'; +import { FunctionKind } from './FunctionParser.mts'; +import type { ParseNode } from './ParseNode.mts'; + +export abstract class ModuleParser extends StatementParser { + // ImportDeclaration : + // `import` ImportClause FromClause WithClause? `;` + // `import` ModuleSpecifier WithClause? `;` + parseImportDeclaration(): ParseNode.ImportDeclaration | ParseNode.ExpressionStatement | ParseNode.LabelledStatement { + 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 { + if (this.test('defer') && this.testAhead(Token.MUL)) { + this.next(); // defer + node.Phase = 'defer'; + const importClause = this.startNode(); + importClause.NameSpaceImport = this.parseNameSpaceImport(); + node.ImportClause = this.finishNode(importClause, 'ImportClause'); + } else { + node.Phase = 'evaluation'; + node.ImportClause = this.parseImportClause(); + } + this.scope.declare(node.ImportClause, 'import'); + node.FromClause = this.parseFromClause(); + } + if (this.test(Token.WITH)) { + node.WithClause = this.parseWithClause(); + } + this.semicolon(); + return this.finishNode(node, 'ImportDeclaration'); + } + + // ImportClause : + // ImportedDefaultBinding + // NameSpaceImport + // NamedImports + // ImportedDefaultBinding `,` NameSpaceImport + // ImportedDefaultBinding `,` NamedImports + // + // ImportedBinding : + // BindingIdentifier + parseImportClause(): ParseNode.ImportClause { + 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(): ParseNode.ImportedDefaultBinding { + const node = this.startNode(); + node.ImportedBinding = this.parseBindingIdentifier(); + return this.finishNode(node, 'ImportedDefaultBinding'); + } + + // NameSpaceImport : + // `*` `as` ImportedBinding + parseNameSpaceImport(): ParseNode.NameSpaceImport { + const node = this.startNode(); + this.expect(Token.MUL); + this.expect('as'); + node.ImportedBinding = this.parseBindingIdentifier(); + return this.finishNode(node, 'NameSpaceImport'); + } + + // NamedImports : + // `{` `}` + // `{` ImportsList `}` + // `{` ImportsList `,` `}` + parseNamedImports(): ParseNode.NamedImports { + const node = this.startNode(); + const ImportsList: Mutable = []; + node.ImportsList = ImportsList; + while (!this.eat(Token.RBRACE)) { + ImportsList.push(this.parseImportSpecifier()); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'NamedImports'); + } + + // ImportSpecifier : + // ImportedBinding + // ModuleExportName `as` ImportedBinding + parseImportSpecifier(): ParseNode.ImportSpecifier { + const node = this.startNode(); + const name = this.parseModuleExportName(); + if (name.type === 'StringLiteral' || this.test('as')) { + this.expect('as'); + node.ModuleExportName = name; + node.ImportedBinding = this.parseBindingIdentifier(); + } else { + node.ImportedBinding = this.repurpose(name, '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 + // DecoratorList? `export` Declaration + // `export` `default` HoistableDeclaration + // DecoratorList? `export` `default` ClassDeclaration + // `export` `default` AssignmentExpression `;` + // + // ExportFromClause : + // `*` + // `*` as ModuleExportName + // NamedExports + parseExportDeclaration(decoratorsBeforeExportKeyword: null | readonly ParseNode.Decorator[]): ParseNode.ExportDeclaration { + const node = this.startNode(); + node.Decorators = decoratorsBeforeExportKeyword; + 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.AT: { + const decorators = this.parseDecorators(); + node.ClassDeclaration = this.scope.with({ default: true }, () => this.parseClassDeclaration(decorators)); + break; + } + case Token.CLASS: + node.ClassDeclaration = this.scope.with({ default: true }, () => this.parseClassDeclaration(null)); + 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, 'default'); + } 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.AT: + case Token.CLASS: + node.Declaration = this.parseClassDeclaration(null); + 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(); + if (this.test(Token.WITH)) { + node.WithClause = this.parseWithClause(); + } + } else { + NamedExports.ExportsList.forEach((n) => { + if (n.localName.type === 'StringLiteral') { + this.raiseEarly('UnexpectedToken', n.localName); + } + }); + node.NamedExports = NamedExports; + this.scope.checkUndefinedExports(node.NamedExports); + } + this.semicolon(); + break; + } + case Token.MUL: { + const inner = this.startNode(); + this.next(); + if (this.eat('as')) { + inner.ModuleExportName = this.parseModuleExportName(); + this.scope.declare(inner.ModuleExportName, 'export'); + } + node.ExportFromClause = this.finishNode(inner, 'ExportFromClause'); + node.FromClause = this.parseFromClause(); + if (this.test(Token.WITH)) { + node.WithClause = this.parseWithClause(); + } + 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(): ParseNode.NamedExports { + const node = this.startNode(); + this.expect(Token.LBRACE); + const ExportsList: Mutable = []; + node.ExportsList = ExportsList; + while (!this.eat(Token.RBRACE)) { + ExportsList.push(this.parseExportSpecifier()); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'NamedExports'); + } + + // ExportSpecifier : + // ModuleExportName + // ModuleExportName `as` ModuleExportName + parseExportSpecifier(): ParseNode.ExportSpecifier { + const node = this.startNode(); + node.localName = this.parseModuleExportName(); + if (this.eat('as')) { + node.exportName = this.parseModuleExportName(); + } else { + node.exportName = node.localName; + } + this.scope.declare(node.exportName, 'export'); + return this.finishNode(node, 'ExportSpecifier'); + } + + // ModuleExportName : + // IdentifierName + // StringLiteral + parseModuleExportName(): ParseNode.ModuleExportName { + if (this.test(Token.STRING)) { + const literal = this.parseStringLiteral(); + if (!IsStringWellFormedUnicode(StringValue(literal))) { + this.raiseEarly('ModuleExportNameInvalidUnicode', literal); + } + return literal; + } + return this.parseIdentifierName(); + } + + // FromClause : + // `from` ModuleSpecifier + parseFromClause(): ParseNode.FromClause { + this.expect('from'); + return this.parseStringLiteral(); + } + + // WithClause : + // `with` `{` `}` + // `with` `{` WithEntries `,`? `}` + parseWithClause(): ParseNode.WithClause { + const node = this.startNode(); + this.expect(Token.WITH); + this.expect(Token.LBRACE); + + const seenKeys = new Set(); + + const WithEntries = []; + while (!this.eat(Token.RBRACE)) { + const entry = this.parseWithEntry(); + + const key = StringValue(entry.AttributeKey).value; + if (seenKeys.has(key)) { + this.raiseEarly('DuplicateImportAttribute', entry, key); + } + seenKeys.add(key); + + WithEntries.push(entry); + if (this.eat(Token.RBRACE)) { + break; + } + this.expect(Token.COMMA); + } + node.WithEntries = WithEntries; + + return this.finishNode(node, 'WithClause'); + } + + parseWithEntry(): ParseNode.WithEntry { + const node = this.startNode(); + node.AttributeKey = this.test(Token.STRING) ? this.parseStringLiteral() : this.parseIdentifierName(); + this.expect(Token.COLON); + node.AttributeValue = this.parseStringLiteral(); + return this.finishNode(node, 'WithEntry'); + } +} diff --git a/src/parser/ParseNode.mts b/src/parser/ParseNode.mts new file mode 100644 index 0000000..95a0fa7 --- /dev/null +++ b/src/parser/ParseNode.mts @@ -0,0 +1,2871 @@ +import type { ArrowInfo } from './Scope.mts'; +import type { Character, UnicodeCharacter } from '#self'; + +export interface Position { + /** 1-based */ + readonly line: number; + readonly column: number; +} + +export interface Location { + readonly startIndex: number; + readonly endIndex: number; + readonly start: Position; + readonly end: Position; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ParseNode { + export interface BaseParseNode { + // NOTE: while we could use `string` here, by limiting `type` to only those types defined in the `ParseNode` + // union we can ensure that new subtypes of `BaseParseNode` are correctly added to the union. + readonly type: ParseNode['type']; + readonly location: Location; + readonly strict: boolean; + readonly sourceText: string; + readonly parent: ParseNode | undefined; + } + + // A.1 Lexical Grammar + // https://tc39.es/ecma262/#sec-lexical-grammar + + // PrivateIdentifier :: + // `#` IdentifierName + export interface PrivateIdentifier extends BaseParseNode { + readonly type: 'PrivateIdentifier'; + readonly name: string; + } + + // IdentifierName :: + // IdentifierStart + // IdentifierName IdentifierPart + export interface IdentifierName extends BaseParseNode { + readonly type: 'IdentifierName'; + readonly name: string; + } + + // NullLiteral :: + // `null` + export interface NullLiteral extends BaseParseNode { + readonly type: 'NullLiteral'; + } + + // BooleanLiteral :: + // `true` + // `false` + export interface BooleanLiteral extends BaseParseNode { + readonly type: 'BooleanLiteral'; + readonly value: boolean; + } + + // NumericLiteral :: + // DecimalLiteral + // DecimalBigIntegerLiteral + // NonDecimalIntegerLiteral + // NonDecimalIntegerLiteral BigIntLiteralSuffix + // LegacyOctalIntegerLiteral + export interface NumericLiteral extends BaseParseNode { + readonly type: 'NumericLiteral'; + readonly value: number | bigint; + } + + // StringLiteral :: + // `"` DoubleStringCharacters? `"` + // `'` SingleStringCharacters? `'` + export interface StringLiteral extends BaseParseNode { + readonly type: 'StringLiteral'; + readonly value: string; + } + + // RegularExpressionLiteral :: + // `/` RegularExpressionBody `/` RegularExpressionFlags + export interface RegularExpressionLiteral extends BaseParseNode { + readonly type: 'RegularExpressionLiteral'; + readonly RegularExpressionBody: string; + readonly RegularExpressionFlags: string; + } + + // A.2 Expressions + // https://tc39.es/ecma262/#sec-expressions + + // IdentifierReference : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + // + // Identifier : + // IdentifierName but not ReservedWord + export interface IdentifierReference extends BaseParseNode { + readonly type: 'IdentifierReference'; + readonly escaped: boolean; + readonly name: string; + } + + // BindingIdentifier : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + // + // Identifier : + // IdentifierName but not ReservedWord + export interface BindingIdentifier extends BaseParseNode { + readonly type: 'BindingIdentifier'; + readonly name: string; + } + + // LabelIdentifier : + // Identifier + // [~Yield] `yield` + // [~Await] `await` + // + // Identifier : + // IdentifierName but not ReservedWord + export interface LabelIdentifier extends BaseParseNode { + readonly type: 'LabelIdentifier'; + readonly name: string; + } + + // PrimaryExpression : + // `this` + // IdentifierReference + // Literal + // ArrayLiteral + // ObjectLiteral + // FunctionExpression + // ClassExpression + // GeneratorExpression + // AsyncFunctionExpression + // AsyncGeneratorExpression + // RegularExpressionLiteral + // TemplateLiteral + // CoverParenthesizedExpressionAndArrowParameterList + export type PrimaryExpression = + | ThisExpression + | IdentifierReference + | Literal + | ArrayLiteral + | ObjectLiteral + | FunctionExpression + | ClassExpression + | GeneratorExpression + | AsyncFunctionExpression + | AsyncGeneratorExpression + | RegularExpressionLiteral + | TemplateLiteral + | CoverParenthesizedExpressionAndArrowParameterList + | ParenthesizedExpression; + + // PrimaryExpression (partial) : + // `this` + export interface ThisExpression extends BaseParseNode { + readonly type: 'ThisExpression'; + } + + // CoverParenthesizedExpressionAndArrowParameterList : + // `(` Expression `)` + // `(` Expression `,` `)` + // `(` `)` + // `(` `...` BindingIdentifier `)` + // `(` `...` BindingPattern `)` + // `(` Expression `,` `...` BindingIdentifier `)` + // `(` Expression `.` `...` BindingPattern `)` + export interface CoverParenthesizedExpressionAndArrowParameterList extends BaseParseNode { + readonly type: 'CoverParenthesizedExpressionAndArrowParameterList'; + readonly Arguments: readonly (ArgumentListElement | BindingRestElement)[]; + readonly arrowInfo?: ArrowInfo; + } + + // CoverParenthesizedExpressionAndArrowParameterList (partial) : + // `(` Expression `)` + // + // ParenthesizedExpression (refined) : + // `(` Expression `)` + export interface ParenthesizedExpression extends BaseParseNode { + readonly type: 'ParenthesizedExpression'; + readonly Expression: Expression; + } + + // Literal : + // NullLiteral + // BooleanLiteral + // NumericLiteral + // StringLiteral + export type Literal = + | NullLiteral + | BooleanLiteral + | NumericLiteral + | StringLiteral; + + // ArrayLiteral : + // `[` `]` + // `[` Elision `]` + // `[` ElementList `]` + // `[` ElementList `,` `]` + // `[` ElementList `,` Elision `]` + export interface ArrayLiteral extends BaseParseNode { + readonly type: 'ArrayLiteral'; + readonly ElementList: ElementList; + readonly hasTrailingComma: boolean; + } + + // ElementList : + // Elision? AssignmentExpression + // Elision? SpreadElement + // ElementList `,` Elision? AssignmentExpression + // ElementList `,` Elision? SpreadElement + export type ElementList = readonly ElementListElement[]; + + // NON-SPEC + export type ElementListElement = + | AssignmentExpressionOrHigher + | SpreadElement + | Elision; + + // Elision : + // `,` + // Elision `,` + export interface Elision extends BaseParseNode { + readonly type: 'Elision'; + } + + // SpreadElement : + // `...` AssignmentExpression + export interface SpreadElement extends BaseParseNode { + readonly type: 'SpreadElement'; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + } + + // ObjectLiteral : + // `{` `}` + // `{` PropertyDefinitionList `}` + // `{` PropertyDefinitionList `,` `}` + export interface ObjectLiteral extends BaseParseNode { + readonly type: 'ObjectLiteral'; + readonly PropertyDefinitionList: PropertyDefinitionList; + } + + // PropertyDefinitionList : + // PropertyDefinition + // PropertyDefinitionList `,` PropertyDefinition + export type PropertyDefinitionList = readonly PropertyDefinitionLike[]; + + // PropertyDefinition : + // IdentifierReference + // CoverInitializedName + // PropertyName `:` AssignmentExpression + // MethodDefinition + // `...` AssignmentExpression + export type PropertyDefinitionLike = + | IdentifierReference + | CoverInitializedName + | PropertyDefinition + | MethodDefinitionLike; + + // PropertyDefinition (partial) : + // PropertyName `:` AssignmentExpression + // `...` AssignmentExpression + export interface PropertyDefinition extends BaseParseNode { + readonly type: 'PropertyDefinition'; + readonly PropertyName: PropertyNameLike | null; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + } + + // PropertyName : + // LiteralPropertyName + // ComputedPropertyName + // + // LiteralPropertyName : + // IdentifierName + // StringLiteral + // NumericLiteral + // + // ComputedPropertyName : + // `[` AssignmentExpression `]` + export type PropertyNameLike = + | PropertyName + | StringLiteral + | NumericLiteral + | IdentifierName; + + // PropertyName (partial) : + // ComputedPropertyName + // + // ComputedPropertyName : + // `[` AssignmentExpression `]` + export interface PropertyName extends BaseParseNode { + readonly type: 'PropertyName'; + readonly ComputedPropertyName: AssignmentExpressionOrHigher; + } + + // CoverInitializedName : + // IdentifierReference Initializer + export interface CoverInitializedName extends BaseParseNode { + readonly type: 'CoverInitializedName'; + readonly IdentifierReference: IdentifierReference; + readonly Initializer: Initializer | null; + } + + // Initializer : + // `=` AssignmentExpression + export type Initializer = AssignmentExpressionOrHigher; + + // TemplateLiteral : + // NoSubstitutionTemplate + // SubstitutionTemplate + // + // SubstitutionTemplate : + // TemplateHead Expression TemplateSpans + // + // TemplateSpans : + // TemplateTail + // TemplateMiddleList TemplateTail + // + // TemplateMiddleList : + // TemplateMiddle Expression + // TemplateMiddleList TemplateMiddle Expression + export interface TemplateLiteral extends BaseParseNode { + readonly type: 'TemplateLiteral'; + readonly TemplateSpanList: readonly string[]; + readonly ExpressionList: readonly Expression[]; + } + + // MemberExpression : + // PrimaryExpression + // MemberExpression `[` Expression `]` + // MemberExpression `.` IdentifierName + // MemberExpression TemplateLiteral + // SuperProperty + // MetaProperty + // `new` MemberExpression Arguments + // MemberExpression `.` PrivateIdentifier + export type MemberExpressionOrHigher = + | PrimaryExpression + | MemberExpression + | SuperProperty + | MetaProperty + | NewExpression; + + // MemberExpression : + // MemberExpression `[` Expression `]` + // MemberExpression `.` IdentifierName + // MemberExpression `.` PrivateIdentifier + export interface MemberExpression extends BaseParseNode { + readonly type: 'MemberExpression'; + + /// MemberExpression : MemberExpression `[` Expression `]` + // readonly MemberExpression: LeftHandSideExpression; // NOTE: Should be MemberExpressionOrHigher + // readonly Expression: Expression | null; + + /// MemberExpression : MemberExpression `.` IdentifierName + // readonly MemberExpression: LeftHandSideExpression; // NOTE: Should be MemberExpressionOrHigher + // readonly IdentifierName: IdentifierName | null; + + /// MemberExpression : MemberExpression `.` PrivateIdentifier + // readonly MemberExpression: LeftHandSideExpression; // NOTE: Should be MemberExpressionOrHigher + // readonly PrivateIdentifier: PrivateIdentifier | null; + + readonly MemberExpression: LeftHandSideExpression; // NOTE: Should be MemberExpressionOrHigher + readonly Expression: Expression | null; + readonly IdentifierName: IdentifierName | null; + readonly PrivateIdentifier: PrivateIdentifier | null; + } + + // SuperProperty : + // super `[` Expression `]` + // super `.` IdentifierName + export interface SuperProperty extends BaseParseNode { + readonly type: 'SuperProperty'; + + /// SuperProperty : super `[` Expression `]` + // readonly Expression: Expression | null; + + /// SuperProperty : super `.` IdentifierName + // readonly IdentifierName: IdentifierName | null; + + readonly Expression: Expression | null; + readonly IdentifierName: IdentifierName | null; + } + + // MetaProperty : + // NewTarget + // ImportMeta + export type MetaProperty = + | NewTarget + | ImportMeta; + + // NewTarget : + // `new` `.` `target` + export interface NewTarget extends BaseParseNode { + readonly type: 'NewTarget'; + } + + // ImportMeta : + // `import` `.` `meta` + export interface ImportMeta extends BaseParseNode { + readonly type: 'ImportMeta'; + } + + // NewExpression : + // MemberExpression + // `new` NewExpression + export type NewExpressionOrHigher = + | MemberExpressionOrHigher + | NewExpression; + + // NewExpression (partial) : + // `new` NewExpression + // + // MemberExpression (partial) : + // `new` MemberExpression Arguments + export interface NewExpression extends BaseParseNode { + readonly type: 'NewExpression'; + // NOTE: Should be NewExpressionOrHigher | MemberExpressionOrHigher + readonly MemberExpression: LeftHandSideExpression; + readonly Arguments: Arguments | null; + } + + // CallExpression : + // CoverCallExpressionAndAsyncArrowHead + // SuperCall + // ImportCall + // CallExpression Arguments + // CallExpression `[` Expression `]` + // CallExpression `.` IdentifierName + // CallExpression TemplateLiteral + // CallExpression `.` PrivateIdentifier + export type CallExpressionOrHigher = + // CoverCallExpressionAndAsyncArrowHead + | SuperCall + | ImportCall + | CallExpression + | MemberExpression + | TaggedTemplateExpression; + + // CallExpression (partial) : + // CoverCallExpressionAndAsyncArrowHead + // CallExpression Arguments + // + // CallMemberExpression (refined) : + // MemberExpression Arguments + export interface CallExpression extends BaseParseNode { + readonly type: 'CallExpression'; + readonly CallExpression: CallExpressionOrHigher | MemberExpressionOrHigher; + readonly Arguments: Arguments; + // NON-SPEC + readonly arrowInfo?: ArrowInfo; + } + + // CallExpression (partial) : + // CallExpression TemplateLiteral + // + // MemberExpression (partial) : + // MemberExpression TemplateLiteral + export interface TaggedTemplateExpression extends BaseParseNode { + readonly type: 'TaggedTemplateExpression'; + readonly MemberExpression: CallExpressionOrHigher | MemberExpressionOrHigher; + readonly TemplateLiteral: TemplateLiteral; + // NON-SPEC + readonly arrowInfo?: ArrowInfo; + } + + // SuperCall : + // `super` Arguments + export interface SuperCall extends BaseParseNode { + readonly type: 'SuperCall'; + readonly Arguments: Arguments; + } + + // ImportCall : + // `import` `(` AssignmentExpression `,`? `)` + // `import` `(` AssignmentExpression `,` AssignmentExpression `,`? `)` + export interface ImportCall extends BaseParseNode { + readonly type: 'ImportCall'; + readonly Phase: 'defer' | 'evaluation'; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + readonly OptionsExpression?: AssignmentExpressionOrHigher; + } + + // Arguments : + // `(` `)` + // `(` ArgumentList `)` + // `(` ArgumentList `,` `)` + // + // ArgumentList : + // AssignmentExpression + // `...` AssignmentExpression + // ArgumentList `,` AssignmentExpression + // ArgumentList `,` `...` AssignmentExpression + export type Arguments = readonly ArgumentListElement[]; + + // NON-SPEC + export type ArgumentListElement = + | AssignmentExpressionOrHigher + | AssignmentRestElement; + + // OptionalExpression : + // MemberExpression OptionalChain + // CallExpression OptionalChain + // OptionalExpression OptionalChain + export interface OptionalExpression extends BaseParseNode { + readonly type: 'OptionalExpression'; + // NOTE: The following doesn't match how this is handled in other nodes. + readonly MemberExpression: MemberExpressionOrHigher | CallExpressionOrHigher | OptionalExpression; + readonly OptionalChain: OptionalChain; + } + + // OptionalChain : + // `?.` Arguments + // `?.` `[` Expression `]` + // `?.` IdentifierName + // `?.` PrivateIdentifier + // OptionalChain `?.` Arguments + // OptionalChain `?.` `[` Expression `]` + // OptionalChain `?.` IdentifierName + // OptionalChain `?.` PrivateIdentifier + export interface OptionalChain extends BaseParseNode { + readonly type: 'OptionalChain'; + + /// OptionalChain : `?.` Arguments + // readonly Arguments?: Arguments; + + /// OptionalChain : `?.` `[` Expression `]` + // readonly Expression?: Expression; + + /// OptionalChain : `?.` IdentifierName + // readonly IdentifierName?: IdentifierName; + + /// OptionalChain : `?.` PrivateIdentifier + // readonly PrivateIdentifier?: PrivateIdentifier; + + /// OptionalChain : OptionalChain `?.` Arguments + // readonly OptionalChain: OptionalChain | null; + // readonly Arguments?: Arguments; + + /// OptionalChain : OptionalChain `?.` `[` Expression `]` + // readonly OptionalChain: OptionalChain | null; + // readonly Expression?: Expression; + + /// OptionalChain : OptionalChain `?.` IdentifierName + // readonly OptionalChain: OptionalChain | null; + // readonly IdentifierName?: IdentifierName; + + /// OptionalChain : OptionalChain `?.` PrivateIdentifier + // readonly OptionalChain: OptionalChain | null; + // readonly PrivateIdentifier?: PrivateIdentifier; + + readonly OptionalChain: OptionalChain | null; + readonly Arguments?: Arguments; + readonly Expression?: Expression; + readonly IdentifierName?: IdentifierName; + readonly PrivateIdentifier?: PrivateIdentifier; + } + + // LeftHandSideExpression : + // NewExpression + // CallExpression + // OptionalExpression + export type LeftHandSideExpression = + | NewExpressionOrHigher + | CallExpressionOrHigher + | OptionalExpression; + + // UpdateExpression : + // LeftHandSideExpression + // LeftHandSideExpression [no LineTerminator here] `++` + // LeftHandSideExpression [no LineTerminator here] `--` + // `++` UnaryExpression + // `--` UnaryExpression + export type UpdateExpressionOrHigher = + | LeftHandSideExpression + | UpdateExpression; + + // UpdateExpression (partial) : + // LeftHandSideExpression [no LineTerminator here] `++` + // LeftHandSideExpression [no LineTerminator here] `--` + // `++` UnaryExpression + // `--` UnaryExpression + export interface UpdateExpression extends BaseParseNode { + readonly type: 'UpdateExpression'; + + /// UpdateExpression : + /// LeftHandSideExpression [no LineTerminator here] `++` + /// LeftHandSideExpression [no LineTerminator here] `--` + // readonly LeftHandSideExpression: LeftHandSideExpression | null; + // readonly operator: '++' | '--'; + + /// UpdateExpression : + /// `++` UnaryExpression + /// `--` UnaryExpression + // readonly operator: '++' | '--'; + // readonly UnaryExpression: UnaryExpressionOrHigher | null; + + readonly operator: '++' | '--'; + readonly LeftHandSideExpression: LeftHandSideExpression | null; + readonly UnaryExpression: UnaryExpressionOrHigher | null; + } + + // UnaryExpression : + // UpdateExpression + // `delete` UnaryExpression + // `void` UnaryExpression + // `typeof` UnaryExpression + // `+` UnaryExpression + // `-` UnaryExpression + // `~` UnaryExpression + // `!` UnaryExpression + // [+Await] AwaitExpression + export type UnaryExpressionOrHigher = + | UpdateExpressionOrHigher + | UnaryExpression + | AwaitExpression; + + // UnaryExpression (partial) : + // `delete` UnaryExpression + // `void` UnaryExpression + // `typeof` UnaryExpression + // `+` UnaryExpression + // `-` UnaryExpression + // `~` UnaryExpression + // `!` UnaryExpression + export interface UnaryExpression extends BaseParseNode { + readonly type: 'UnaryExpression'; + readonly operator: 'delete' | 'void' | 'typeof' | '+' | '-' | '~' | '!'; + readonly UnaryExpression: UnaryExpressionOrHigher; + } + + // ExponentiationExpression : + // UnaryExpression + // UpdateExpresion `**` ExponentiationExpression + export type ExponentiationExpressionOrHigher = + | UnaryExpressionOrHigher + | ExponentiationExpression; + + // ExponentiationExpression (partial) : + // UpdateExpresion `**` ExponentiationExpression + export interface ExponentiationExpression extends BaseParseNode { + readonly type: 'ExponentiationExpression'; + readonly UpdateExpression: UpdateExpressionOrHigher; + readonly ExponentiationExpression: ExponentiationExpressionOrHigher; + } + + // MultiplicativeExpression : + // ExponentiationExpression + // MultiplicativeExpression MultiplicativeOperator ExponentiationExpression + export type MultiplicativeExpressionOrHigher = + | ExponentiationExpressionOrHigher + | MultiplicativeExpression; + + // MultiplicativeExpression (partial) : + // MultiplicativeExpression MultiplicativeOperator ExponentiationExpression + export interface MultiplicativeExpression extends BaseParseNode { + readonly type: 'MultiplicativeExpression'; + readonly MultiplicativeExpression: MultiplicativeExpressionOrHigher; + readonly MultiplicativeOperator: MultiplicativeOperator; + readonly ExponentiationExpression: ExponentiationExpressionOrHigher; + } + + // MultiplicativeOperator : one of + // `*` `/` `%`; + export type MultiplicativeOperator = '*' | '/' | '%'; + + // AdditiveExpression : + // MultiplicativeExpression + // AdditiveExpression `+` MultiplicativeExpression + // AdditiveExpression `-` MultiplicativeExpression + export type AdditiveExpressionOrHigher = + | MultiplicativeExpressionOrHigher + | AdditiveExpression; + + // AdditiveExpression (partial) : + // AdditiveExpression `+` MultiplicativeExpression + // AdditiveExpression `-` MultiplicativeExpression + export interface AdditiveExpression extends BaseParseNode { + readonly type: 'AdditiveExpression'; + readonly operator: '+' | '-'; + readonly AdditiveExpression: AdditiveExpressionOrHigher; + readonly MultiplicativeExpression: MultiplicativeExpressionOrHigher; + } + + // ShiftExpression : + // AdditiveExpression + // ShiftExpression `<<` AdditiveExpression + // ShiftExpression `>>` AdditiveExpression + // ShiftExpression `>>>` AdditiveExpression + export type ShiftExpressionOrHigher = + | AdditiveExpressionOrHigher + | ShiftExpression; + + // ShiftExpression (partial) : + // ShiftExpression `<<` AdditiveExpression + // ShiftExpression `>>` AdditiveExpression + // ShiftExpression `>>>` AdditiveExpression + export interface ShiftExpression extends BaseParseNode { + readonly type: 'ShiftExpression'; + readonly operator: '<<' | '>>' | '>>>'; + readonly ShiftExpression: ShiftExpressionOrHigher; + readonly AdditiveExpression: AdditiveExpressionOrHigher; + } + + // RelationalExpression : + // ShiftExpression + // RelationalExpression `<` ShiftExpression + // RelationalExpression `>` ShiftExpression + // RelationalExpression `<=` ShiftExpression + // RelationalExpression `>=` ShiftExpression + // RelationalExpression `instanceof` ShiftExpression + // RelationalExpression `in` ShiftExpression + export type RelationalExpressionOrHigher = + | ShiftExpressionOrHigher + | RelationalExpression; + + // RelationalExpression (partial) : + // RelationalExpression `<` ShiftExpression + // RelationalExpression `>` ShiftExpression + // RelationalExpression `<=` ShiftExpression + // RelationalExpression `>=` ShiftExpression + // RelationalExpression `instanceof` ShiftExpression + // RelationalExpression `in` ShiftExpression + // PrivateIdentifier `in` ShiftExpression + export interface RelationalExpression extends BaseParseNode { + readonly type: 'RelationalExpression'; + readonly operator: '<' | '>' | '<=' | '>=' | 'instanceof' | 'in'; + readonly PrivateIdentifier?: PrivateIdentifier; + readonly RelationalExpression?: RelationalExpressionOrHigher; + readonly ShiftExpression: ShiftExpressionOrHigher; + } + + // EqualityExpression : + // RelationalExpression + // EqualityExpression == RelationalExpression + // EqualityExpression != RelationalExpression + // EqualityExpression === RelationalExpression + // EqualityExpression !== RelationalExpression + export type EqualityExpressionOrHigher = + | RelationalExpressionOrHigher + | EqualityExpression; + + // EqualityExpression (partial) : + // EqualityExpression == RelationalExpression + // EqualityExpression != RelationalExpression + // EqualityExpression === RelationalExpression + // EqualityExpression !== RelationalExpression + export interface EqualityExpression extends BaseParseNode { + readonly type: 'EqualityExpression'; + readonly operator: '==' | '!=' | '===' | '!=='; + readonly EqualityExpression: EqualityExpressionOrHigher; + readonly RelationalExpression: RelationalExpressionOrHigher; + } + + // BitwiseANDExpression : + // EqualityExpression + // BitwiseANDExpression `^&` EqualityExpression + export type BitwiseANDExpressionOrHigher = + | EqualityExpressionOrHigher + | BitwiseANDExpression; + + // BitwiseANDExpression (partial) : + // BitwiseANDExpression `^&` EqualityExpression + export interface BitwiseANDExpression extends BaseParseNode { + readonly type: 'BitwiseANDExpression'; + readonly operator: '&'; + readonly A: BitwiseANDExpressionOrHigher; + readonly B: EqualityExpressionOrHigher; + } + + // BitwiseXORExpression : + // BitwiseANDExpression + // BitwiseXORExpression `^` BitwiseANDExpression + export type BitwiseXORExpressionOrHigher = + | BitwiseANDExpressionOrHigher + | BitwiseXORExpression; + + // BitwiseXORExpression (partial) : + // BitwiseXORExpression `^` BitwiseANDExpression + export interface BitwiseXORExpression extends BaseParseNode { + readonly type: 'BitwiseXORExpression'; + readonly operator: '^'; + readonly A: BitwiseXORExpressionOrHigher; + readonly B: BitwiseANDExpressionOrHigher; + } + + // BitwiseORExpression : + // BitwiseXORExpression + // BitwiseORExpression `|` BitwiseXORExpression + export type BitwiseORExpressionOrHigher = + | BitwiseXORExpressionOrHigher + | BitwiseORExpression; + + // BitwiseORExpression (partial) : + // BitwiseORExpression `|` BitwiseXORExpression + export interface BitwiseORExpression extends BaseParseNode { + readonly type: 'BitwiseORExpression'; + readonly operator: '|'; + readonly A: BitwiseORExpressionOrHigher; + readonly B: BitwiseXORExpressionOrHigher; + } + + // LogicalANDExpression : + // BitwiseORExpression + // LogicalANDExpression `&&` BitwiseORExpression + export type LogicalANDExpressionOrHigher = + | BitwiseORExpressionOrHigher + | LogicalANDExpression; + + // LogicalANDExpression (partial) : + // LogicalANDExpression `&&` BitwiseORExpression + export interface LogicalANDExpression extends BaseParseNode { + readonly type: 'LogicalANDExpression'; + readonly LogicalANDExpression: LogicalANDExpressionOrHigher; + readonly BitwiseORExpression: BitwiseORExpressionOrHigher; + } + + // LogicalORExpression : + // LogicalANDExpression + // LogicalORExpression `||` LogicalANDExpression + export type LogicalORExpressionOrHigher = + | LogicalANDExpressionOrHigher + | LogicalORExpression; + + // LogicalORExpression (partial) : + // LogicalORExpression `||` LogicalANDExpression + export interface LogicalORExpression extends BaseParseNode { + readonly type: 'LogicalORExpression'; + readonly LogicalORExpression: LogicalORExpressionOrHigher; + readonly LogicalANDExpression: LogicalANDExpressionOrHigher; + } + + // CoalesceExpression : + // CoalesceExpressionHead `??` BitwiseORExpression + export interface CoalesceExpression extends BaseParseNode { + readonly type: 'CoalesceExpression'; + readonly CoalesceExpressionHead: CoalesceExpressionHead; + readonly BitwiseORExpression: BitwiseORExpressionOrHigher; + } + + // CoalesceExpressionHead : + // CoalesceExpression + // BitwiseORExpression + export type CoalesceExpressionHead = + | BitwiseORExpressionOrHigher + | CoalesceExpression; + + // ShortCircuitExpression : + // LogicalORExpression + // CoalesceExpression + export type ShortCircuitExpressionOrHigher = + | LogicalORExpressionOrHigher + | CoalesceExpression; + + // ConditionalExpression : + // ShortCircuitExpression + // ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression + export type ConditionalExpressionOrHigher = + | ShortCircuitExpressionOrHigher + | ConditionalExpression; + + // ConditionalExpression (partial) : + // ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression + export interface ConditionalExpression extends BaseParseNode { + readonly type: 'ConditionalExpression'; + readonly ShortCircuitExpression: ShortCircuitExpressionOrHigher; + readonly AssignmentExpression_a: AssignmentExpressionOrHigher; + readonly AssignmentExpression_b: AssignmentExpressionOrHigher; + } + + // AssignmentExpression : + // ConditionalExpression + // [+Yield] YieldExpression + // ArrowFunction + // AsyncArrowFunction + // LeftHandSideExpression `=` AssignmentExpression + // LeftHandSideExpression AssignmentOperator AssignmentExpression + // LeftHandSideExpression LogicalAssignmentOperator AssignmentExpression + export type AssignmentExpressionOrHigher = + | ConditionalExpressionOrHigher + | YieldExpression + | ArrowFunction + | AsyncArrowFunction + | AssignmentExpression; + + // AssignmentExpression (partial) : + // LeftHandSideExpression `=` AssignmentExpression + // LeftHandSideExpression AssignmentOperator AssignmentExpression + // LeftHandSideExpression LogicalAssignmentOperator AssignmentExpression + // + export interface AssignmentExpression extends BaseParseNode { + readonly type: 'AssignmentExpression'; + // NOTE: Should be LeftHandSideExpression, but some invalid nodes are allowed as they report early errors + readonly LeftHandSideExpression: AssignmentExpressionOrHigher; + readonly AssignmentOperator: '=' | AssignmentOperator | '&&=' | '||=' | '??='; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + } + + // AssignmentOperator : one of + // `*=` `/=` `%=` `+=` `-=` `<<=` `>>=` `>>>=` `&=` `^=` `|=` `**=` + export type AssignmentOperator = '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '>>>=' | '&=' | '^=' | '|=' | '**='; + + // AssignmentRestElement : + // `...` DestructuringAssignmentTarget + // + // ArgumentList (partial) : + // `...` AssignmentExpression + // ArgumentList `,` `...` AssignmentExpression + export interface AssignmentRestElement extends BaseParseNode { + readonly type: 'AssignmentRestElement'; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + } + + // NON-SPEC + export type BinaryExpressionOrHigher = + | BinaryExpression + | UnaryExpressionOrHigher; + + // NON-SPEC + export type BinaryExpression = + | AssignmentExpression + | LogicalORExpression + | LogicalANDExpression + | BitwiseORExpression + | BitwiseXORExpression + | BitwiseANDExpression + | RelationalExpression + | EqualityExpression + | ShiftExpression + | AdditiveExpression + | MultiplicativeExpression + | ExponentiationExpression; + + // Expression : + // AssignmentExpression + // Expression `,` AssignmentExpression + export type Expression = + | CommaOperator + | AssignmentExpressionOrHigher; + + // Expression (partial) : + // Expression `,` AssignmentExpression + export interface CommaOperator extends BaseParseNode { + readonly type: 'CommaOperator'; + readonly ExpressionList: readonly AssignmentExpressionOrHigher[]; + } + + // A.3 Statements + // https://tc39.es/ecma262/#sec-statements + + // Statement : + // BlockStatement + // VariableStatement + // EmptyStatement + // ExpressionStatement + // IfStatement + // BreakableStatement + // ContinueStatement + // BreakStatement + // [+Return] ReturnStatement + // WithStatement + // LabelledStatement + // ThrowStatement + // TryStatement + // DebuggerStatement + export type Statement = + | BlockStatement + | VariableStatement + | EmptyStatement + | ExpressionStatement + | IfStatement + | BreakableStatement + | ContinueStatement + | BreakStatement + | ReturnStatement + | WithStatement + | LabelledStatement + | ThrowStatement + | TryStatement + | DebuggerStatement; + + // Declaration : + // HoistableDeclaration + // ClassDeclaration + // LexicalDeclarationLike + export type Declaration = + | HoistableDeclaration + | ClassDeclaration + | LexicalDeclarationLike; + + // HoistableDeclaration + // FunctionDeclaration + // GeneratorDeclaration + // AsyncFunctionDeclaration + // AsyncGeneratorDeclaration + export type HoistableDeclaration = + | FunctionDeclaration + | GeneratorDeclaration + | AsyncFunctionDeclaration + | AsyncGeneratorDeclaration; + + // BreakableStatement : + // IterationStatement + // SwitchStatement + export type BreakableStatement = + | IterationStatement + | SwitchStatement; + + // BlockStatement : + // Block + export type BlockStatement = + | Block; + + // Block : + // `{` StatementList `}` + export interface Block extends BaseParseNode { + readonly type: 'Block'; + readonly StatementList: StatementList; + } + + // StatementList : + // StatementListItem + // StatementList StatementListItem + export type StatementList = readonly StatementListItem[]; + + // StatementListItem : + // Statement + // Declaration + export type StatementListItem = + | Statement + | Declaration; + + // LexicalDeclaration : + // LetOrConst BindingList `;` + export type LexicalDeclarationLike = + | LexicalDeclaration; + + // LexicalDeclaration : + // LetOrConst BindingList `;` + export interface LexicalDeclaration extends BaseParseNode { + readonly type: 'LexicalDeclaration'; + readonly LetOrConst: LetOrConst; + readonly BindingList: BindingList; + } + + // LetOrConst : + // `let` + // `const` + export type LetOrConst = + | 'let' + | 'const'; + + // BindingList : + // LexicalBinding + // BindingList `,` LexicalBinding + export type BindingList = readonly LexicalBinding[]; + + // LexicalBinding : + // BindingIdentifier Initializer? + // BindingPattern Initializer + export interface LexicalBinding extends BaseParseNode { + readonly type: 'LexicalBinding'; + + // LexicalBinding : BindingIdentifier Initializer? + readonly BindingIdentifier?: BindingIdentifier; + + // LexicalBinding : BindingPattern Initializer + readonly BindingPattern?: BindingPattern; + + readonly Initializer: Initializer | null; + } + + // VariableStatement : + // `var` VariableDeclarationList `;` + export interface VariableStatement extends BaseParseNode { + readonly type: 'VariableStatement'; + readonly VariableDeclarationList: VariableDeclarationList; + } + + // VariableDeclarationList : + // VariableDeclaration + // VariableDeclarationList `,` VariableDeclaration + export type VariableDeclarationList = readonly VariableDeclaration[]; + + // VariableDeclaration : + // BindingIdentifier Initializer? + // BindingPattern Initializer + export interface VariableDeclaration extends BaseParseNode { + readonly type: 'VariableDeclaration'; + readonly BindingPattern?: BindingPattern; + readonly BindingIdentifier?: BindingIdentifier; + readonly Initializer: Initializer | null; + } + + // BindingPattern : + // ObjectBindingPattern + // ArrayBindingPattern + export type BindingPattern = + | ObjectBindingPattern + | ArrayBindingPattern; + + // ObjectBindingPattern : + // `{` `}` + // `{` BindingRestProperty `}` + // `{` BindingPropertyList `}` + // `{` BindingPropertyList `,` BindingRestProperty? `}` + export interface ObjectBindingPattern extends BaseParseNode { + readonly type: 'ObjectBindingPattern'; + readonly BindingPropertyList: BindingPropertyList; + readonly BindingRestProperty?: BindingRestProperty; + } + + // ArrayBindingPattern : + // `[` Elision? BindingRestElement `]` + // `[` BindingElementList `]` + // `[` BindingElementList `,` Elision? BindingRestElement `]` + export interface ArrayBindingPattern extends BaseParseNode { + readonly type: 'ArrayBindingPattern'; + readonly BindingElementList: BindingElementList; + readonly BindingRestElement: BindingRestElement; + } + + // BindingRestProperty : + // `...` BindingIdentifier + export interface BindingRestProperty extends BaseParseNode { + readonly type: 'BindingRestProperty'; + readonly BindingIdentifier: BindingIdentifier; + } + + // BindingPropertyList : + // BindingProperty + // BindingPropertyList BindingProperty + export type BindingPropertyList = readonly BindingPropertyLike[]; + + // BindingElementList : + // BindingElisionElement + // BindingElementList `,` BindingElisionElement + export type BindingElementList = readonly BindingElisionElement[]; + + // BindingElisionElement : + // Elision? BindingElement + export type BindingElisionElement = + | BindingElementLike + | Elision; + + // BindingProperty : + // SingleNameBinding + // PropertyName `:` BindingElement + export type BindingPropertyLike = + | BindingProperty + | SingleNameBinding; + + // BindingProperty : + // PropertyName `:` BindingElement + export interface BindingProperty extends BaseParseNode { + readonly type: 'BindingProperty'; + readonly PropertyName: PropertyNameLike; + readonly BindingElement: BindingElementLike; + } + + // BindingElement : + // SingleNameBinding + // BindingPattern Initializer? + export type BindingElementLike = + | BindingElement + | SingleNameBinding; + + // BindingElement (partial) : + // BindingPattern Initializer? + export interface BindingElement extends BaseParseNode { + readonly type: 'BindingElement'; + readonly BindingPattern: BindingPattern; + readonly Initializer: Initializer | null; + } + + // SingleNameBinding : + // BindingIdentifier Initializer? + export interface SingleNameBinding extends BaseParseNode { + readonly type: 'SingleNameBinding'; + readonly BindingIdentifier: BindingIdentifier; + readonly Initializer: Initializer | null; + } + + // BindingRestElement : + // `...` BindingIdentifier + // `...` BindingPattern + export interface BindingRestElement extends BaseParseNode { + readonly type: 'BindingRestElement'; + readonly BindingIdentifier?: BindingIdentifier; + readonly BindingPattern?: BindingPattern; + } + + // EmptyStatement : + // `;` + export interface EmptyStatement extends BaseParseNode { + readonly type: 'EmptyStatement'; + } + + // ExpressionStatement : + // [lookahead != `{`, `function`, `async` [no LineTerminator here] `function`, `class`, `let` `[` ] Expression `;` + export interface ExpressionStatement extends BaseParseNode { + readonly type: 'ExpressionStatement'; + readonly Expression: Expression; + } + + // IfStatement : + // `if` `(` Expression `)` Statement `else` Statement + // `if` `(` Expression `)` Statement [lookahead ≠ `else`] + export interface IfStatement extends BaseParseNode { + readonly type: 'IfStatement'; + readonly Expression: Expression; + readonly Statement_a: Statement; + readonly Statement_b: Statement; + } + + // IterationStatement : + // DoWhileStatement + // WhileStatement + // ForStatement + // ForInOfStatement + export type IterationStatement = + | DoWhileStatement + | WhileStatement + | ForStatement + | ForInOfStatement; + + // DoWhileStatement : + // `do` Statement `while` `(` Expression `)` `;` + export interface DoWhileStatement extends BaseParseNode { + readonly type: 'DoWhileStatement'; + readonly Statement: Statement; + readonly Expression: Expression; + } + + // WhileStatement : + // `while` `(` Expression `)` Statement + export interface WhileStatement extends BaseParseNode { + readonly type: 'WhileStatement'; + readonly Expression: Expression; + readonly Statement: Statement; + } + + // ForStatement : + // `for` `(` [lookahead != `let` `[`] Expression? `;` Expression? `;` Expression? `)` Statement + // `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement + // `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement + export interface ForStatement extends BaseParseNode { + readonly type: 'ForStatement'; + + /// ForStatement : `for` `(` [lookahead != `let` `[`] Expression? `;` Expression? `;` Expression? `)` Statement + // Expression_a?: Expression; + // Expression_b?: Expression; + // Expression_c?: Expression; + // Statement: Statement; + + /// ForStatement : `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement + // VariableDeclarationList: VariableDeclarationList; + // Expression_a?: Expression; + // Expression_b?: Expression; + // Statement: Statement; + + /// ForStatement : `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement + // LexicalDeclaration?: LexicalDeclarationLike; + // Expression_a?: Expression; + // Expression_b?: Expression; + // Statement: Statement; + + readonly VariableDeclarationList: VariableDeclarationList; + readonly LexicalDeclaration?: LexicalDeclarationLike; + readonly Expression_a?: Expression; + readonly Expression_b?: Expression; + readonly Expression_c?: Expression; + readonly Statement: Statement; + } + + // ForInOfStatement : + // `for` `(` [lookahead != `let` `[`] LeftHandSideExpression `in` Expression `)` Statement + // `for` `(` `var` ForBinding `in` Expression `)` Statement + // `for` `(` ForDeclaration `in` Expression `)` Statement + // `for` `(` [lookahead != { `let`, `async` `of` }] 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 + export type ForInOfStatement = + | ForInStatement + | ForOfStatement + | ForAwaitStatement; + + // ForInOfStatement (partial) : + // `for` `(` [lookahead != `let` `[`] LeftHandSideExpression `in` Expression `)` Statement + // `for` `(` `var` ForBinding `in` Expression `)` Statement + // `for` `(` ForDeclaration `in` Expression `)` Statement + export interface ForInStatement extends BaseParseNode { + readonly type: 'ForInStatement'; + + /// ForInStatement : `for` `(` [lookahead != `let` `[`] LeftHandSideExpression `in` Expression `)` Statement + // LeftHandSideExpression?: LeftHandSideExpression; + // Expression: Expression; + // Statement: Statement; + + /// ForInStatement : `for` `(` `var` ForBinding `in` Expression `)` Statement + // ForBinding?: ForBinding; + // Expression: Expression; + // Statement: Statement; + + /// ForInStatement : `for` `(` ForDeclaration `in` Expression `)` Statement + // ForDeclaration?: ForDeclarationLike; + // Expression: Expression; + // Statement: Statement; + + readonly LeftHandSideExpression?: LeftHandSideExpression; + readonly ForBinding?: ForBinding; + readonly ForDeclaration?: ForDeclarationLike; + readonly Expression: Expression; + readonly Statement: Statement; + } + + // ForInOfStatement (partial) : + // `for` `(` [lookahead != { `let`, `async` `of` }] LeftHandSideExpression `of` AssignmentExpression `)` Statement + // `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + // `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement + export interface ForOfStatement extends BaseParseNode { + readonly type: 'ForOfStatement'; + + /// ForOfStatement : `for` `(` [lookahead != { `let`, `async` `of` }] LeftHandSideExpression `of` AssignmentExpression `)` Statement + // LeftHandSideExpression?: LeftHandSideExpression; + // AssignmentExpression: AssignmentExpressionOrHigher; + // Statement: Statement; + + /// ForOfStatement : `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + // ForBinding?: ForBinding; + // AssignmentExpression: AssignmentExpressionOrHigher; + // Statement: Statement; + + /// ForOfStatement : `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement + // ForDeclaration?: ForDeclarationLike; + // AssignmentExpression: AssignmentExpressionOrHigher; + // Statement: Statement; + + readonly LeftHandSideExpression?: LeftHandSideExpression; + readonly ForDeclaration?: ForDeclarationLike; + readonly ForBinding?: ForBinding; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + readonly Statement: Statement; + } + + // ForInOfStatement (partial) : + // `for` `await` `(` [lookahead != `let`] LeftHandSideExpression `of` AssignmentExpression `)` Statement + // `for` `await` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + // `for` `await` `(` ForDeclaration `of` AssignmentExpression `)` Statement + export interface ForAwaitStatement extends BaseParseNode { + readonly type: 'ForAwaitStatement'; + + /// ForOfStatement : `for` `await` `(` [lookahead != `let`] LeftHandSideExpression `of` AssignmentExpression `)` Statement + // LeftHandSideExpression?: LeftHandSideExpression; + // AssignmentExpression: AssignmentExpressionOrHigher; + // Statement: Statement; + + /// ForOfStatement : `for` `await` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + // ForBinding?: ForBinding; + // AssignmentExpression: AssignmentExpressionOrHigher; + // Statement: Statement; + + /// ForOfStatement : `for` `await` `(` ForDeclaration `of` AssignmentExpression `)` Statement + // ForDeclaration?: ForDeclarationLike; + // AssignmentExpression: AssignmentExpressionOrHigher; + // Statement: Statement; + + readonly LeftHandSideExpression?: LeftHandSideExpression; + readonly ForDeclaration?: ForDeclarationLike; + readonly ForBinding?: ForBinding; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + readonly Statement: Statement; + } + + // ForDeclaration : + // LetOrConst ForBinding + export type ForDeclarationLike = + | ForDeclaration; + + // ForDeclaration : + // LetOrConst ForBinding + export interface ForDeclaration extends BaseParseNode { + readonly type: 'ForDeclaration'; + readonly LetOrConst: LetOrConst; + readonly ForBinding: ForBinding; + } + + // ForBinding : + // BindingPattern + // BindingIdentifier + export interface ForBinding extends BaseParseNode { + readonly type: 'ForBinding'; + readonly BindingIdentifier?: BindingIdentifier; + readonly BindingPattern?: BindingPattern; + } + + // ContinueStatement : + // `continue` `;` + // `continue` [no LineTerminator here] LabelIdentifier `;` + export interface ContinueStatement extends BaseParseNode { + readonly type: 'ContinueStatement'; + readonly LabelIdentifier: LabelIdentifier | null; + } + + // BreakStatement : + // `break` `;` + // `break` [no LineTerminator here] LabelIdentifier `;` + export interface BreakStatement extends BaseParseNode { + readonly type: 'BreakStatement'; + readonly LabelIdentifier: LabelIdentifier | null; + } + + // ReturnStatement : + // `return` `;` + // `return` [no LineTerminator here] Expression `;` + export interface ReturnStatement extends BaseParseNode { + readonly type: 'ReturnStatement'; + readonly Expression: Expression | null; + } + + // WithStatement : + // `with` `(` Expression `)` Statement + export interface WithStatement extends BaseParseNode { + readonly type: 'WithStatement'; + readonly Expression: Expression; + readonly Statement: Statement; + } + + // SwitchStatement : + // `switch` `(` Expression `)` CaseBlock + export interface SwitchStatement extends BaseParseNode { + readonly type: 'SwitchStatement'; + readonly Expression: Expression; + readonly CaseBlock: CaseBlock; + } + + // CaseBlock : + // `{` CaseClauses? `}` + // `{` CaseClauses? DefaultClause CaseClauses? `}` + export interface CaseBlock extends BaseParseNode { + readonly type: 'CaseBlock'; + readonly CaseClauses_a?: CaseClauses; + readonly DefaultClause?: DefaultClause; + readonly CaseClauses_b?: CaseClauses; + } + + // CaseClauses : + // CaseClause + // CaseClauses CauseClause + export type CaseClauses = readonly CaseClause[]; + + // CaseClause : + // `case` Expression `:` StatementList? + export interface CaseClause extends BaseParseNode { + readonly type: 'CaseClause'; + readonly Expression: Expression; + readonly StatementList: StatementList; + } + + // DefaultClause : + // `default` `:` StatementList? + export interface DefaultClause extends BaseParseNode { + readonly type: 'DefaultClause'; + readonly StatementList: StatementList; + } + + // LabelledStatement : + // LabelIdentifier `:` LabelledItem + export interface LabelledStatement extends BaseParseNode { + readonly type: 'LabelledStatement'; + readonly LabelIdentifier: LabelIdentifier; + readonly LabelledItem: LabelledItem; + } + + // LabelledItem : + // Statement + // FunctionDeclaration + export type LabelledItem = + | Statement + | FunctionDeclaration; // SPEC QUESTION: why only |FunctionDeclaration| and not generators or async functions? + + // ThrowStatement : + // `throw` [no LineTerminator here] Expression `;` + export interface ThrowStatement extends BaseParseNode { + readonly type: 'ThrowStatement'; + readonly Expression: Expression; + } + + // TryStatement : + // `try` Block Catch + // `try` Block Finally + // `try` Block Catch Finally + export interface TryStatement extends BaseParseNode { + readonly type: 'TryStatement'; + readonly Block: Block; + readonly Catch: Catch | null; + readonly Finally: Finally | null; + } + + // Catch : + // `catch` `(` CatchParameter `)` Block + // `catch` Block + // + // CatchParameter : + // BindingIdentifier + // BindingPattern + export interface Catch extends BaseParseNode { + readonly type: 'Catch'; + readonly CatchParameter: CatchParameter | null; + readonly Block: Block; + } + + // Finally : + // `finally` Block + export type Finally = + | Block; + + // CatchParameter : + // BindingPattern + // BindingIdentifier + export type CatchParameter = + | BindingPattern + | BindingIdentifier; + + // DebuggerStatement : + // `debugger` `;` + export interface DebuggerStatement extends BaseParseNode { + readonly type: 'DebuggerStatement'; + } + + // A.4 Functions and Classes + // https://tc39.es/ecma262/#sec-functions-and-classes + + // UniqueFormalParameters : + // FormalParameters + export type UniqueFormalParameters = + | FormalParameters; + + // FormalParameters : + // [empty] + // FunctionRestParameter + // FormalParameterList + // FormalParameterList `,` + // FormalParameterList `,` FunctionRestParameter + export type FormalParameters = readonly FormalParametersElement[]; + + // NON-SPEC + export type FormalParametersElement = FormalParameterList[number] | FunctionRestParameter; + + // FormalParameterList : + // FormalParameter + // FormalParameterList `,` FormalParameterList + export type FormalParameterList = readonly FormalParameter[]; + + // FunctionRestParameter : + // BindingRestElement + export type FunctionRestParameter = + | BindingRestElement; + + // FormalParameter : + // BindingElement + export type FormalParameter = + | BindingElementLike; + + // NON-SPEC + export type FunctionLike = + | FunctionDeclarationLike + | FunctionExpressionLike; + + // NON-SPEC + export type FunctionDeclarationLike = + | FunctionDeclaration + | GeneratorDeclaration + | AsyncFunctionDeclaration + | AsyncGeneratorDeclaration; + + // NON-SPEC + export type FunctionExpressionLike = + | FunctionExpression + | GeneratorExpression + | AsyncFunctionExpression + | AsyncGeneratorExpression; + + // NON-SPEC + export type FunctionBodyLike = + | FunctionBody + | GeneratorBody + | AsyncBody + | AsyncGeneratorBody; + + // FunctionDeclaration : + // `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` + // [+Default] `function` `(` FormalParameters `)` `{` FunctionBody `}` + export interface FunctionDeclaration extends BaseParseNode { + readonly type: 'FunctionDeclaration'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly FunctionBody: FunctionBody; + } + + // FunctionExpression : + // `function` BindingIdentifier? `(` FormalParameters `)` `{` FunctionBody `}` + export interface FunctionExpression extends BaseParseNode { + readonly type: 'FunctionExpression'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly FunctionBody: FunctionBody; + } + + // FunctionBody : + // FunctionStatementList + export interface FunctionBody extends BaseParseNode { + readonly type: 'FunctionBody'; + readonly directives: string[]; + readonly strict: boolean; + readonly FunctionStatementList: FunctionStatementList; + } + + // FunctionStatementList : + // StatementList + export type FunctionStatementList = StatementList; + + // ArrowFunction : + // ArrowParameters [no LineTerminator here] `=>` ConciseBody + export interface ArrowFunction extends BaseParseNode { + readonly type: 'ArrowFunction'; + readonly ArrowParameters: ArrowParameters; + readonly ConciseBody: ConciseBodyLike; + } + + // ArrowParameters : + // BindingIdentifier + // CoverParenthesizedExpressionAndArrowParameterList + // + // CoverParenthesizedExpressionAndArrowParameterList refined as: + // ArrowFormalParameters (refined) : + // `(` UniqueFormalParameters `)` + export type ArrowParameters = ArrowFormalParameters; + + // ConciseBody : + // ExpressionBody + // `{` FunctionBody `}` + export type ConciseBodyLike = + | FunctionBody + | ConciseBody; + + // ConciseBody (partial) : + // ExpressionBody + export interface ConciseBody extends BaseParseNode { + readonly type: 'ConciseBody'; + readonly directives?: undefined; + readonly ExpressionBody: ExpressionBody; + } + + // ExpressionBody : + // AssignmentExpression + export interface ExpressionBody extends BaseParseNode { + readonly type: 'ExpressionBody'; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + } + + // CoverParenthesizedExpressionAndArrowParameterList refined as: + // ArrowFormalParameters : + // `(` UniqueFormalParameters `)` + export type ArrowFormalParameters = + | UniqueFormalParameters; + + // AsyncArrowFunction : + // `async` AsyncArrowBindingIdentifier `=>` AsyncConciseBody + // CoverCallExpressionAndAsyncArrowHead `=>` AsyncConciseBody + // + // CoverCallExpressionAndAsyncArrowHead : + // MemberExpression Arguments + // + // AsyncArrowHead (refined) : + // `async` ArrowFormalParameters + export interface AsyncArrowFunction extends BaseParseNode { + readonly type: 'AsyncArrowFunction'; + readonly ArrowParameters: ArrowParameters; + readonly AsyncConciseBody: AsyncConciseBodyLike; + } + + // AsyncConciseBody : + // ExpressionBody + // `{` AsyncBody `}` + export type AsyncConciseBodyLike = + | AsyncConciseBody + | AsyncBody; + + // AsyncConciseBody (partial) : + // ExpressionBody + export interface AsyncConciseBody extends BaseParseNode { + readonly type: 'AsyncConciseBody'; + readonly directives?: undefined; + readonly ExpressionBody: ExpressionBody; + } + + // MethodDefinition : + // ClassElementName `(` UniqueFormalParameters `)` `{` FunctionBody `}` + // GeneratorMethod + // AsyncMethod + // AsyncGeneratorMethod + // `get` ClassElementName `(` `)` `{` FunctionBody `}` + // `set` ClassElementName `(` PropertySetParameterList `)` `{` FunctionBody `}` + export type MethodDefinitionLike = + | MethodDefinition + | GeneratorMethod + | AsyncMethod + | AsyncGeneratorMethod; + + // MethodDefinition (partial) : + // ClassElementName `(` UniqueFormalParameters `)` `{` FunctionBody `}` + // `get` ClassElementName `(` `)` `{` FunctionBody `}` + // `set` ClassElementName `(` PropertySetParameterList `)` `{` FunctionBody `}` + export interface MethodDefinition extends BaseParseNode { + readonly type: 'MethodDefinition'; + readonly Decorators?: readonly Decorator[] | null; + readonly static?: boolean; + readonly ClassElementName: ClassElementName; + readonly PropertySetParameterList: PropertySetParameterList | null; + readonly UniqueFormalParameters: UniqueFormalParameters | null; + readonly FunctionBody: FunctionBody; + } + + // PropertySetParameterList : + // FormalParameter + export type PropertySetParameterList = [FormalParameter]; + + // GeneratorDeclaration : + // `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` + // [+Default] `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` + export interface GeneratorDeclaration extends BaseParseNode { + readonly type: 'GeneratorDeclaration'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly GeneratorBody: GeneratorBody; + } + + // GeneratorExpression : + // `function` `*` BindingIdentifier? `(` FormalParameters `)` `{` GeneratorBody `}` + export interface GeneratorExpression extends BaseParseNode { + readonly type: 'GeneratorExpression'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly GeneratorBody: GeneratorBody; + } + + // GeneratorMethod : + // `*` ClassElementName `(` UniqueFormalParameters `)` `{` GeneratorBody `}` + export interface GeneratorMethod extends BaseParseNode { + readonly type: 'GeneratorMethod'; + readonly Decorators?: readonly Decorator[] | null; + readonly static?: boolean; + readonly ClassElementName: ClassElementName; + readonly PropertySetParameterList: null; + readonly UniqueFormalParameters: UniqueFormalParameters; + readonly GeneratorBody: GeneratorBody; + } + + // GeneratorBody : + // FunctionBody + export interface GeneratorBody extends BaseParseNode { + readonly type: 'GeneratorBody'; + readonly directives: string[]; + readonly strict: boolean; + readonly FunctionStatementList: FunctionStatementList; + } + + // YieldExpression : + // `yield` + // `yield` [no LineTerminator here] AssignmentExpression + // `yield` [no LineTerminator here] `*` AssignmentExpression + export interface YieldExpression extends BaseParseNode { + readonly type: 'YieldExpression'; + readonly hasStar: boolean; + readonly AssignmentExpression: AssignmentExpressionOrHigher | null; + } + + // AsyncGeneratorDeclaration : + // `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + // [+Default] `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + export interface AsyncGeneratorDeclaration extends BaseParseNode { + readonly type: 'AsyncGeneratorDeclaration'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly AsyncGeneratorBody: AsyncGeneratorBody; + } + + // AsyncGeneratorExpression : + // `async` `function` `*` BindingIdentifier? `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + export interface AsyncGeneratorExpression extends BaseParseNode { + readonly type: 'AsyncGeneratorExpression'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly AsyncGeneratorBody: AsyncGeneratorBody; + } + + // AsyncGeneratorMethod : + // `async` `*` ClassElementName `(` UniqueFormalParameters `)` `{` AsyncGeneratorBody `}` + export interface AsyncGeneratorMethod extends BaseParseNode { + readonly type: 'AsyncGeneratorMethod'; + readonly Decorators?: readonly Decorator[] | null; + readonly static?: boolean; + readonly ClassElementName: ClassElementName; + readonly PropertySetParameterList: null; + readonly UniqueFormalParameters: UniqueFormalParameters; + readonly AsyncGeneratorBody: AsyncGeneratorBody; + } + + // AsyncGeneratorBody : + // FunctionBody + export interface AsyncGeneratorBody extends BaseParseNode { + readonly type: 'AsyncGeneratorBody'; + readonly directives: string[]; + readonly strict: boolean; + readonly FunctionStatementList: FunctionStatementList; + } + + // AsyncFunctionDeclaration : + // `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncBody `}` + // [+Default] `async` `function` `*` `(` FormalParameters `)` `{` AsyncBody `}` + export interface AsyncFunctionDeclaration extends BaseParseNode { + readonly type: 'AsyncFunctionDeclaration'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly AsyncBody: AsyncBody; + } + + // AsyncFunctionExpression : + // `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncBody `}` + export interface AsyncFunctionExpression extends BaseParseNode { + readonly type: 'AsyncFunctionExpression'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly FormalParameters: FormalParameters; + readonly AsyncBody: AsyncBody; + } + + // AsyncMethod : + // `async` ClassElementName `(` UniqueFormalParameters `)` `{` AsyncBody `}` + export interface AsyncMethod extends BaseParseNode { + readonly type: 'AsyncMethod'; + readonly Decorators?: readonly Decorator[] | null; + readonly static?: boolean; + readonly ClassElementName: ClassElementName; + readonly PropertySetParameterList: null; + readonly UniqueFormalParameters: UniqueFormalParameters; + readonly AsyncBody: AsyncBody; + } + + // AsyncBody : + // FunctionBody + export interface AsyncBody extends BaseParseNode { + readonly type: 'AsyncBody'; + readonly directives: string[]; + readonly strict: boolean; + readonly FunctionStatementList: FunctionStatementList; + } + + // AwaitExpression : `await` UnaryExpression + export interface AwaitExpression extends BaseParseNode { + readonly type: 'AwaitExpression'; + readonly UnaryExpression: UnaryExpressionOrHigher; + } + + // pending + + // NON-SPEC + export type ClassLike = + | ClassDeclaration + | ClassExpression; + + // Decorator[Yield, Await] : + // @ DecoratorMemberExpression (subset of MemberExpression that only allows identifiers, e.g. A.B, no this.y or x[y]) + // @ DecoratorParenthesizedExpression : `(` Expression[+In] `)` + // @ DecoratorCallExpression : DecoratorMemberExpression Arguments + export type Decorator = Decorator_MemberExpression | Decorator_ParenthesizedExpression | Decorator_CallExpression; + export interface Decorator_MemberExpression extends BaseParseNode { + readonly type: 'Decorator'; + readonly subtype: 'MemberExpression'; + readonly MemberExpression: MemberExpression | IdentifierReference; + readonly ParenthesizedExpression?: undefined; + readonly CallExpression?: undefined; + } + export interface Decorator_ParenthesizedExpression extends BaseParseNode { + readonly type: 'Decorator'; + readonly subtype: 'ParenthesizedExpression'; + readonly ParenthesizedExpression: Expression; + readonly MemberExpression?: undefined; + readonly CallExpression?: undefined; + } + export interface Decorator_CallExpression extends BaseParseNode { + readonly type: 'Decorator'; + readonly subtype: 'CallExpression'; + readonly CallExpression: CallExpression; + readonly MemberExpression?: undefined; + readonly ParenthesizedExpression?: undefined; + } + + // ClassDeclaration : + // DecoratorList? `class` BindingIdentifier ClassTail + // DecoratorList? [+Default] `class` ClassTail + export interface ClassDeclaration extends BaseParseNode { + readonly Decorators?: readonly Decorator[] | null; + readonly type: 'ClassDeclaration'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly ClassTail: ClassTail; + } + + // ClassExpression : + // DecoratorList? `class` BindingIdentifier? ClassTail + export interface ClassExpression extends BaseParseNode { + readonly Decorators?: readonly Decorator[] | null; + readonly type: 'ClassExpression'; + readonly BindingIdentifier: BindingIdentifier | null; + readonly ClassTail: ClassTail; + } + + // ClassTail : + // ClassHeritage? `{` ClassBody? `}` + export interface ClassTail extends BaseParseNode { + readonly type: 'ClassTail'; + readonly ClassHeritage: ClassHeritage | null; + readonly ClassBody: ClassBody | null; + } + + // ClassHeritage : + // `extends` LeftHandSideExpression + export type ClassHeritage = + | LeftHandSideExpression; + + // ClassBody : + // ClassElementList + export type ClassBody = + | ClassElementList; + + // ClassElementList : + // ClassElement + // ClassElementList ClassElement + export type ClassElementList = readonly ClassElement[]; + + // ClassElement : + // DecoratorList? MethodDefinition + // DecoratorList? `static` MethodDefinition + // DecoratorList? FieldDefinition `;` + // DecoratorList? `static` FieldDefinition `;` + // ClassStaticBlock + // `;` + export type ClassElement = + | MethodDefinitionLike + | FieldDefinition + | ClassStaticBlock; + + // FieldDefinition : + // ClassElementName Initializer? + // accessor [nLTh] ClassElementName Initializer? + export interface FieldDefinition extends BaseParseNode { + readonly Decorators?: readonly Decorator[] | null; + readonly accessor?: boolean; + readonly type: 'FieldDefinition'; + readonly static?: boolean; + readonly ClassElementName: ClassElementName; + readonly Initializer: Initializer | null; + } + + // ClassElementName : + // PropertyName + // PrivateIdentifier + export type ClassElementName = + | PropertyNameLike + | PrivateIdentifier; + + // ClassStaticBlock : + // `static` `{` ClassStaticBlockBody `}` + export interface ClassStaticBlock extends BaseParseNode { + readonly type: 'ClassStaticBlock'; + readonly static: true; + readonly ClassStaticBlockBody: ClassStaticBlockBody; + } + + // ClassStaticBlockBody : + // ClassStaticBlockStatementList + export interface ClassStaticBlockBody extends BaseParseNode { + readonly type: 'ClassStaticBlockBody'; + readonly ClassStaticBlockStatementList: ClassStaticBlockStatementList; + } + + // ClassStaticBlockStatementList : + // StatementList? + export type ClassStaticBlockStatementList = + | StatementList; + + + // A.5 Scripts and Modules + // https://tc39.es/ecma262/#sec-scripts-and-modules + + // Script : + // ScriptBody? + export interface Script extends BaseParseNode { + readonly type: 'Script'; + readonly ScriptBody: ScriptBody | null; + } + + // ScriptBody : + // StatementList + export interface ScriptBody extends BaseParseNode { + readonly type: 'ScriptBody'; + readonly StatementList: StatementList; + } + + // Module : + // ModuleBody? + export interface Module extends BaseParseNode { + readonly type: 'Module'; + readonly ModuleBody: ModuleBody | null; + readonly hasTopLevelAwait: boolean; + } + + // ModuleBody : + // ModuleItemList + export interface ModuleBody extends BaseParseNode { + readonly type: 'ModuleBody'; + readonly ModuleItemList: ModuleItemList; + } + + // ModuleItemList : + // ModuleItem + // ModuleItemList ModuleItem + export type ModuleItemList = readonly ModuleItem[]; + + // ModuleItem : + // ImportDeclaration + // ExportDeclaration + // StatementListItem + export type ModuleItem = + | ImportDeclaration + | ExportDeclaration + | StatementListItem; + + // ModuleExportName : + // IdentifierName + // StringLiteral + export type ModuleExportName = + | IdentifierName + | StringLiteral; + + // ImportDeclaration : + // `import` ImportClause FromClause WithClause? `;` + // `import` ModuleSpecifier WithClause? `;` + export interface ImportDeclaration extends BaseParseNode { + readonly type: 'ImportDeclaration'; + readonly ModuleSpecifier?: PrimaryExpression; + readonly Phase: 'defer' | 'evaluation'; + readonly ImportClause?: ImportClause; + readonly FromClause?: FromClause; + readonly WithClause?: WithClause; + } + + // ImportClause : + // ImportedDefaultBinding + // NameSpaceImport + // NamedImports + // ImportedDefaultBinding `,` NameSpaceImport + // ImportedDefaultBinding `,` NamedImports + export interface ImportClause extends BaseParseNode { + readonly type: 'ImportClause'; + readonly ImportedDefaultBinding?: ImportedDefaultBinding; + readonly NameSpaceImport?: NameSpaceImport; + readonly NamedImports?: NamedImports; + } + + // ImportedDefaultBinding : + // ImportedBinding + export interface ImportedDefaultBinding extends BaseParseNode { + readonly type: 'ImportedDefaultBinding'; + readonly ImportedBinding: ImportedBinding; + } + + // NameSpaceImport : + // `*` `as` ImportedBinding + export interface NameSpaceImport extends BaseParseNode { + readonly type: 'NameSpaceImport'; + readonly ImportedBinding: ImportedBinding; + } + + // NamedImports : + // `{` `}` + // `{` ImportsList `}` + // `{` ImportsList `,` `}` + export interface NamedImports extends BaseParseNode { + readonly type: 'NamedImports'; + readonly ImportsList: ImportsList; + } + + // FromClause : + // `from` ModuleSpecifier + export type FromClause = + | ModuleSpecifier; + + // ImportsList : + // ImportSpecifier + // ImportsList `,` ImportSpecifier + export type ImportsList = readonly ImportSpecifier[]; + + // ImportSpecifier : + // ImportedBinding + // ModuleExportName `as` ImportedBinding + export interface ImportSpecifier extends BaseParseNode { + readonly type: 'ImportSpecifier'; + readonly ModuleExportName?: ModuleExportName; + readonly ImportedBinding: ImportedBinding; + } + + // ModuleSpecifier : + // StringLiteral + export type ModuleSpecifier = + | StringLiteral; + + // ImportedBinding : + // BindingIdentifier + export type ImportedBinding = + | BindingIdentifier; + + // WithClause : + // `with` `{` `}` + // `with` `{` WithEntries `,`? `}` + export interface WithClause extends BaseParseNode { + readonly type: 'WithClause'; + readonly WithEntries: WithEntries; + } + + // WithEntries : + // AttributeKey `:` StringLiteral + // AttributeKey `:` StringLiteral `,` WithEntries + export type WithEntries = readonly WithEntry[]; + export interface WithEntry extends BaseParseNode { + readonly type: 'WithEntry'; + readonly AttributeKey: AttributeKey; + readonly AttributeValue: StringLiteral; + } + + // AttributeKey : + // IdentifierName + // StringLiteral + export type AttributeKey = + | IdentifierName + | StringLiteral; + + export type ExportDeclaration = + | ExportDeclaration_Declaration + | ExportDeclaration_DefaultClass + | ExportDeclaration_DefaultDeclaration + | ExportDeclaration_DefaultExpression + | ExportDeclaration_NamedExports + | ExportDeclaration_NamedFrom + | ExportDeclaration_VariableStatement; + + // `export` ExportFromClause FromClause WithClause?; + export interface ExportDeclaration_NamedFrom extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly ExportFromClause: ExportFromClauseLike; + readonly FromClause: FromClause; + readonly WithClause: undefined | WithClause; + + readonly AssignmentExpression?: undefined; + readonly ClassDeclaration?: undefined; + readonly Declaration?: null; + readonly Decorators?: null; + readonly default?: boolean; + readonly HoistableDeclaration?: undefined; + readonly NamedExports?: undefined; + readonly VariableStatement?: undefined; + } + + // `export` NamedExports `;` + export interface ExportDeclaration_NamedExports extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly NamedExports: NamedExports; + + readonly AssignmentExpression?: undefined; + readonly ClassDeclaration?: undefined; + readonly Declaration?: null; + readonly Decorators?: null; + readonly default?: boolean; + readonly ExportFromClause?: undefined; + readonly FromClause?: undefined; + readonly HoistableDeclaration?: undefined; + readonly VariableStatement?: undefined; + readonly WithClause?: undefined; + } + + // `export` VariableStatement + export interface ExportDeclaration_VariableStatement extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly VariableStatement: VariableStatement; + + readonly AssignmentExpression?: undefined; + readonly ClassDeclaration?: undefined; + readonly Declaration?: null; + readonly Decorators?: null; + readonly default?: boolean; + readonly ExportFromClause?: undefined; + readonly FromClause?: undefined; + readonly HoistableDeclaration?: undefined; + readonly NamedExports?: undefined; + readonly WithClause?: undefined; + } + + // DecoratorList? `export` Declaration + export interface ExportDeclaration_Declaration extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly Decorators: readonly Decorator[] | null; + readonly Declaration: Declaration; + + readonly AssignmentExpression?: undefined; + readonly ClassDeclaration?: undefined; + readonly default?: boolean; + readonly ExportFromClause?: undefined; + readonly FromClause?: undefined; + readonly HoistableDeclaration?: undefined; + readonly NamedExports?: undefined; + readonly VariableStatement?: undefined; + readonly WithClause?: undefined; + } + + // `export` `default` HoistableDeclaration + export interface ExportDeclaration_DefaultDeclaration extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly default: true; + readonly HoistableDeclaration: HoistableDeclaration; + + readonly AssignmentExpression?: undefined; + readonly ClassDeclaration?: undefined; + readonly Declaration?: null; + readonly Decorators?: null; + readonly ExportFromClause?: undefined; + readonly FromClause?: undefined; + readonly NamedExports?: undefined; + readonly VariableStatement?: undefined; + readonly WithClause?: undefined; + } + + // DecoratorList? `export` `default` ClassDeclaration + export interface ExportDeclaration_DefaultClass extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly Decorators: readonly Decorator[] | null; + readonly default: true; + readonly ClassDeclaration: ClassDeclaration; + + readonly AssignmentExpression?: undefined; + readonly Declaration?: null; + readonly ExportFromClause?: undefined; + readonly FromClause?: undefined; + readonly HoistableDeclaration?: undefined; + readonly NamedExports?: undefined; + readonly VariableStatement?: undefined; + readonly WithClause?: undefined; + } + + // `export` `default` AssignmentExpression `;` + export interface ExportDeclaration_DefaultExpression extends BaseParseNode { + readonly type: 'ExportDeclaration'; + readonly default: true; + readonly AssignmentExpression: AssignmentExpressionOrHigher; + + readonly ClassDeclaration?: undefined; + readonly Declaration?: null; + readonly Decorators?: null; + readonly ExportFromClause?: undefined; + readonly FromClause?: undefined; + readonly HoistableDeclaration?: undefined; + readonly NamedExports?: undefined; + readonly VariableStatement?: undefined; + readonly WithClause?: undefined; + } + + // ExportFromClause : + // `*` + // `*` as ModuleExportName + // NamedExports + export type ExportFromClauseLike = + | NamedExports + | ExportFromClause; + + // ExportFromClause (partial) : + // `*` + // `*` as ModuleExportName + export interface ExportFromClause extends BaseParseNode { + readonly type: 'ExportFromClause'; + readonly ModuleExportName?: ModuleExportName; + } + + // NamedExports : + // `{` `}` + // `{` ExportsList `}` + // `{` ExportsList `,` `}` + export interface NamedExports extends BaseParseNode { + readonly type: 'NamedExports'; + readonly ExportsList: ExportsList; + } + + // ExportsList : + // ExportSpecifier + // ExportsList `,` ExportSpecifier + export type ExportsList = readonly ExportSpecifier[]; + + // ExportSpecifier : + // ModuleExportName + // ModuleExportName `as` ModuleExportName + export interface ExportSpecifier extends BaseParseNode { + readonly type: 'ExportSpecifier'; + readonly localName: ModuleExportName; + readonly exportName: ModuleExportName; + } + + export type AssignmentPattern = ObjectAssignmentPattern | ArrayAssignmentPattern | AssignmentProperty | AssignmentElement | ParseNode.Elision; + export type ObjectAssignmentPattern = { + type: 'ObjectAssignmentPattern'; + AssignmentPropertyList: (AssignmentProperty | AssignmentPattern)[]; + AssignmentRestProperty: AssignmentRestProperty | undefined; + } + export type AssignmentProperty = { + type: 'AssignmentProperty'; + IdentifierReference: ParseNode.IdentifierReference; + Initializer?: ParseNode.Initializer | null | undefined; + } | { + type: 'AssignmentProperty'; + PropertyName: ParseNode.PropertyNameLike | null; + AssignmentElement: AssignmentElement; + } + export type AssignmentElement = { + type: 'AssignmentElement'; + DestructuringAssignmentTarget: ParseNode.AssignmentExpressionOrHigher; + Initializer: ParseNode.Initializer | undefined | null; + } + + export type ArrayAssignmentPattern = { + type: 'ArrayAssignmentPattern'; + AssignmentElementList: AssignmentElisionElement[]; + AssignmentRestElement: AssignmentRestElement | undefined; + } + export type AssignmentElisionElement = ParseNode.Elision | AssignmentElement | AssignmentPattern; + export type AssignmentRestProperty = { + type: 'AssignmentRestProperty'; + DestructuringAssignmentTarget: ParseNode.AssignmentExpressionOrHigher; + } + + // Helpers + // NON-SPEC + + // Gets all keys of all constituents of a union + type AllKeysOf = T extends unknown ? keyof T : never; + + // Gets all values for a given key for all constituents of a union + type AllValuesOf> = T extends unknown ? K extends keyof T ? T[K] : never : never; + + // NON-SPEC + /** + * Used internally to describe a node that is still in the process of being parsed. Unfinished nodes may not yet be + * fully defined. + */ + export type Unfinished = ( + // An unfinished node... + + // ...includes all properties of BaseParseNode + & { + type?: T['type'] & ParseNode['type']; + location: { + startIndex: number; + endIndex: number; + start: { line: number, column: number }; + end: { line: number, column: number }; + }; + strict: boolean; + sourceText: string; + } + + // ...includes all properties of all potential types, with each property marked as optional + & { + -readonly [K in Exclude, 'location' | 'strict' | 'sourceText'>]?: AllValuesOf; + } + ); + + // NON-SPEC + /** + * Used internally to indicate a node that has finished parsing. + */ + export type Finished, K extends T['type'] & ParseNode['type']> = + T extends Unfinished ? Extract : + T; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ParseNode { + export type WithStatementListChild = + | ModuleBody + | ScriptBody + | Block + | CaseClause + | DefaultClause + | ClassStaticBlockBody + | FunctionBody + | GeneratorBody + | AsyncBody + | AsyncGeneratorBody; +} + +/** https://tc39.es/ecma262/multipage/text-processing.html#sec-patterns */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ParseNode.RegExp { + export interface NodeWithPosition { + readonly position: number; + } + export interface Pattern { + readonly type: 'Pattern'; + readonly Disjunction: Disjunction; + readonly capturingGroups: readonly { readonly GroupName: string | undefined, readonly position: number }[]; + } + export interface Disjunction { + readonly type: 'Disjunction'; + readonly Alternative: Alternative; + readonly Disjunction: Disjunction | undefined; + } + export interface Alternative { + readonly type: 'Alternative'; + readonly Term: readonly Term[]; + } + export type Term = Term_Assertion | Term_Atom; + export interface Term_Assertion { + readonly type: 'Term'; + readonly production: 'Assertion'; + readonly Assertion: Assertion; + } + export interface Term_Atom { + readonly type: 'Term'; + readonly production: 'Atom'; + readonly Atom: Atom; + readonly Quantifier: Quantifier | undefined; + readonly leftCapturingParenthesesBefore: number; + readonly capturingParenthesesWithin: number; + } + export type Assertion = Assertion_Plain | Assertion_LookaheadOrLookbehind; + export interface Assertion_Plain { + readonly type: 'Assertion'; + readonly production: '^' | '$' | 'b' | 'B' | 'A' | 'z'; + } + export interface Assertion_LookaheadOrLookbehind { + readonly type: 'Assertion'; + readonly production: '?=' | '?!' | '?<=' | '?` which is often + * more expensive and can complicate assignability checks. + */ +export type ParseNodesByType = { + [N in ParseNode as N['type']]: N; +}; diff --git a/src/parser/Parser.mts b/src/parser/Parser.mts new file mode 100644 index 0000000..1bdbbf1 --- /dev/null +++ b/src/parser/Parser.mts @@ -0,0 +1,201 @@ +import { surroundingAgent, type Feature } from '../host-defined/engine.mts'; +import * as messages from '../messages.mts'; +import { LanguageParser } from './LanguageParser.mts'; +import { isLineTerminator, type Locatable } from './Lexer.mts'; +import type { + Location, + ParseNode, + ParseNodesByType, + Position, +} from './ParseNode.mts'; +import { Scope } from './Scope.mts'; +import { Token } from './tokens.mts'; + +export interface ParserOptions { + readonly source: string; + readonly specifier?: string; + readonly json?: boolean; + readonly allowAllPrivateNames?: boolean; +} + +export class Parser extends LanguageParser { + protected readonly source: string; + + protected readonly specifier?: string; + + /** @deprecated migrating to earlyErrors2... */ + readonly earlyErrors: Set; + + readonly state: { + hasTopLevelAwait: boolean; + strict: boolean; + json: boolean; + allowAllPrivateNames: boolean; + }; + + readonly scope = new Scope(this); + + constructor({ + source, specifier, json = false, allowAllPrivateNames = false, + }: ParserOptions) { + super(); + this.source = source; + this.specifier = specifier; + this.earlyErrors = new Set(); + this.state = { + hasTopLevelAwait: false, + strict: false, + json, + allowAllPrivateNames, + }; + } + + isStrictMode() { + return this.state.strict; + } + + feature(name: Feature) { + return surroundingAgent.feature(name); + } + + startNode(inheritStart?: ParseNode.BaseParseNode): ParseNode.Unfinished; + + startNode(inheritStart?: ParseNode.BaseParseNode): ParseNode.Unfinished { + this.peek(); + const s = this.source; + const node: ParseNode.BaseParseNode = { + type: undefined!, + parent: 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, + get sourceText() { + return s.slice(node.location.startIndex, node.location.endIndex); + }, + }; + return node; + } + + markNodeStart(node: ParseNode.Unfinished) { + node.location.startIndex = this.peekToken.startIndex; + node.location.start = { + line: this.peekToken.line, + column: this.peekToken.column, + }; + } + + finishNode(node: T, type: K): ParseNodesByType[K]; + + finishNode(node: ParseNode.Unfinished, type: ParseNode['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: number | Locatable = this.peek(), template: K, templateArgs: Parameters): SyntaxError { + if (template === 'UnexpectedToken' && typeof context !== 'number' && 'type' in context && context.type === Token.EOS) { + return this.createSyntaxError(context, '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 ('type' in context && 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 ('location' in context && context.location) { + context = context.location; + } + ({ + startIndex, + endIndex, + start: { + line, + column, + } = context as Position, // NOTE: unsound cast + } = context as Location); // NOTE: unsound cast + } + + /* + * 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 message = messages[template] as (...args: Parameters) => string; + const e = new SyntaxError(message(...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: K, context?: number | Locatable, ...templateArgs: Parameters) { + const e = this.createSyntaxError(context, template, templateArgs); + this.earlyErrors.add(e); + return e; + } + + raise(template: K, context?: number | Locatable, ...templateArgs: Parameters): never { + const e = this.createSyntaxError(context, template, templateArgs); + throw e; + } + + unexpected(...args: [(number | Locatable)?, ...Parameters]) { + return this.raise('UnexpectedToken', ...args); + } +} diff --git a/src/parser/RegExpParser.mts b/src/parser/RegExpParser.mts new file mode 100644 index 0000000..4fbb3a0 --- /dev/null +++ b/src/parser/RegExpParser.mts @@ -0,0 +1,1424 @@ +import { + Table70_BinaryUnicodeProperties, + Table69_NonbinaryUnicodeProperties, + type UnicodeCharacter, + CountLeftCapturingParensWithin, + type Character, + IsCharacterClass, + type CodePoint, + Table71_BinaryPropertyOfStrings, + isLeadingSurrogate, + isTrailingSurrogate, +} from '../runtime-semantics/all.mts'; +import { + CharacterValue, + UTF16SurrogatePairToCodePoint, + type CharacterValueAcceptNode, +} from '../static-semantics/all.mts'; +// @ts-ignore +import PropertyValueAliases from '../unicode/PropertyValueAliases.json' with { type: 'json' }; +import { __ts_cast__, unreachable } from '../helpers.mts'; +import { + isIdentifierStart, + isIdentifierPart, + isHexDigit, +} from './Lexer.mts'; +import type { ParseNode } from './ParseNode.mts'; +import { Assert, surroundingAgent, type Mutable } from '#self'; + +export const isSyntaxCharacter = (c: string) => '^$\\.*+?()[]{}|'.includes(c); +const isClosingSyntaxCharacter = (c: string) => ')]}|'.includes(c); +const isDecimalDigit = (c: string) => /[0123456789]/u.test(c); +const isControlLetter = (c: string) => /[a-zA-Z]/u.test(c); +const isIdentifierContinue = (c: string) => c && /\p{ID_Continue}/u.test(c); +/** https://tc39.es/ecma262/#table-controlescape-code-point-values */ +export const isControlEscape = (c: CodePoint) => c >= 9 && c <= 13; +export const isAsciiLetter = (c: CodePoint) => (c >= 65 && c <= 90) || (c >= 97 && c <= 122); + +enum ParserContext { + None = 0, + UnicodeMode = 1 << 0, + NamedCaptureGroups = 1 << 1, + UnicodeSetMode = 1 << 2, +} + +export interface RegExpParserContext { UnicodeMode?: boolean; NamedCaptureGroups?: boolean; UnicodeSetsMode?: boolean; } +export class RegExpParser { + private source: string; + + private position = 0; + + get debug() { + return `${this.source.slice(0, this.position)}👀${this.source.slice(this.position)}`; + } + + private capturingGroups: Mutable = []; + + private leftCapturingParenthesesBefore = 0; + + private decimalEscapes: { readonly value: number, readonly position: number }[] = []; + + private groupNameRefs: ParseNode.RegExp.AtomEscape_CaptureGroupName[] = []; + + private groupNameThatMatches: Record = Object.create(null); + + private getAllGroupsWithName(name: string) { + this.groupNameThatMatches[name] ??= []; + return this.groupNameThatMatches[name]; + } + + private state = ParserContext.None; + + constructor(source: string) { + this.source = source; + } + + scope(flags: RegExpParserContext, f: () => T): T { + const oldState = this.state; + + if (flags.UnicodeMode === true) { + this.state |= ParserContext.UnicodeMode; + } else if (flags.UnicodeMode === false) { + this.state &= ~ParserContext.UnicodeMode; + } + + if (flags.NamedCaptureGroups === true) { + this.state |= ParserContext.NamedCaptureGroups; + } else if (flags.NamedCaptureGroups === false) { + this.state &= ~ParserContext.NamedCaptureGroups; + } + + if (flags.UnicodeSetsMode === true) { + this.state |= ParserContext.UnicodeSetMode; + } else if (flags.UnicodeSetsMode === false) { + this.state &= ~ParserContext.UnicodeSetMode; + } + + const r = f(); + + this.state = oldState; + + return r; + } + + private get inUnicodeMode() { + return (this.state & ParserContext.UnicodeMode) === ParserContext.UnicodeMode; + } + + private get inNamedCaptureGroups() { + return (this.state & ParserContext.NamedCaptureGroups) === ParserContext.NamedCaptureGroups; + } + + private get inUnicodeSetMode() { + return (this.state & ParserContext.UnicodeSetMode) === ParserContext.UnicodeSetMode; + } + + private raise(message: string, position = this.position): never { + const e = new SyntaxError(message); + e.position = position; + throw e; + } + + private peek(length = 1) { + return this.source.slice(this.position, this.position + length); + } + + private test(c: string) { + return this.source.slice(this.position, this.position + c.length) === c; + } + + private eat(c: string) { + if (this.source.slice(this.position, this.position + c.length) === c) { + this.position += c.length; + return true; + } + return false; + } + + private next() { + const c = this.source[this.position]; + if (!c) { + this.raise('Unexpected end of input', this.position - 1); + } + this.position += 1; + return c; + } + + private expect(c: string) { + if (!this.eat(c)) { + this.raise(`Expected ${c} but got ${this.peek()}`); + } + } + + // Pattern :: + // Disjunction + parsePattern(): ParseNode.RegExp.Pattern { + const node: ParseNode.RegExp.Pattern = { + type: 'Pattern', + capturingGroups: this.capturingGroups, + Disjunction: this.parseDisjunction(), + }; + if (this.position < this.source.length) { + this.raise('Unexpected token'); + } + // AtomEscape :: DecimalEscape + // EE: It is a Syntax Error if the CapturingGroupNumber of DecimalEscape is strictly greater than CountLeftCapturingParensWithin(the Pattern containing AtomEscape). + this.decimalEscapes.forEach((d) => { + if (d.value > node.capturingGroups.length) { + this.raise(`There is no ${d.value} capture groups`, d.position); + } + }); + // AtomEscape :: k GroupName + // EE: It is a Syntax Error if GroupSpecifiersThatMatch(GroupName) is empty. + this.groupNameRefs.forEach((g) => { + if (!node.capturingGroups.find((x) => g.production === 'CaptureGroupName' && x.GroupName === g.GroupName)) { + this.raise(`There is no capture group called ${JSON.stringify(g.GroupName)}`, g.position); + } + }); + // EE: It is a Syntax Error if CountLeftCapturingParensWithin(Pattern) ≥ 2**32 - 1. + if (CountLeftCapturingParensWithin(node) >= 2 ** 32 - 1) { + this.raise('Too many capturing groups'); + } + return node; + } + + // in case ((?x)|(?y))|b, after we check the inner Disjunction, we need to mark them as safe, + // so when checking the outer Disjunction, we don't make a false positive + private disjunctionCheckedCaptureGroups = new Set(); + + // Disjunction :: + // Alternative + // Alternative `|` Disjunction + private parseDisjunction(): ParseNode.RegExp.Disjunction { + const beforeCaptureGroups = this.capturingGroups.length; + const Alternative = this.parseAlternative(); + const node: Mutable = { + type: 'Disjunction', + Alternative, + Disjunction: undefined, + }; + const afterAlternativeCaptureGroups = this.capturingGroups.length; + if (this.eat('|')) { + node.Disjunction = this.parseDisjunction(); + } + // EE: It is a Syntax Error if Pattern contains two distinct GroupSpecifiers x and y such that the CapturingGroupName of x is the CapturingGroupName of y and such that MightBothParticipate(x, y) is true. + const alternativeSeenNameGroups = new Set(); + this.capturingGroups.slice(beforeCaptureGroups, afterAlternativeCaptureGroups).forEach((x) => { + if (this.disjunctionCheckedCaptureGroups.has(x)) { + return; + } + if (x.GroupName) { + if (alternativeSeenNameGroups.has(x.GroupName)) { + this.raise(`Duplicated capture group ${JSON.stringify(x.GroupName)}`, x.position); + } + alternativeSeenNameGroups.add(x.GroupName); + } + this.disjunctionCheckedCaptureGroups.add(x); + }); + + const disjunctionSeenNameGroups = new Set(); + this.capturingGroups.slice(afterAlternativeCaptureGroups).forEach((x) => { + if (this.disjunctionCheckedCaptureGroups.has(x)) { + return; + } + if (x.GroupName) { + if (disjunctionSeenNameGroups.has(x.GroupName)) { + this.raise(`Duplicated capture group ${JSON.stringify(x.GroupName)}`, x.position); + } + disjunctionSeenNameGroups.add(x.GroupName); + } + this.disjunctionCheckedCaptureGroups.add(x); + }); + return node; + } + + + // Alternative :: + // [empty] + // Term Alternative + private parseAlternative(): ParseNode.RegExp.Alternative { + const Term: ParseNode.RegExp.Term[] = []; + const node: Mutable = { + type: 'Alternative', + Term, + }; + while (this.position < this.source.length && !isClosingSyntaxCharacter(this.peek())) { + Term.push(this.parseTerm()); + } + return node; + } + + // Term :: + // Assertion + // Atom + // Atom Quantifier + private parseTerm(): ParseNode.RegExp.Term { + const assertion = this.maybeParseAssertion(); + if (assertion) { + return { type: 'Term', production: 'Assertion', Assertion: assertion }; + } + const capturingParenthesesBefore = this.capturingGroups.length; + return { + type: 'Term', + production: 'Atom', + leftCapturingParenthesesBefore: this.leftCapturingParenthesesBefore, + Atom: this.parseAtom(), + Quantifier: this.maybeParseQuantifier(), + capturingParenthesesWithin: this.capturingGroups.length - capturingParenthesesBefore, + }; + } + + // Assertion :: + // `^` + // `$` + // `\` `b` + // `\` `B` + // `(` `?` `=` Disjunction `)` + // `(` `?` `!` Disjunction `)` + // `(` `?` `<=` Disjunction `)` + // `(` `?` ` DecimalDigits_b) { + this.raise('Numbers out of order in quantifier', quantifierPos); + } + } + QuantifierPrefix = { + type: 'QuantifierPrefix', + production: '{}', + DecimalDigits_a, + DecimalDigits_b, + }; + this.expect('}'); + } + + if (QuantifierPrefix!) { + return { + type: 'Quantifier', + QuantifierPrefix, + QuestionMark: this.eat('?'), + }; + } + + return undefined; + } + + // Atom :: + // PatternCharacter + // `.` + // `\` AtomEscape + // CharacterClass + // `(` GroupSpecifier Disjunction `)` + // (? RegularExpressionModifiers : Disjunction ) + // (? RegularExpressionModifiers - RegularExpressionModifiers : Disjunction ) + private parseAtom(): ParseNode.RegExp.Atom { + if (this.eat('.')) { + return { type: 'Atom', production: '.' }; + } + if (this.eat('\\')) { + return { type: 'Atom', production: 'AtomEscape', AtomEscape: this.parseAtomEscape() }; + } + if (this.eat('(')) { + let node: Mutable; + if (this.eat('?')) { + if (this.peek() === '<') { + this.leftCapturingParenthesesBefore += 1; + const groupNamePos = this.position + 1; + const name = this.parseGroupName(); + node = { + type: 'Atom', + production: 'Group', + leftCapturingParenthesesBefore: this.leftCapturingParenthesesBefore - 1, + GroupSpecifier: name, + Disjunction: this.parseDisjunction(), + }; + this.getAllGroupsWithName(name).push(node); + this.capturingGroups.push({ GroupName: name, position: groupNamePos }); + } else { + const { PlusModifiers, MinusModifiers } = this.parseAtomModifiers(); + node = { + type: 'Atom', + production: 'Modifier', + leftCapturingParenthesesBefore: this.leftCapturingParenthesesBefore, + AddModifiers: PlusModifiers, + RemoveModifiers: MinusModifiers, + Disjunction: this.parseDisjunction(), + }; + } + } else { + this.leftCapturingParenthesesBefore += 1; + node = { + type: 'Atom', + production: 'Group', + leftCapturingParenthesesBefore: this.leftCapturingParenthesesBefore - 1, + GroupSpecifier: undefined, + Disjunction: this.parseDisjunction(), + }; + this.capturingGroups.push({ GroupName: undefined, position: this.position }); + } + this.expect(')'); + return node; + } + if (this.test('[')) { + return { + type: 'Atom', + production: 'CharacterClass', + CharacterClass: this.parseCharacterClass(), + }; + } + if (isSyntaxCharacter(this.peek())) { + this.raise(`Expected a character but got ${this.peek()}`); + } + return { + type: 'Atom', + production: 'PatternCharacter', + PatternCharacter: this.parseSourceCharacter(), + }; + } + + // WhatWeAreParsingHere :: (used in Atom, `<` is for named capture groups) + // [empty] [lookahead = `:` or `<`] + // RegularExpressionModifiers [lookahead = `:` or `<`] + // RegularExpressionModifiers `-` RegularExpressionModifiers [lookahead = `:` or `<`] + // + // RegularExpressionModifiers :: + // [empty] + // RegularExpressionModifiers RegularExpressionModifier + // + // RegularExpressionModifier :: one of `i` `m` `s` + private parseAtomModifiers(): Record<'PlusModifiers' | 'MinusModifiers', ParseNode.RegExp.RegularExpressionModifier[] | undefined> { + const modifierPos = this.position; + let modifiers: ParseNode.RegExp.RegularExpressionModifier[] | undefined; + const result = { PlusModifiers: modifiers, MinusModifiers: modifiers }; + + let seenMinus = false; + while (this.position < this.source.length) { + if (this.eat(':')) { + break; + } else if (this.test('<')) { + break; + } else if (this.eat('i')) { + modifiers ??= []; + modifiers.push('i'); + } else if (this.eat('m')) { + modifiers ??= []; + modifiers.push('m'); + } else if (this.eat('s')) { + modifiers ??= []; + modifiers.push('s'); + } else if (this.eat('-')) { + modifiers ??= []; + if (seenMinus) { + this.raise('Unexpected - in modifiers', this.position - 1); + } + seenMinus = true; + result.PlusModifiers = modifiers; + modifiers = []; + result.MinusModifiers = modifiers; + } else { + this.raise(`${JSON.stringify(this.peek())} is not a valid modifier`); + } + } + if (!seenMinus) { + result.PlusModifiers = modifiers; + } + const allModifiers = result.PlusModifiers?.concat(result.MinusModifiers || []); + // EE: It is a Syntax Error if the source text matched by the first RegularExpressionModifiers and the source text matched by the second RegularExpressionModifiers are both empty. + if (result.PlusModifiers && result.MinusModifiers && result.PlusModifiers.length + result.MinusModifiers.length === 0) { + this.raise('PlusModifiers and MinusModifiers cannot be both empty.', this.position - 2); + } + // EE: It is a Syntax Error if the source text matched by RegularExpressionModifiers contains the same code point more than once. + // EE: It is a Syntax Error if the source text matched by the first RegularExpressionModifiers contains the same code point more than once. + // EE: It is a Syntax Error if the source text matched by the second RegularExpressionModifiers contains the same code point more than once. + // EE: It is a Syntax Error if any code point in the source text matched by the first RegularExpressionModifiers is also contained in the source text matched by the second RegularExpressionModifiers. + if (allModifiers?.length && allModifiers.length !== new Set(allModifiers).size) { + this.raise('Repeated modifiers in modifier group', modifierPos); + } + return result; + } + + // AtomEscape :: + // DecimalEscape + // CharacterClassEscape + // CharacterEscape + // [+N] `k` GroupName + private parseAtomEscape(): ParseNode.RegExp.AtomEscape { + if (this.inNamedCaptureGroups && this.eat('k')) { + const groupNamePos = this.position + 1; + const GroupName = this.parseGroupName(); + const node: ParseNode.RegExp.AtomEscape = { + type: 'AtomEscape', + position: groupNamePos, + production: 'CaptureGroupName', + GroupName, + groupSpecifiersThatMatchSelf: this.getAllGroupsWithName(GroupName), + }; + this.groupNameRefs.push(node); + return node; + } + const CharacterClassEscape = this.maybeParseCharacterClassEscape(); + if (CharacterClassEscape) { + return { + type: 'AtomEscape', + production: 'CharacterClassEscape', + CharacterClassEscape, + }; + } + const DecimalEscape = this.maybeParseDecimalEscape(); + if (DecimalEscape) { + return { + type: 'AtomEscape', + production: 'DecimalEscape', + DecimalEscape, + }; + } + return { + type: 'AtomEscape', + production: 'CharacterEscape', + CharacterEscape: this.parseCharacterEscape(), + }; + } + + // CharacterEscape :: + // ControlEscape + // `c` AsciiLetter + // `0` [lookahead ∉ DecimalDigit] + // HexEscapeSequence + // RegExpUnicodeEscapeSequence + // IdentityEscape + // + // IdentityEscape :: + // [+U] SyntaxCharacter + // [+U] `/` + // [~U] SourceCharacter but not UnicodeIDContinue + private parseCharacterEscape(): ParseNode.RegExp.CharacterEscape { + switch (this.peek()) { + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + return { + type: 'CharacterEscape', + production: 'ControlEscape', + ControlEscape: this.next() as 'f' | 'n' | 'r' | 't' | 'v', + }; + case 'c': { + this.next(); + const c = this.next(); + if (c === undefined) { + if (this.inUnicodeMode) { + this.raise('Invalid identity escape'); + } + return { + type: 'CharacterEscape', + production: 'IdentityEscape', + IdentityEscape: 'c' as Character, + }; + } + const p = c.codePointAt(0)!; + if ((p >= 65 && p <= 90) || (p >= 97 && p <= 122)) { + return { + type: 'CharacterEscape', + production: 'AsciiLetter', + AsciiLetter: c, + }; + } + if (this.inUnicodeMode) { + this.raise('Invalid identity escape', this.position - 2); + } + return { + type: 'CharacterEscape', + production: 'IdentityEscape', + IdentityEscape: c as Character, + }; + } + case 'x': + if (isHexDigit(this.source[this.position + 1]) && isHexDigit(this.source[this.position + 2])) { + return { + type: 'CharacterEscape', + production: 'HexEscapeSequence', + HexEscapeSequence: this.parseHexEscapeSequence(), + }; + } + if (this.inUnicodeMode) { + this.raise('Invalid identity escape'); + } + this.next(); + return { + type: 'CharacterEscape', + production: 'IdentityEscape', + IdentityEscape: 'x' as Character, + }; + case 'u': { + const RegExpUnicodeEscapeSequence = this.maybeParseRegExpUnicodeEscapeSequence(); + if (RegExpUnicodeEscapeSequence) { + return { + type: 'CharacterEscape', + production: 'RegExpUnicodeEscapeSequence', + RegExpUnicodeEscapeSequence, + }; + } + if (this.inUnicodeMode) { + this.raise('Invalid identity escape'); + } + this.next(); + return { + type: 'CharacterEscape', + production: 'IdentityEscape', + IdentityEscape: 'u' as Character, + }; + } + default: { + const c = this.peek(); + if (c === '') { + this.raise('Unexpected escape'); + } + if (c === '0' && !isDecimalDigit(this.source[this.position + 1])) { + this.position += 1; + return { + type: 'CharacterEscape', + production: c, + }; + } + if (this.inUnicodeMode) { + if (c !== '/' && !isSyntaxCharacter(c)) { + this.raise('Invalid identity escape'); + } + } else { + if (isIdentifierContinue(c)) { + this.raise('Invalid identity escape'); + } + } + return { + type: 'CharacterEscape', + production: 'IdentityEscape', + IdentityEscape: this.next() as Character, + }; + } + } + } + + // DecimalEscape :: + // NonZeroDigit DecimalDigits? [lookahead != DecimalDigit] + private maybeParseDecimalEscape(): ParseNode.RegExp.DecimalEscape | undefined { + 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: ParseNode.RegExp.DecimalEscape = { + 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 `}` + private maybeParseCharacterClassEscape(): ParseNode.RegExp.CharacterClassEscape | undefined { + const peek = this.peek(); + switch (peek) { + case 'd': + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + this.next(); + return { + type: 'CharacterClassEscape', + production: peek, + }; + case 'p': + case 'P': { + if (!this.inUnicodeMode) { + return undefined; + } + this.next(); + this.expect('{'); + let LoneUnicodePropertyNameOrValue = ''; + const namePos = this.position; + while (true) { + if (this.position >= this.source.length) { + this.raise('Invalid unicode property name or value'); + } + const c = this.source[this.position]; + if (c === '_' || isDecimalDigit(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'); + } + let UnicodePropertyValue; + let valuePos; + if (this.source[this.position] === '=') { + this.position += 1; + valuePos = this.position; + UnicodePropertyValue = ''; + while (true) { + if (this.position >= this.source.length) { + this.raise('Invalid unicode property value', valuePos); + } + 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', valuePos); + } + } + this.expect('}'); + if (UnicodePropertyValue) { + const UnicodePropertyName = LoneUnicodePropertyNameOrValue; + // EE: It is a Syntax Error if the source text matched by UnicodePropertyName is not a Unicode property name or property alias listed in the “Property name and aliases” column of Table 69. + if (!(UnicodePropertyName in Table69_NonbinaryUnicodeProperties)) { + this.raise('Invalid unicode property name', namePos); + } + __ts_cast__(UnicodePropertyName); + if (UnicodePropertyName !== 'Script_Extensions' && UnicodePropertyName !== 'scx') { + // EE: It is a Syntax Error if the source text matched by UnicodePropertyName is neither Script_Extensions nor scx and the source text matched by UnicodePropertyValue is not a property value or property value alias for the Unicode property or property alias given by the source text matched by UnicodePropertyName listed in PropertyValueAliases.txt. + if (!((UnicodePropertyValue in PropertyValueAliases[Table69_NonbinaryUnicodeProperties[UnicodePropertyName]]))) { + this.raise('Invalid unicode property value', valuePos); + } + } else if (!(UnicodePropertyValue in PropertyValueAliases.Script)) { + // EE: It is a Syntax Error if the source text matched by UnicodePropertyName is either Script_Extensions or scx and the source text matched by UnicodePropertyValue is not a property value or property value alias for the Unicode property Script (sc) listed in PropertyValueAliases.txt. + this.raise('Invalid unicode property value', valuePos); + } + return { + type: 'CharacterClassEscape', + production: peek, + UnicodePropertyValueExpression: { + type: 'UnicodePropertyValueExpression', + production: '=', + UnicodePropertyName, + UnicodePropertyValue, + }, + }; + } + // UnicodePropertyValueExpression :: LoneUnicodePropertyNameOrValue + // EE: It is a Syntax Error if the source text matched by LoneUnicodePropertyNameOrValue is not a Unicode property value or property value alias for the General_Category (gc) property listed in PropertyValueAliases.txt, nor a binary property or binary property alias listed in the “Property name and aliases” column of Table 70, nor a binary property of strings listed in the “Property name” column of Table 71. + if ( + !(LoneUnicodePropertyNameOrValue in PropertyValueAliases.General_Category) + && !(LoneUnicodePropertyNameOrValue in Table70_BinaryUnicodeProperties) + && !(LoneUnicodePropertyNameOrValue in Table71_BinaryPropertyOfStrings) + ) { + this.raise('Invalid unicode property', namePos); + } + // EE: It is a Syntax Error if the enclosing Pattern does not have a [UnicodeSetsMode] parameter and the source text matched by LoneUnicodePropertyNameOrValue is a binary property of strings listed in the “Property name” column of Table 71. + if (LoneUnicodePropertyNameOrValue in Table71_BinaryPropertyOfStrings && !this.inUnicodeSetMode) { + this.raise(`${LoneUnicodePropertyNameOrValue} can only be used with v flag`, namePos); + } + // EE: It is a Syntax Error if MayContainStrings of the UnicodePropertyValueExpression is true. + if (peek === 'P' && LoneUnicodePropertyNameOrValue in Table71_BinaryPropertyOfStrings) { + this.raise(`${LoneUnicodePropertyNameOrValue} cannot be inverted`, namePos - 2); + } + return { + type: 'CharacterClassEscape', + production: peek, + UnicodePropertyValueExpression: { + type: 'UnicodePropertyValueExpression', + production: 'Lone', + LoneUnicodePropertyNameOrValue, + }, + }; + } + default: + return undefined; + } + } + + // CharacterClass :: + // `[` ClassContents `]` + // `[` `^` ClassContents `]` + private parseCharacterClass(): ParseNode.RegExp.CharacterClass { + this.expect('['); + const invertPos = this.position; + const invert = this.eat('^'); + const node: ParseNode.RegExp.CharacterClass = { + type: 'CharacterClass', + invert, + ClassContents: this.parseClassContents(), + }; + // CharacterClass :: [^ ClassContents ] + // EE: It is a Syntax Error if MayContainStrings of the ClassContents is true. + if (invert && MayContainStrings(node.ClassContents)) { + this.raise('This class cannot be inverted', invertPos); + } + this.expect(']'); + return node; + } + + // ClassContents + // [empty] + // [~UnicodeSetMode] NonemptyClassRanges + // [+UnicodeSetMode] ClassSetExpression + private parseClassContents(): ParseNode.RegExp.ClassContents { + // [empty] + if (this.test(']')) { + return { type: 'ClassContents', production: 'Empty' }; + } + if (this.inUnicodeSetMode) { + return { + type: 'ClassContents', + production: 'ClassSetExpression', + ClassSetExpression: this.parseClassSetExpression(), + }; + } else { + return { + type: 'ClassContents', + production: 'NonEmptyClassRanges', + NonemptyClassRanges: this.parseNonemptyClassRanges(), + }; + } + } + + // NonemptyClassRanges :: + // ClassAtom + // ClassAtom NonemptyClassRangesNoDash + // ClassAtom `-` ClassAtom [empty] + // ClassAtom `-` ClassAtom NonemptyClassRanges + private parseNonemptyClassRanges(): ParseNode.RegExp.ClassRange[] { + Assert(!this.inUnicodeSetMode); + const ranges: Mutable = []; + while (!this.test(']')) { + if (this.position >= this.source.length) { + this.raise('Unexpected end of CharacterClass'); + } + const atomPos = this.position; + const atom = this.parseClassAtom(); + if (this.eat('-')) { + if (this.test(']')) { + // [\w-] is valid (\w ++ "-") + ranges.push(atom); + ranges.push({ type: 'ClassAtom', production: '-' }); + } else { + // EE: It is a Syntax Error if IsCharacterClass of the first ClassAtom is true or IsCharacterClass of the second ClassAtom is true. + if (atom.production === 'ClassEscape' && atom.ClassEscape.production === 'CharacterClassEscape') { + this.raise('Invalid class range', atomPos); + } + const atom2Pos = this.position; + const atom2 = this.parseClassAtom(); + // EE: It is a Syntax Error if IsCharacterClass of the first ClassAtom is false, IsCharacterClass of the second ClassAtom is false, and the CharacterValue of the first ClassAtom is strictly greater than the CharacterValue of the second ClassAtom. + // EE: It is a Syntax Error if IsCharacterClass of ClassAtomNoDash is false, IsCharacterClass of ClassAtom is false, and the CharacterValue of ClassAtomNoDash is strictly greater than the CharacterValue of ClassAtom. + if (!IsCharacterClass(atom) && !IsCharacterClass(atom2) && CharacterValue(atom as CharacterValueAcceptNode) > CharacterValue(atom2 as CharacterValueAcceptNode)) { + this.raise('Invalid class range', atomPos); + } + // EE: It is a Syntax Error if IsCharacterClass of ClassAtomNoDash is true or IsCharacterClass of ClassAtom is true. + if (IsCharacterClass(atom)) { + this.raise('Invalid class range', atomPos); + } + if (IsCharacterClass(atom2)) { + this.raise('Invalid class range', atom2Pos); + } + 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 + private parseClassAtom(): ParseNode.RegExp.ClassAtom { + if (this.eat('\\')) { + if (this.eat('b')) { + return { type: 'ClassAtom', production: 'ClassEscape', ClassEscape: { type: 'ClassEscape', production: 'b' } }; + } + if (this.inUnicodeMode && this.eat('-')) { + return { type: 'ClassAtom', production: '-' }; + } + const CharacterClassEscape = this.maybeParseCharacterClassEscape(); + if (CharacterClassEscape) { + return { + type: 'ClassAtom', + production: 'ClassEscape', + ClassEscape: { type: 'ClassEscape', production: 'CharacterClassEscape', CharacterClassEscape }, + }; + } + return { + type: 'ClassAtom', + production: 'ClassEscape', + ClassEscape: { + type: 'ClassEscape', + production: 'CharacterEscape', + CharacterEscape: this.parseCharacterEscape(), + }, + }; + } + return { + type: 'ClassAtom', + production: 'SourceCharacter', + SourceCharacter: this.parseSourceCharacter(), + }; + } + + private parseSourceCharacter(): Character { + if (this.inUnicodeMode || this.inUnicodeSetMode) { + 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()) as UnicodeCharacter; + } + } + return this.next() as Character; + } + + private parseGroupName(): string { + this.expect('<'); + const RegExpIdentifierName = this.parseRegExpIdentifierName(); + this.expect('>'); + return RegExpIdentifierName; + } + + // RegExpIdentifierName :: + // RegExpIdentifierStart + // RegExpIdentifierName RegExpIdentifierPart + private parseRegExpIdentifierName(): string { + 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({ UnicodeMode: true }, () => this.maybeParseRegExpUnicodeEscapeSequence()); + if (!RegExpUnicodeEscapeSequence) { + this.raise('Invalid unicode escape'); + } + const raw = String.fromCodePoint(CharacterValue(RegExpUnicodeEscapeSequence)); + // EE: It is a Syntax Error if the CharacterValue of RegExpUnicodeEscapeSequence is not the numeric value of some code point matched by the IdentifierStartChar lexical grammar production. + // EE: It is a Syntax Error if the CharacterValue of RegExpUnicodeEscapeSequence is not the numeric value of some code point matched by the IdentifierPartChar lexical grammar production. + // EE: It is a Syntax Error if the RegExpIdentifierCodePoint of RegExpIdentifierPart is not matched by the UnicodeIDContinue lexical grammar production. + if (!check(raw)) { + this.raise('Invalid identifier escape'); + } + buffer += raw; + } else if (isLeadingSurrogate(code)) { + // EE: It is a Syntax Error if the RegExpIdentifierCodePoint of RegExpIdentifierStart is not matched by the UnicodeIDStart lexical grammar production. + 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 + private parseDecimalDigits(): string { + let n = ''; + if (!isDecimalDigit(this.peek())) { + this.raise('Invalid decimal digits'); + } + while (isDecimalDigit(this.peek())) { + n += this.next(); + } + return n; + } + + // HexEscapeSequence :: + // `x` HexDigit HexDigit + private parseHexEscapeSequence(): ParseNode.RegExp.HexEscapeSequence { + 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, + }; + } + + private scanHex(length: number) { + if (length === 0) { + this.raise('Invalid code point'); + } + let n = 0; + let oldN = 0; + for (let i = 0; i < length; i += 1) { + const c = this.source[this.position]; + if (isHexDigit(c)) { + this.position += 1; + oldN = n; + n = (n << 4) | Number.parseInt(c, 16); + if (oldN > n) { + // overflow + this.raise('Invalid hex digit'); + } + } 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 `}` + private maybeParseRegExpUnicodeEscapeSequence(): ParseNode.RegExp.RegExpUnicodeEscapeSequence | undefined { + const start = this.position; + if (!this.eat('u')) { + this.position = start; + return undefined; + } + if (this.inUnicodeMode && this.eat('{')) { + const end = this.source.indexOf('}' as Character, 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.inUnicodeMode && isLeadingSurrogate(lead)) { + const back = this.position; + if (this.eat('\\u')) { + let trail; + try { + trail = this.scanHex(4); + if (isTrailingSurrogate(trail)) { + return { + type: 'RegExpUnicodeEscapeSequence', + HexLeadSurrogate: lead, + HexTrailSurrogate: trail, + }; + } + } catch { + } + this.position = back; + } + return { + type: 'RegExpUnicodeEscapeSequence', + HexLeadSurrogate: lead, + }; + } + return { + type: 'RegExpUnicodeEscapeSequence', + Hex4Digits: lead, + }; + } + + // ClassSetExpression :: + // ClassUnion + // ClassIntersection + // ClassSubtraction + private parseClassSetExpression(): ParseNode.RegExp.ClassSetExpression { + Assert(this.inUnicodeSetMode); + + const oldPos = this.position; + const left = this.maybeParseClassSetCharacter(); + const peek2 = this.peek(2); + // ClassUnion :: ClassSetRange + if (left !== undefined && peek2 !== '--' && peek2[0] === '-') { + this.position = oldPos; + return this.parseClassUnion(); + } + // ClassUnion :: ClassSetOperand ... + // ClassIntersection :: ClassSetOperand ... + // ClassSubtraction :: ClassSetOperand ... + const leftReparsed = this.parseClassSetOperand(left); + if (this.eat('&&')) { + return this.parseClassIntersectionOrSubtraction('&&', leftReparsed); + } + if (this.eat('--')) { + return this.parseClassIntersectionOrSubtraction('--', leftReparsed); + } + return this.parseClassUnion(leftReparsed); + } + + private parseClassUnion(operand?: ParseNode.RegExp.ClassSetOperand): ParseNode.RegExp.ClassUnion { + const union: Array = operand ? [operand] : []; + while (true) { + const charPos = this.position; + const char = this.maybeParseClassSetCharacter(); + if (char !== undefined) { + // ClassSetRange + if (this.eat('-')) { + const char2 = this.maybeParseClassSetCharacter(); + if (char2 === undefined) { + this.raise('Unterminated range'); + } + // EE: It is a Syntax Error if the CharacterValue of the first ClassSetCharacter is strictly greater than the CharacterValue of the second ClassSetCharacter. + if (CharacterValue(char) > CharacterValue(char2)) { + this.raise(`Invalid range: ${String.fromCodePoint(CharacterValue(char))} is bigger than ${String.fromCodePoint(CharacterValue(char2))}`, charPos); + } + union.push({ type: 'ClassSetRange', left: char, right: char2 }); + continue; + } + // ClassSetCharacter + union.push({ type: 'ClassSetOperand', production: 'ClassSetCharacter', ClassSetCharacter: char }); + } else if (this.peek() === '\\' || this.peek() === '[') { + // NestedClass or ClassStringDisjunction + union.push(this.parseClassSetOperand()); + } else { + break; + } + } + return { type: 'ClassUnion', union }; + } + + private parseClassIntersectionOrSubtraction(type: '&&' | '--', operand?: ParseNode.RegExp.ClassSetOperand): ParseNode.RegExp.ClassIntersection | ParseNode.RegExp.ClassSubtraction { + const tokens = operand ? [operand] : []; + while (true) { + tokens.push(this.parseClassSetOperand()); + if (this.eat(type)) { + continue; + } + break; + } + Assert(tokens.length >= 2); + return { type: type === '&&' ? 'ClassIntersection' : 'ClassSubtraction', operands: tokens }; + } + + private parseClassSetOperand(left?: ParseNode.RegExp.ClassSetCharacter): ParseNode.RegExp.ClassSetOperand { + Assert(this.inUnicodeSetMode); + if (left !== undefined) { + return { type: 'ClassSetOperand', production: 'ClassSetCharacter', ClassSetCharacter: left }; + } + // ClassSetOperand :: NestedClass :: [ [lookahead ≠ ^] ClassContents[+UnicodeMode, +UnicodeSetsMode] ] + // ClassSetOperand :: NestedClass :: [^ ClassContents[+UnicodeMode, +UnicodeSetsMode] ] + if (this.eat('[')) { + const invertPos = this.position; + const invert = this.eat('^'); + const ClassContents = this.scope( + { UnicodeMode: true, UnicodeSetsMode: true }, + () => this.parseClassContents(), + ); + // NestedClass :: [^ ClassContents ] + // EE: It is a Syntax Error if MayContainStrings of the ClassContents is true. + if (invert && MayContainStrings(ClassContents)) { + this.raise('This class cannot be inverted', invertPos); + } + this.expect(']'); + return { + type: 'ClassSetOperand', + production: 'NestedClass', + NestedClass: { + type: 'NestedClass', production: 'ClassContents', invert, ClassContents, + }, + }; + } + if (this.eat('\\')) { + // ClassSetOperand :: ClassStringDisjunction :: \q{ ClassStringDisjunctionContents } + if (this.eat('q')) { + this.expect('{'); + const ClassStringDisjunction = this.parseClassStringDisjunctionContents(); + this.expect('}'); + return { + type: 'ClassSetOperand', + production: 'ClassStringDisjunction', + ClassStringDisjunction, + }; + } + // ClassSetOperand :: NestedClass :: \ CharacterClassEscape[+UnicodeMode] + const escape = this.scope( + { UnicodeMode: true }, + () => this.maybeParseCharacterClassEscape(), + ); + if (!escape) { + this.raise(`Expect a CharacterClassEscape but ${this.peek()}`); + } + return { + type: 'ClassSetOperand', + production: 'NestedClass', + NestedClass: { type: 'NestedClass', production: 'CharacterClassEscape', CharacterClassEscape: escape }, + }; + } + const ClassSetCharacter = this.maybeParseClassSetCharacter(); + if (!ClassSetCharacter) { + this.raise(`Unexpected ${this.peek()}`); + } + return { type: 'ClassSetOperand', production: 'ClassSetCharacter', ClassSetCharacter }; + } + + // ClassSetCharacter :: + // [lookahead ∉ ClassSetReservedDoublePunctuator] SourceCharacter but not ClassSetSyntaxCharacter + // \ CharacterEscape[+UnicodeMode] + // \ ClassSetReservedPunctuator + // \b + private maybeParseClassSetCharacter(): ParseNode.RegExp.ClassSetCharacter | undefined { + Assert(this.inUnicodeSetMode); + const nextTwo = this.peek(2); + // ClassSetCharacter :: \b + if (nextTwo === '\\b') { + this.position += 2; + return { type: 'ClassSetCharacter', production: 'UnicodeCharacter', UnicodeCharacter: '\\b' as UnicodeCharacter }; + } + + // ClassSetCharacter :: [lookahead ∉ ClassSetReservedDoublePunctuator] SourceCharacter but not ClassSetSyntaxCharacter + if ( + // [lookahead ∉ ClassSetReservedDoublePunctuator] + !'&& !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~'.split(' ').includes(nextTwo) + // and not ClassSetSyntaxCharacter + && !'( ) [ ] { } / - \\ |'.split(' ').includes(nextTwo[0]) + ) { + // parse SourceCharacter + return { type: 'ClassSetCharacter', production: 'UnicodeCharacter', UnicodeCharacter: this.parseSourceCharacter() as UnicodeCharacter }; + } + + // all production left requires a \ at the beginning + if (nextTwo[0] !== '\\') { + return undefined; + } + + // \ ClassSetReservedPunctuator + if ('& - ! # % , : ; < = > @ ` ~'.split(' ').includes(nextTwo[1])) { + this.position += 2; + return { type: 'ClassSetCharacter', production: 'UnicodeCharacter', UnicodeCharacter: nextTwo[1] as UnicodeCharacter }; + } + + // anything that can start a Character Escape + if ('fnrtvc0xu/^$\\.*+?()[]{}|'.includes(nextTwo[1])) { + this.position += 1; + return { type: 'ClassSetCharacter', production: 'CharacterEscape', CharacterEscape: this.scope({ UnicodeMode: true }, () => this.parseCharacterEscape()) }; + } + return undefined; + } + + // ClassStringDisjunctionContents is a list of ClassString that separated by |. + private parseClassStringDisjunctionContents(): ParseNode.RegExp.ClassStringDisjunction { + const parsed: ParseNode.RegExp.ClassSetCharacter[][] = []; + let current: ParseNode.RegExp.ClassSetCharacter[] = []; + while (true) { + const parse = this.maybeParseClassSetCharacter(); + if (parse) { + current.push(parse); + } else if (this.eat('|')) { + parsed.push(current); + current = []; + } else { + parsed.push(current); + break; + } + } + return { type: 'ClassStringDisjunction', ClassString: parsed }; + } +} + +/** https://tc39.es/ecma262/#sec-static-semantics-maycontainstrings */ +function MayContainStrings(node: ParseNode.RegExp.UnicodePropertyValueExpression | ParseNode.RegExp.ClassContents | ParseNode.RegExp.ClassSetExpression | ParseNode.RegExp.ClassSetOperand | ParseNode.RegExp.ClassSetRange | ParseNode.RegExp.NestedClass): boolean { + switch (node.type) { + case 'ClassContents': + if (node.production === 'ClassSetExpression') { + return MayContainStrings(node.ClassSetExpression); + } + return false; + case 'UnicodePropertyValueExpression': + if (node.production === 'Lone') { + if (node.LoneUnicodePropertyNameOrValue in Table71_BinaryPropertyOfStrings) { + return true; + } + } + return false; + case 'ClassUnion': + return node.union.some(MayContainStrings); + case 'ClassIntersection': + return node.operands.some(MayContainStrings); + case 'ClassSubtraction': + return node.operands.some(MayContainStrings); + case 'ClassSetRange': + return false; + case 'ClassSetOperand': + if (node.production === 'ClassSetCharacter') { + return false; + } else if (node.production === 'NestedClass') { + return MayContainStrings(node.NestedClass); + } else if (node.production === 'ClassStringDisjunction') { + return node.ClassStringDisjunction.ClassString.some((x) => x.length !== 1); + } + unreachable(node); + case 'NestedClass': + if (node.production === 'CharacterClassEscape') { + if (node.CharacterClassEscape.production !== 'p') { + return false; + } + return MayContainStrings(node.CharacterClassEscape.UnicodePropertyValueExpression); + } else if (node.production === 'ClassContents') { + return MayContainStrings(node.ClassContents); + } + unreachable(node); + default: + unreachable(node); + } +} diff --git a/src/parser/Scope.mts b/src/parser/Scope.mts new file mode 100644 index 0000000..94d3b02 --- /dev/null +++ b/src/parser/Scope.mts @@ -0,0 +1,538 @@ +import { Assert, Parser } from '../index.mts'; +import { isArray, OutOfRange } from '../helpers.mts'; +import type { TokenData } from './Lexer.mts'; +import type { ParseNode } from './ParseNode.mts'; + +export enum Flag { + return = 1 << 0, + await = 1 << 1, + yield = 1 << 2, + parameters = 1 << 3, + newTarget = 1 << 4, + importMeta = 1 << 5, + superCall = 1 << 6, + superProperty = 1 << 7, + in = 1 << 8, + default = 1 << 9, + module = 1 << 10, + classStaticBlock = 1 << 11, +} + +export interface DeclarationInfo { + readonly name: string; + readonly node: ParseNode; +} + +export function getDeclarations(node: ParseNode | readonly ParseNode[]): DeclarationInfo[] { + if (isArray(node)) { + return node.flatMap((n) => getDeclarations(n)); + } + switch (node.type) { + case 'LexicalBinding': + case 'VariableDeclaration': + case 'BindingRestElement': + case 'ForBinding': + if (node.BindingIdentifier) { + return getDeclarations(node.BindingIdentifier); + } + if (node.BindingPattern) { + return getDeclarations(node.BindingPattern); + } + return []; + case 'BindingRestProperty': + if (node.BindingIdentifier) { + return getDeclarations(node.BindingIdentifier); + } + 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 'PrivateIdentifier': + return [{ name: `#${node.name}`, node }]; + case 'StringLiteral': + return [{ name: node.value, node }]; + case 'Elision': + return []; + case 'ForDeclaration': + return getDeclarations(node.ForBinding); + case 'ExportSpecifier': + return getDeclarations(node.exportName); + case 'FunctionDeclaration': + case 'GeneratorDeclaration': + case 'AsyncFunctionDeclaration': + case 'AsyncGeneratorDeclaration': + Assert(!!node.BindingIdentifier); + return getDeclarations(node.BindingIdentifier); + case 'LexicalDeclaration': + return getDeclarations(node.BindingList); + case 'VariableStatement': + return getDeclarations(node.VariableDeclarationList); + case 'ClassDeclaration': + Assert(!!node.BindingIdentifier); + return getDeclarations(node.BindingIdentifier); + default: + throw new OutOfRange('getDeclarations', node); + } +} + +export type ScopeFlagSetters = + & { readonly [P in (keyof typeof Flag) & string]?: boolean; } + & { + readonly lexical?: boolean; + readonly variable?: boolean; + readonly variableFunctions?: boolean; + readonly private?: boolean; + readonly label?: LabelType | 'boundary'; + readonly strict?: boolean; + }; + +export interface ScopeInfo { + readonly flags: ScopeFlagSetters; + readonly lexicals: Set; + readonly variables: Set; + readonly functions: Set; + readonly parameters: Set; +} + +export interface PrivateScopeInfo { + readonly outer: PrivateScopeInfo | undefined; + readonly names: Map>; +} + +export interface UndefinedPrivateAccessInfo { + readonly node: ParseNode; + readonly name: string; + readonly scope: PrivateScopeInfo | undefined; +} + +export interface ArrowInfo { + readonly isAsync: boolean; + hasTrailingComma: boolean; + readonly yieldExpressions: ParseNode[]; + readonly awaitExpressions: ParseNode[]; + readonly awaitIdentifiers: ParseNode[]; + merge(other: ArrowInfo): void; +} + +export interface AssignmentInfo { + readonly type: 'assign' | 'arrow' | 'for'; + readonly earlyErrors: SyntaxError[]; + clear(): void; +} + +export type LabelType = 'switch' | 'loop'; + +export interface Label { + type: LabelType | null; + readonly name?: string; + readonly nextToken?: TokenData | null; +} + +export class Scope { + private readonly parser: Parser; + + private readonly scopeStack: ScopeInfo[] = []; + + labels: Label[] = []; + + readonly arrowInfoStack: (ArrowInfo | null)[] = []; + + readonly assignmentInfoStack: AssignmentInfo[] = []; + + readonly exports = new Set(); + + readonly undefinedExports = new Map(); + + privateScope: PrivateScopeInfo | undefined; + + private readonly undefinedPrivateAccesses: UndefinedPrivateAccessInfo[] = []; + + private flags: Flag = 0 as Flag; + + constructor(parser: Parser) { + this.parser = parser; + } + + 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; + } + + inClassStaticBlock() { + return (this.flags & Flag.classStaticBlock) !== 0; + } + + isDefault() { + return (this.flags & Flag.default) !== 0; + } + + isModule() { + return (this.flags & Flag.module) !== 0; + } + + with(flags: ScopeFlagSetters, f: () => R) { + const oldFlags = this.flags; + + Object.entries(flags) + .forEach(([k, v]) => { + if (k in Flag && typeof Flag[k as keyof typeof Flag] === 'number') { + if (v === true) { + this.flags |= Flag[k as keyof typeof Flag]; + } else if (v === false) { + this.flags &= ~Flag[k as keyof typeof Flag]; + } + } + }); + + if (flags.lexical || flags.variable) { + this.scopeStack.push({ + flags, + lexicals: new Set(), + variables: new Set(), + functions: new Set(), + parameters: new Set(), + }); + } + + if (flags.private) { + this.privateScope = { + outer: this.privateScope, + names: new Map(), + }; + } + + 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.private) { + this.privateScope = this.privateScope!.outer; + + if (this.privateScope === undefined) { + this.undefinedPrivateAccesses.forEach(({ node, name, scope }) => { + while (scope) { + if (scope.names.has(name)) { + return; + } + scope = scope.outer; + } + this.parser.raiseEarly('NotDefined', node, name); + }); + } + } + + 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: [], + merge(other) { + this.yieldExpressions.push(...other.yieldExpressions); + this.awaitExpressions.push(...other.awaitExpressions); + this.awaitIdentifiers.push(...other.awaitIdentifiers); + }, + }); + } + + popArrowInfo() { + const arrowInfo = this.arrowInfoStack.pop(); + Assert(!!arrowInfo); + return arrowInfo; + } + + get arrowInfo() { + if (this.arrowInfoStack.length > 0) { + return this.arrowInfoStack[this.arrowInfoStack.length - 1]; + } + return undefined; + } + + pushAssignmentInfo(type: 'assign' | 'arrow' | 'for') { + const parser = this.parser; + this.assignmentInfoStack.push({ + type, + earlyErrors: [], + clear() { + this.earlyErrors.forEach((e) => { + parser.earlyErrors.delete(e); + }); + }, + }); + } + + popAssignmentInfo() { + const assignmentInfo = this.assignmentInfoStack.pop(); + Assert(!!assignmentInfo); + return assignmentInfo; + } + + registerObjectLiteralEarlyError(error: SyntaxError) { + for (let i = this.assignmentInfoStack.length - 1; i >= 0; i -= 1) { + const info = this.assignmentInfoStack[i]; + info.earlyErrors.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; + } + } + /* node:coverage 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; + } + } + /* node:coverage ignore next */ + throw new RangeError(); + } + + declare(node: ParseNode | readonly ParseNode[], type: 'private', extraType?: 'field' | 'method' | 'get' | 'set'): void; + + declare(node: ParseNode | readonly ParseNode[], type: 'lexical' | 'import' | 'function' | 'parameter' | 'variable' | 'export'): void; + + declare(node: ParseNode | readonly ParseNode[], type: 'lexical' | 'import' | 'function' | 'parameter' | 'variable' | 'export' | 'private', extraType?: 'field' | 'method' | 'get' | 'set') { + 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; + case 'private': { + const types = this.privateScope!.names.get(d.name); + if (types) { + let duplicate = true; + switch (extraType) { + case 'field': + case 'method': + break; + case 'set': + case 'get': + duplicate = types.has(extraType) || types.has('field') || types.has('method'); + types.add(extraType); + break; + default: + break; + } + if (duplicate) { + this.parser.raiseEarly('AlreadyDeclared', d.node, d.name); + } + } else if (extraType) { + this.privateScope!.names.set(d.name, new Set([extraType])); + } + break; + } + /* node:coverage ignore next 2 */ + default: + throw new RangeError(type); + } + }); + } + + checkUndefinedExports(NamedExports: ParseNode.NamedExports) { + const scope = this.variableScope(); + NamedExports.ExportsList.forEach((n) => { + const name = n.localName.type === 'IdentifierName' ? n.localName.name : n.localName.value; + if (!scope.lexicals.has(name) && !scope.variables.has(name)) { + this.undefinedExports.set(name, n.localName); + } + }); + } + + checkUndefinedPrivate(PrivateIdentifier: ParseNode.PrivateIdentifier) { + if (this.parser.state.allowAllPrivateNames) { + return; + } + const [{ node, name }] = getDeclarations(PrivateIdentifier); + + if (!this.privateScope) { + this.parser.raiseEarly('NotDefined', node, name); + return; + } + + let scope: PrivateScopeInfo | undefined = this.privateScope; + while (scope) { + if (scope.names.has(name)) { + return; + } + scope = scope.outer; + } + + this.undefinedPrivateAccesses.push({ + node, + name, + scope: this.privateScope, + }); + } +} diff --git a/src/parser/StatementParser.mts b/src/parser/StatementParser.mts new file mode 100644 index 0000000..b6aa2c6 --- /dev/null +++ b/src/parser/StatementParser.mts @@ -0,0 +1,959 @@ +import type { Mutable } from '../helpers.mts'; +import { Token, isAutomaticSemicolon } from './tokens.mts'; +import { ExpressionParser } from './ExpressionParser.mts'; +import { FunctionKind } from './FunctionParser.mts'; +import { getDeclarations, type LabelType } from './Scope.mts'; +import type { ParseNode } from './ParseNode.mts'; + +export abstract class StatementParser extends ExpressionParser { + eatSemicolonWithASI() { + if (this.eat(Token.SEMICOLON)) { + return true; + } + if (this.peek().hadLineTerminatorBefore || isAutomaticSemicolon(this.peek().type)) { + return true; + } + return false; + } + + semicolon() { + if (!this.eatSemicolonWithASI()) { + this.unexpected(); + } + } + + // StatementList : + // StatementListItem + // StatementList StatementListItem + /** + * @param endToken endToken + * @param directives directives, this array will be mutated. + */ + parseStatementList(endToken: string | Token, directives?: string[]): ParseNode.StatementList { + const statementList: Mutable = []; + 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(): ParseNode.StatementListItem { + switch (this.peek().type) { + case Token.FUNCTION: + return this.parseHoistableDeclaration(); + case Token.AT: + case Token.CLASS: + return this.parseClassDeclaration(null); + 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(): ParseNode.HoistableDeclaration { + 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(decoratorsAttachedToClassDeclaration: null | readonly ParseNode.Decorator[]): ParseNode.ClassDeclaration { + return this.parseClass(decoratorsAttachedToClassDeclaration, false) as ParseNode.ClassDeclaration; + } + + // LexicalDeclaration : LetOrConst BindingList `;` + parseLexicalDeclaration(): ParseNode.LexicalDeclarationLike { + const node = this.startNode(); + const letOrConst = this.eat('let') ? 'let' : this.expect(Token.CONST) && 'const'; + node.LetOrConst = letOrConst; + 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(): ParseNode.BindingList { + const bindingList: Mutable = []; + do { + const node = this.parseBindingElement(); + bindingList.push(this.repurpose(node, 'LexicalBinding')); + } while (this.eat(Token.COMMA)); + return bindingList; + } + + // BindingElement : + // SingleNameBinding + // BindingPattern Initializer? + // SingleNameBinding : + // BindingIdentifier Initializer? + parseBindingElement(): ParseNode.BindingElementLike { + 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(): ParseNode.BindingPattern { + 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(): ParseNode.ObjectBindingPattern { + const node = this.startNode(); + this.expect(Token.LBRACE); + const BindingPropertyList: Mutable = []; + node.BindingPropertyList = BindingPropertyList; + while (!this.eat(Token.RBRACE)) { + if (this.test(Token.ELLIPSIS)) { + node.BindingRestProperty = this.parseBindingRestProperty(); + this.expect(Token.RBRACE); + break; + } else { + BindingPropertyList.push(this.parseBindingProperty()); + if (!this.eat(Token.COMMA)) { + this.expect(Token.RBRACE); + break; + } + } + } + return this.finishNode(node, 'ObjectBindingPattern'); + } + + // BindingProperty : + // SingleNameBinding + // PropertyName : BindingElement + parseBindingProperty(): ParseNode.BindingPropertyLike { + 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'); + } else { + if (name.type !== 'IdentifierName') { + this.unexpected(name); + } + this.validateIdentifierReference(name.name, node); + } + node.BindingIdentifier = this.repurpose(name, 'BindingIdentifier'); + node.Initializer = this.parseInitializerOpt(); + return this.finishNode(node, 'SingleNameBinding'); + } + + // BindingRestProperty : + // `...` BindingIdentifier + parseBindingRestProperty(): ParseNode.BindingRestProperty { + 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(): ParseNode.ArrayBindingPattern { + const node = this.startNode(); + this.expect(Token.LBRACK); + const BindingElementList: Mutable = []; + node.BindingElementList = BindingElementList; + while (true) { + while (this.test(Token.COMMA)) { + const elision = this.startNode(); + this.next(); + 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 { + BindingElementList.push(this.parseBindingElement()); + } + if (this.eat(Token.RBRACK)) { + break; + } + this.expect(Token.COMMA); + } + return this.finishNode(node, 'ArrayBindingPattern'); + } + + // BindingRestElement : + // `...` BindingIdentifier + // `...` BindingPattern + parseBindingRestElement(): ParseNode.BindingRestElement { + 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(): ParseNode.Initializer | null { + if (this.eat(Token.ASSIGN)) { + return this.parseAssignmentExpression(); + } + return null; + } + + // FunctionDeclaration + parseFunctionDeclaration(kind: FunctionKind): ParseNode.FunctionDeclarationLike { + return this.parseFunction(false, kind) as ParseNode.FunctionDeclarationLike; + } + + // Statement : + // ... + parseStatement(): ParseNode.Statement { + 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(): ParseNode.BlockStatement { + return this.parseBlock(); + } + + // Block : `{` StatementList `}` + parseBlock(lexical = true): ParseNode.Block { + const node = this.startNode(); + this.expect(Token.LBRACE); + node.StatementList = this.scope.with({ lexical }, () => this.parseStatementList(Token.RBRACE)); + return this.finishNode(node, 'Block'); + } + + // VariableStatement : `var` VariableDeclarationList `;` + parseVariableStatement(): ParseNode.VariableStatement { + 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): ParseNode.VariableDeclarationList { + const declarationList: Mutable = []; + do { + const node = this.parseVariableDeclaration(firstDeclarationRequiresInit); + declarationList.push(node); + } while (this.eat(Token.COMMA)); + return declarationList; + } + + // VariableDeclaration : + // BindingIdentifier Initializer? + // BindingPattern Initializer + parseVariableDeclaration(firstDeclarationRequiresInit: boolean): ParseNode.VariableDeclaration { + 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 [lookahead != `else`] + parseIfStatement(): ParseNode.IfStatement { + 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(): ParseNode.WhileStatement { + 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(): ParseNode.DoWhileStatement { + const node = this.startNode(); + this.expect(Token.DO); + node.Statement = this.scope.with({ label: 'loop' }, () => 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`, `async` `of` }] 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(): ParseNode.ForStatement | ParseNode.ForInOfStatement { + 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 = this.repurpose(list[0], 'ForBinding', (_, oldNode) => { + if (oldNode.Initializer) { + this.unexpected(oldNode.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 = this.repurpose(list[0], 'ForBinding', (_, oldNode) => { + if (oldNode.Initializer) { + this.unexpected(oldNode.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: ParseNode) => { + 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 as ParseNode.LeftHandSideExpression; // NOTE: unsound cast + node.Expression = this.parseExpression(); + this.expect(Token.RPAREN); + node.Statement = this.parseStatement(); + return this.finishNode(node, 'ForInStatement'); + } + const isExactlyAsync = expression.type === 'IdentifierReference' + && !expression.escaped + && expression.name === 'async'; + if ((!isExactlyAsync || isAwait) && this.eat('of')) { + assignmentInfo.clear(); + validateLHS(expression); + node.LeftHandSideExpression = expression as ParseNode.LeftHandSideExpression; // NOTE: unsound cast + 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(): ParseNode.ForBinding { + 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(): ParseNode.SwitchStatement { + 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(): ParseNode.CaseBlock { + const node = this.startNode(); + let CaseClauses_a: Mutable | undefined; + let CaseClauses_b: Mutable | undefined; + 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); + let StatementList: Mutable | undefined; + while (!(this.test(Token.CASE) || this.test(Token.DEFAULT) || this.test(Token.RBRACE))) { + if (!StatementList) { + StatementList = []; + inner.StatementList = StatementList; + } + StatementList.push(this.parseStatementListItem()); + } + if (t === Token.DEFAULT) { + node.DefaultClause = this.finishNode(inner, 'DefaultClause'); + } else { + if (node.DefaultClause) { + if (!CaseClauses_b) { + CaseClauses_b = []; + node.CaseClauses_b = CaseClauses_b; + } + CaseClauses_b.push(this.finishNode(inner, 'CaseClause')); + } else { + if (!CaseClauses_a) { + CaseClauses_a = []; + node.CaseClauses_a = CaseClauses_a; + } + 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(): ParseNode.BreakStatement | ParseNode.ContinueStatement { + 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: ParseNode.Unfinished, isBreak: boolean) { + 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(): ParseNode.ReturnStatement { + if (!this.scope.hasReturn()) { + this.unexpected(); + } + const node = this.startNode(); + this.expect(Token.RETURN); + if (this.eatSemicolonWithASI()) { + node.Expression = null; + } else { + node.Expression = this.parseExpression(); + this.semicolon(); + } + return this.finishNode(node, 'ReturnStatement'); + } + + // WithStatement : + // `with` `(` Expression `)` Statement + parseWithStatement(): ParseNode.WithStatement { + 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(): ParseNode.ThrowStatement { + 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(): ParseNode.TryStatement { + 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(): ParseNode.DebuggerStatement { + 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(): ParseNode.ExpressionStatement | ParseNode.LabelledStatement { + 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 startToken = this.peek(); + const node = this.startNode(); + const expression = this.parseExpression(); + if (expression.type === 'IdentifierReference' && this.eat(Token.COLON)) { + const LabelIdentifier = this.repurpose(expression, 'LabelIdentifier'); + node.LabelIdentifier = LabelIdentifier; + + if (this.scope.labels.find((l) => l.name === LabelIdentifier.name)) { + this.raiseEarly('AlreadyDeclared', node.LabelIdentifier, node.LabelIdentifier.name); + } + let type: LabelType | null = 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; + } + if (type !== null && this.scope.labels.length > 0) { + const last = this.scope.labels[this.scope.labels.length - 1]; + if (last.nextToken === startToken) { + last.type = type; + } + } + this.scope.labels.push({ + name: node.LabelIdentifier.name, + type, + nextToken: type === null ? this.peek() : null, + }); + + node.LabelledItem = this.parseStatement(); + + this.scope.labels.pop(); + + return this.finishNode(node, 'LabelledStatement'); + } + node.Expression = expression; + this.semicolon(); + return this.finishNode(node, 'ExpressionStatement'); + } +} diff --git a/src/parser/TemporalParser.mts b/src/parser/TemporalParser.mts new file mode 100644 index 0000000..166616d --- /dev/null +++ b/src/parser/TemporalParser.mts @@ -0,0 +1,243 @@ +// https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar + +import type { TemporalDurationObject } from '../intrinsics/Temporal/Duration.mts'; +import { temporal_todo } from '../abstract-ops/temporal/not-implemented.mts'; +import { + Assert, + JSStringValue, + Q, + surroundingAgent, + ToPrimitive, + Value, + type PlainCompletion, type PlainEvaluator, type TimeRecord, type ValueCompletion, +} from '#self'; + +/** https://tc39.es/proposal-temporal/#sec-temporal-iso-string-time-zone-parse-records */ +export interface ISOStringTimeZoneParseRecord { + readonly Z: boolean; + readonly OffsetString: string | undefined; + readonly TimeZoneAnnotation: string | undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-iso-date-time-parse-records */ +export interface ISODateTimeParseRecord { + readonly Year: number | undefined; + readonly Month: number; + readonly Day: number; + readonly Time: TimeRecord | 'start-of-day'; + readonly TimeZone: ISOStringTimeZoneParseRecord; + readonly Calendar: string | undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime */ +export function ParseISODateTime(_isoString: string, _allowedFormats: Array<'TemporalInstantString' | 'TemporalDateTimeString[~Zoned]' | 'TemporalTimeString' | 'TemporalMonthDayString' | 'TemporalYearMonthString' | 'TemporalDateTimeString[+Zoned]'>): PlainCompletion { + temporal_todo(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring */ +export function ParseTemporalCalendarString(_isoString: string): PlainCompletion { + temporal_todo(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring */ +export function ParseTemporalDurationString(_isoString: string): ValueCompletion { + temporal_todo(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring */ +export function ParseTemporalTimeZoneString(_timeZoneString: string): PlainCompletion { + temporal_todo(); +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-time-zone-identifier-parse-records */ +export interface TimeZoneIdentifierParseRecord { + Name: string | undefined; + OffsetMinutes: number | undefined; +} + +/** https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode */ +export function* ParseMonthCode(argument: Value | string): PlainEvaluator<{ MonthNumber: number; IsLeapMonth: boolean }> { + const monthCode = typeof argument === 'string' ? Value(argument) : Q(yield* ToPrimitive(argument, 'string')); + if (!(monthCode instanceof JSStringValue)) { + return surroundingAgent.Throw('TypeError', 'NotAString', typeof argument === 'string' ? Value(argument) : argument); + } + + // If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception. + + // MonthCode ::: + // M00L + // M0 NonZeroDigit L? + // M NonZeroDigit DecimalDigit L? + + if (!monthCode.stringValue().match(/^(M00L|M0[1-9]L?|M[1-9][0-9]L?)$/)) { + return surroundingAgent.Throw('RangeError', 'InvalidMonth'); + } + + let isLeapMonth = false; + if (monthCode.stringValue().length === 4) { + // Assert: The fourth code unit of monthCode is 0x004C (LATIN CAPITAL LETTER L). + Assert(monthCode.stringValue().charCodeAt(4) === 0x004C); + isLeapMonth = true; + } + const monthCodeDigits = monthCode.stringValue().substring(1, 3); + const monthNumber = parseInt(monthCodeDigits, 10); + if (monthNumber === 0 && !isLeapMonth) { + return surroundingAgent.Throw('RangeError', 'InvalidMonth'); + } + return { MonthNumber: monthNumber, IsLeapMonth: isLeapMonth }; +} + +/** https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset */ +export function ParseDateTimeUTCOffset(_offsetString: string): PlainCompletion { + temporal_todo(); +} + +// https://tc39.es/proposal-temporal/#sec-temporal-parsetimezoneidentifier +export function ParseTimeZoneIdentifier(_identifier: string): PlainCompletion { + temporal_todo(); +} + +export class DateParser { + public input: string; + + public pos = 0; + + constructor(input: string) { + this.input = input; + } + + peek() { + return this.input[this.pos]; + } + + expect(char: string, message?: string) { + if (this.input[this.pos] !== char) { + throw new Error(message || `Expected '${char}' at position ${this.pos}`); + } + this.pos += 1; + } + + tryParse(f: () => T) { + const startPos = this.pos; + try { + return f(); + } catch { + this.pos = startPos; + return undefined; + } + } + + // #region Top Goals (used as a parameter of ParseText) + // AmbiguousTemporalTimeString ::: + // DateSpecMonthDay TimeZoneAnnotation? Annotations? + // DateSpecYearMonth TimeZoneAnnotation? Annotations? + parseAmbiguousTemporalTimeString() { + const DateSpecMonthDay = this.tryParse(() => this.parseDateSpecMonthDay()); + const DateSpecYearMonth = DateSpecMonthDay ? undefined : this.parseDateSpecYearMonth(); + const TimeZoneAnnotation = this.tryParse(() => this.parseTimeZoneAnnotation()); + const Annotations = this.peek() && this.parseAnnotations(); + return { + DateSpecMonthDay, DateSpecYearMonth, TimeZoneAnnotation, Annotations, + }; + } + + // AnnotationValue + parseAnnotationValue() { } + + // TemporalDurationString + parseTemporalDurationString() { } + + // TemporalDateTimeString + parseTemporalDateTimeString() { } + + // TemporalInstantString + parseTemporalInstantString() { } + + // TemporalYearMonthString + parseTemporalYearMonthString() { } + + // TemporalMonthDayString + parseTemporalMonthDayString() { } + + // TemporalTimeString ::: + // AnnotatedTime + // AnnotatedDateTime[~Zoned, +TimeRequired] + parseTemporalTimeString() { } + + // TimeZoneIdentifier ::: + // UTCOffset[~SubMinutePrecision] + // TimeZoneIANAName + parseTimeZoneIdentifier() { + const next = this.peek(); + if (next === '+' || next === '-') { + return { UTCOffset: this.parseUTCOffset(false), TimeZoneIANAName: undefined }; + } + return { UTCOffset: undefined, TimeZoneIANAName: this.parseTimeZoneIANAName() }; + } + + // UTCOffset[SubMinutePrecision] ::: + // ASCIISign Hour + // ASCIISign Hour TimeSeparator[+Extended] MinuteSecond + // ASCIISign Hour TimeSeparator[~Extended] MinuteSecond + // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction? + // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction? + parseUTCOffset(SubMinutePrecision: boolean) { + const sign = this.parseAsciiSign(); + const hour = this.parseHour(); + const timeSeparator = this.tryParseTimeSeparator_ExtendedOrNot(); + const minuteSecond = this.parseMinuteSecond(); + if (SubMinutePrecision && this.peek()) { + return this.tryParse(() => { + const timeSeparator2 = this.tryParseTimeSeparator_ExtendedOrNot(); + const minuteSecond2 = this.parseMinuteSecond(); + const fraction = this.tryParseTemporalDecimalFraction(); + return { + sign, hour, timeSeparator, minuteSecond, timeSeparator2, minuteSecond2, fraction, + }; + }); + } + return { + sign, hour, timeSeparator, minuteSecond, + }; + } + // #endregion + + // #region Sub goals + // ASCIISign ::: one of + - + parseAsciiSign(): '+' | '-' { + const next = this.peek(); + if (next === '+' || next === '-') { + this.pos += 1; + return next; + } + throw new Error(`Expected '+' or '-' at position ${this.pos}`); + } + + // TimeSeparator[Extended] ::: + // [+Extended] : + // [~Extended] [empty] + tryParseTimeSeparator_ExtendedOrNot(): ':' | undefined { + if (this.peek() === ':') { + this.pos += 1; + return ':'; + } + return undefined; + } + + parseTimeZoneIANAName() { } + + parseHour() { } + + parseMinuteSecond() { } + + tryParseTemporalDecimalFraction() { } + + parseDateSpecMonthDay() { } + + parseDateSpecYearMonth() { } + + parseTimeZoneAnnotation() { } + + parseAnnotations() {} + // #endregion +} diff --git a/src/parser/tokens.mts b/src/parser/tokens.mts new file mode 100644 index 0000000..7e689b9 --- /dev/null +++ b/src/parser/tokens.mts @@ -0,0 +1,213 @@ +/** Coerces a property key into a numeric index */ +type ToIndex = + T extends number ? ToIndex<`${T}`> : + T extends `${bigint}` ? T extends `${infer I extends number}` ? I : never : + never; + +type ReplaceType = T extends U ? V : T; + +type TokenDefinition = readonly [name: string, value: string | null, precedence?: number]; + +type TokenArrayToAssignTokenArray = { + readonly [P in keyof A]: readonly [`ASSIGN_${A[P][0]}`, `${A[P][1]}`, A[P][2]]; +}; + +type TokenArrayToEnumLike = { + readonly [I in ToIndex as A[I][0]]: I; +}; + +type TokenArrayToElementArray = { + readonly [P in keyof A]: ReplaceType; +}; + +type TokenArrayToKeywordsArray = readonly { + readonly [I in ToIndex]: A[I][1] extends Lowercase ? A[I][1] : never; +}[ToIndex][]; + +type KeywordsArrayToEnumLike = { + readonly [P in A[number]]: typeof Token[Uppercase

& keyof typeof Token]; +}; + +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], +] as const satisfies readonly TokenDefinition[]; + +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]) as readonly TokenDefinition[] as TokenArrayToAssignTokenArray, + // 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], + ['BIGINT', 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'], + ['PRIVATE_IDENTIFIER', null], + ['AT', '@'], + + ['ENUM', 'enum'], + + ['ESCAPED_KEYWORD', null], +] as const satisfies readonly TokenDefinition[]; + +export const Token = RawTokens + .reduce((obj, [name], i) => { + obj[name] = i; + return obj; + }, Object.create(null)) as TokenArrayToEnumLike; + +export type Token = typeof Token[keyof typeof Token]; + +export const TokenNames = RawTokens.map((r) => r[0]) as readonly string[] as TokenArrayToElementArray; + +export const TokenValues = RawTokens.map((r) => r[1]) as readonly (string | null)[] as TokenArrayToElementArray; + +export const TokenPrecedence = RawTokens.map((r) => (r[2] || 0)) as readonly number[] as TokenArrayToElementArray; + +const Keywords = RawTokens + .filter(([name, raw]) => name.toLowerCase() === raw) + .map(([, raw]) => raw!) as TokenArrayToKeywordsArray; + +export const KeywordLookup = Keywords + .reduce((obj, kw) => { + obj[kw] = Token[kw.toUpperCase() as Uppercase]; + return obj; + }, Object.create(null)) as KeywordsArrayToEnumLike; + +const KeywordRaw: ReadonlySet = new Set(Object.keys(KeywordLookup)); +const KeywordTokens: ReadonlySet = new Set(Object.values(KeywordLookup)); + +const isInRange = (t: number, l: number, h: number) => t >= l && t <= h; +export const isAutomaticSemicolon = (t: number) => isInRange(t, Token.SEMICOLON, Token.EOS); +export const isMember = (t: number) => isInRange(t, Token.TEMPLATE, Token.LBRACK); +export const isPropertyOrCall = (t: number) => isInRange(t, Token.TEMPLATE, Token.LPAREN); +export const isKeyword = (t: number): t is typeof KeywordLookup[keyof typeof KeywordLookup] => KeywordTokens.has(t); +export const isKeywordRaw = (s: string): s is keyof typeof KeywordLookup => KeywordRaw.has(s); + +const ReservedWordsStrict: ReadonlySet = new Set([ + 'implements', 'interface', 'let', + 'package', 'private', 'protected', + 'public', 'static', 'yield', +]); + +export const isReservedWordStrict = (s: string) => ReservedWordsStrict.has(s); diff --git a/src/parser/unicode.d.ts b/src/parser/unicode.d.ts new file mode 100644 index 0000000..7469fe2 --- /dev/null +++ b/src/parser/unicode.d.ts @@ -0,0 +1,20 @@ +declare module '@unicode/unicode-16.0.0/Binary_Property/ID_Start/regex.js' { + let regex: RegExp; + export default regex; +} +declare module '@unicode/unicode-16.0.0/Binary_Property/ID_Continue/regex.js' { + let regex: RegExp; + export default regex; +} +declare module '@unicode/unicode-16.0.0/General_Category/Space_Separator/regex.js' { + let regex: RegExp; + export default regex; +} +declare module '@unicode/unicode-16.0.0/Case_Folding/C/symbols.js' { + let data: Map; + export default data; +} +declare module '@unicode/unicode-16.0.0/Case_Folding/S/symbols.js' { + let data: Map; + export default data; +} diff --git a/src/parser/utils.mts b/src/parser/utils.mts new file mode 100644 index 0000000..18a497b --- /dev/null +++ b/src/parser/utils.mts @@ -0,0 +1,114 @@ +import type { ParseNode } from '#self'; + +export type TargetSymbol = ParseNode['type'] | 'super' | 'this'; + +/** https://tc39.es/ecma262/#sec-static-semantics-contains */ +export function Contains(node: ParseNode, symbol: TargetSymbol): boolean { + switch (node.type) { + case 'FunctionDeclaration': + case 'FunctionExpression': + case 'GeneratorDeclaration': + case 'GeneratorExpression': + case 'AsyncGeneratorDeclaration': + case 'AsyncGeneratorExpression': + case 'AsyncFunctionDeclaration': + case 'AsyncFunctionExpression': + return false; + case 'ClassTail': { + // We don't have ClassBody? + throw new Error('TODO'); + } + case 'ClassStaticBlock': + return false; + case 'ArrowFunction': + case 'AsyncArrowFunction': + throw new Error('TODO'); + case 'PropertyDefinition': { + // Note && TODO: PropertyDefinition in spec refers to MethodDefinition here, + // but our PropertyDefinition is parital one. + // We should check this at all use site of PropertyDefinitionList. + break; + } + // LiteralPropertyName : IdentifierName + // throw new Error('TODO'); + case 'MemberExpression': { + // MemberExpression : MemberExpression . IdentifierName + if (node.IdentifierName) { + return Contains(node.MemberExpression, symbol); + } + break; + } + case 'SuperProperty': { + if (node.IdentifierName) { + return symbol === 'super'; + } + break; + } + case 'CallExpression': { + throw new Error('TODO'); + } + case 'OptionalChain': { + if (node.IdentifierName) { + // OptionalChain : OptionalChain . IdentifierName + if (node.OptionalChain) { + return Contains(node.OptionalChain, symbol); + } + // OptionalChain : ?. IdentifierName + return false; + } + break; + } + default: + } + + // 1. For each child node child of this Parse Node + for (const child of avoid_using_children(node)) { + // a. If child is an instance of symbol, return true. + if (child.type === symbol) { + return true; + } + // b. If child is an instance of a nonterminal, then + const contained = Contains(child, symbol); + // i. If contained is true, return true. + if (contained) { + return true; + } + } + return false; +} + +/** https://tc39.es/ecma262/pr/3714/#sec-static-semantics-arrayliteralcontentnodes */ +export function ArrayLiteralContentNodes(node: ParseNode.ArrayLiteral) { + return node.ElementList; +} + +/** https://tc39.es/ecma262/pr/3714/#sec-static-semantics-propertydefinitionnodes */ +export function PropertyDefinitionNodes(node: ParseNode.ObjectLiteral) { + return node.PropertyDefinitionList; +} + +// Note: this is not a correct forEachChild implementation, but it is not worth the effort to implement it fully. +// defer it to the future if needed. +export function* avoid_using_children(node: ParseNode): Generator { + for (const key of Reflect.ownKeys(node)) { + if (typeof key === 'string' && key !== 'parent' && key !== 'type' && key !== 'location' && key !== 'sourceText') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const child = (node as any)[key]; + if (typeof child === 'object' && child) { + if (Array.isArray(child)) { + for (const element of child) { + if (isParseNode(element)) { + yield element; + } + } + } else if ('type' in child) { + yield child; + } + } + } + } +} + +function isParseNode(value: unknown): value is ParseNode { + return !!(value && typeof value === 'object' && 'type' in value && 'location' in value); +} diff --git a/src/runtime-semantics/AdditiveExpression.mts b/src/runtime-semantics/AdditiveExpression.mts new file mode 100644 index 0000000..3c5f90e --- /dev/null +++ b/src/runtime-semantics/AdditiveExpression.mts @@ -0,0 +1,29 @@ +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-addition-operator-plus-runtime-semantics-evaluation */ +// AdditiveExpression : AdditiveExpression + MultiplicativeExpression +function* Evaluate_AdditiveExpression_Plus({ AdditiveExpression, MultiplicativeExpression }: ParseNode.AdditiveExpression): ValueEvaluator { + // 1. Return ? EvaluateStringOrNumericBinaryExpression(AdditiveExpression, +, MultiplicativeExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(AdditiveExpression, '+', MultiplicativeExpression)); +} + +/** https://tc39.es/ecma262/#sec-subtraction-operator-minus-runtime-semantics-evaluation */ +function* Evaluate_AdditiveExpression_Minus({ AdditiveExpression, MultiplicativeExpression }: ParseNode.AdditiveExpression): ValueEvaluator { + // 1. Return ? EvaluateStringOrNumericBinaryExpression(AdditiveExpression, -, MultiplicativeExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(AdditiveExpression, '-', MultiplicativeExpression)); +} + +export function* Evaluate_AdditiveExpression(AdditiveExpression: ParseNode.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/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mts b/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mts new file mode 100644 index 0000000..2c1bf46 --- /dev/null +++ b/src/runtime-semantics/ApplyStringOrNumericBinaryOperator.mts @@ -0,0 +1,78 @@ +import { + JSStringValue, Value, + NumberValue, + BigIntValue, + SameType, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import { + Assert, Throw, ToNumeric, ToPrimitive, ToString, +} from '#self'; + +export type BinaryOperator = '+' | '-' | '*' | '/' | '%' | '**' | '<<' | '>>' | '>>>' | '&' | '^' | '|'; +/** https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator */ +export function* ApplyStringOrNumericBinaryOperator(lval: Value, opText: BinaryOperator, rval: Value) { + // 1. If opText is +, then + if (opText === '+') { + // a. Let lprim be ? ToPrimitive(lval). + const lprim = Q(yield* ToPrimitive(lval)); + // b. Let rprim be ? ToPrimitive(rval). + const rprim = Q(yield* ToPrimitive(rval)); + // c. If Type(lprim) is String or Type(rprim) is String, then + if (lprim instanceof JSStringValue || rprim instanceof JSStringValue) { + // i. Let lstr be ? ToString(lprim). + const lstr = Q(yield* ToString(lprim)); + // ii. Let rstr be ? ToString(rprim). + const rstr = Q(yield* ToString(rprim)); + // iii. Return the string-concatenation of lstr and rstr. + return 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(yield* ToNumeric(lval)); + // 4. Let rnum be ? ToNumeric(rval). + const rnum = Q(yield* ToNumeric(rval)); + // 5. If SameType(lNum, rNum) is false, throw a TypeError exception. + if (!SameType(lnum, rnum)) { + return Throw.TypeError('Cannot mix BigInt and other types in $1 operation', opText); + } + if (lnum instanceof BigIntValue) { + const operations = { + '**': BigIntValue.exponentiate, + '*': BigIntValue.multiply, + '/': BigIntValue.divide, + '%': BigIntValue.remainder, + '+': BigIntValue.add, + '-': BigIntValue.subtract, + '<<': BigIntValue.leftShift, + '>>': BigIntValue.signedRightShift, + '>>>': BigIntValue.unsignedRightShift, + '&': BigIntValue.bitwiseAND, + '^': BigIntValue.bitwiseXOR, + '|': BigIntValue.bitwiseOR, + }; + return Q(operations[opText](lnum, rnum as BigIntValue)); + } else { + Assert(lnum instanceof NumberValue); + const operations = { + '**': NumberValue.exponentiate, + '*': NumberValue.multiply, + '/': NumberValue.divide, + '%': NumberValue.remainder, + '+': NumberValue.add, + '-': NumberValue.subtract, + '<<': NumberValue.leftShift, + '>>': NumberValue.signedRightShift, + '>>>': NumberValue.unsignedRightShift, + '&': NumberValue.bitwiseAND, + '^': NumberValue.bitwiseXOR, + '|': NumberValue.bitwiseOR, + }; + return Q(operations[opText](lnum, rnum as NumberValue)); + } +} diff --git a/src/runtime-semantics/ArgumentListEvaluation.mts b/src/runtime-semantics/ArgumentListEvaluation.mts new file mode 100644 index 0000000..557a731 --- /dev/null +++ b/src/runtime-semantics/ArgumentListEvaluation.mts @@ -0,0 +1,178 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, Descriptor, type Arguments, +} from '../value.mts'; +import { Evaluate, type PlainEvaluator } from '../evaluator.mts'; +import { Q, X } from '../completion.mts'; +import { OutOfRange, isArray } from '../helpers.mts'; +import { TemplateStrings } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + ArrayCreate, + SetIntegrityLevel, + ToString, + GetIterator, + GetValue, + F, + IteratorStepValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-gettemplateobjec */ +function GetTemplateObject(templateLiteral: ParseNode.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(count)); + // 9. Let template be ! ArrayCreate(count). + const rawObj = X(ArrayCreate(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(F(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(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; +} + +/** https://tc39.es/ecma262/#sec-template-literals-runtime-semantics-argumentlistevaluation */ +// TemplateLiteral : NoSubstitutionTemplate +// +// https://github.com/tc39/ecma262/pull/1402 +// TemplateLiteral : SubstitutionTemplate +function* ArgumentListEvaluation_TemplateLiteral(TemplateLiteral: ParseNode.TemplateLiteral): PlainEvaluator { + switch (true) { + case TemplateLiteral.TemplateSpanList.length === 1: { + const templateLiteral = TemplateLiteral; + const siteObj = GetTemplateObject(templateLiteral); + return [siteObj] as Arguments; + } + + case TemplateLiteral.TemplateSpanList.length > 1: { + const templateLiteral = TemplateLiteral; + const siteObj = GetTemplateObject(templateLiteral); + const restSub = []; + for (const Expression of TemplateLiteral.ExpressionList) { + const subRef = Q(yield* Evaluate(Expression)); + const subValue = Q(yield* GetValue(subRef)); + restSub.push(subValue); + } + return [siteObj, ...restSub] as Arguments; + } + + default: + throw new OutOfRange('ArgumentListEvaluation_TemplateLiteral', TemplateLiteral); + } +} + +/** https://tc39.es/ecma262/#sec-argument-lists-runtime-semantics-argumentlistevaluation */ +// Arguments : `(` `)` +// ArgumentList : +// AssignmentExpression +// `...` AssignmentExpression +// ArgumentList `,` AssignmentExpression +// ArgumentList `,` `...` AssignmentExpression +// +// (implicit) +// Arguments : +// `(` ArgumentList `)` +// `(` ArgumentList `,` `)` +function* ArgumentListEvaluation_Arguments(Arguments: ParseNode.Arguments): PlainEvaluator { + 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 = Q(yield* Evaluate(AssignmentExpression)); + // 3. Let spreadObj be ? GetValue(spreadRef). + const spreadObj = Q(yield* GetValue(spreadRef)); + // 4. Let iteratorRecord be ? GetIterator(spreadObj). + const iteratorRecord = Q(yield* GetIterator(spreadObj, 'sync')); + // 5. Repeat, + while (true) { + // a. Let next be ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // b. If next is false, return list. + if (next === 'done') { + break; + } + // d. Append next as the last element of list. + precedingArgs.push(next); + } + } else { + const AssignmentExpression = element; + // 2. Let ref be the result of evaluating AssignmentExpression. + const ref = Q(yield* Evaluate(AssignmentExpression)); + // 3. Let arg be ? GetValue(ref). + const arg = Q(yield* GetValue(ref)); + // 4. Append arg to the end of precedingArgs. + precedingArgs.push(arg); + // 5. Return precedingArgs. + } + } + return precedingArgs as Arguments; +} + +export function ArgumentListEvaluation(ArgumentsOrTemplateLiteral: ParseNode | ParseNode.Arguments) { + switch (true) { + case isArray(ArgumentsOrTemplateLiteral): + return ArgumentListEvaluation_Arguments(ArgumentsOrTemplateLiteral); + case ('type' in ArgumentsOrTemplateLiteral && ArgumentsOrTemplateLiteral.type === 'TemplateLiteral'): + return ArgumentListEvaluation_TemplateLiteral(ArgumentsOrTemplateLiteral); + default: + throw new OutOfRange('ArgumentListEvaluation', ArgumentsOrTemplateLiteral); + } +} diff --git a/src/runtime-semantics/ArrayLiteral.mts b/src/runtime-semantics/ArrayLiteral.mts new file mode 100644 index 0000000..b93769b --- /dev/null +++ b/src/runtime-semantics/ArrayLiteral.mts @@ -0,0 +1,99 @@ +import { ObjectValue, Value } from '../value.mts'; +import { + Evaluate, type PlainEvaluator, + type ValueEvaluator, +} from '../evaluator.mts'; +import { + Q, X, +} from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Set, + ArrayCreate, + GetValue, + GetIterator, + ToString, + CreateDataPropertyOrThrow, + F, + IteratorStepValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation */ +// Elision : +// `,` +// Elision `,` +// ElementList : +// Elision? AssignmentExpression +// Elision? SpreadElement +// ElementList `,` Elision? AssignmentExpression +// ElementList : ElementList `,` Elision SpreadElement +// SpreadElement : +// `...` AssignmentExpression +function* ArrayAccumulation(ElementList: ParseNode.ElementList, array: ObjectValue, nextIndex: number): PlainEvaluator { + let postIndex = nextIndex; + for (const element of ElementList) { + switch (element.type) { + case 'Elision': + postIndex += 1; + Q(yield* Set(array, Value('length'), F(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 }: ParseNode.SpreadElement, array: ObjectValue, nextIndex: number): PlainEvaluator { + // 1. Let spreadRef be the result of evaluating AssignmentExpression. + const spreadRef = Q(yield* Evaluate(AssignmentExpression)); + // 2. Let spreadObj be ? GetValue(spreadRef). + const spreadObj = Q(yield* GetValue(spreadRef)); + // 3. Let iteratorRecord be ? GetIterator(spreadObj). + const iteratorRecord = Q(yield* GetIterator(spreadObj, 'sync')); + // 4. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // b. If next is done, return nextIndex. + if (next === 'done') { + return nextIndex; + } + // d. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(nextIndex)), next). + X(CreateDataPropertyOrThrow(array, X(ToString(F(nextIndex))), next)); + // e. Set nextIndex to nextIndex + 1. + nextIndex += 1; + } +} + + +function* ArrayAccumulation_AssignmentExpression(AssignmentExpression: ParseNode.AssignmentExpressionOrHigher, array: ObjectValue, nextIndex: number): PlainEvaluator { + // 2. Let initResult be the result of evaluating AssignmentExpression. + const initResult = Q(yield* Evaluate(AssignmentExpression)); + // 3. Let initValue be ? GetValue(initResult). + const initValue = Q(yield* GetValue(initResult)); + // 4. Let created be ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(nextIndex)), initValue). + X(CreateDataPropertyOrThrow(array, X(ToString(F(nextIndex))), initValue)); + // 5. Return nextIndex + 1. + return nextIndex + 1; +} + +/** https://tc39.es/ecma262/#sec-array-initializer-runtime-semantics-evaluation */ +// ArrayLiteral : +// `[` Elision `]` +// `[` ElementList `]` +// `[` ElementList `,` Elision `]` +export function* Evaluate_ArrayLiteral({ ElementList }: ParseNode.ArrayLiteral): ValueEvaluator { + // 1. Let array be ! ArrayCreate(0). + const array = X(ArrayCreate(0)); + // 2. Let len be the result of performing ArrayAccumulation for ElementList with arguments array and 0. + const len = yield* ArrayAccumulation(ElementList, array, 0); + Q(len); + // 4. Return array. + return array; +} diff --git a/src/runtime-semantics/ArrowFunction.mts b/src/runtime-semantics/ArrowFunction.mts new file mode 100644 index 0000000..0e578cb --- /dev/null +++ b/src/runtime-semantics/ArrowFunction.mts @@ -0,0 +1,8 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateArrowFunctionExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-arrow-function-definitions-runtime-semantics-evaluation */ +export function Evaluate_ArrowFunction(ArrowFunction: ParseNode.ArrowFunction) { + // 1. Return InstantiateArrowFunctionExpression of ArrowFunction. + return InstantiateArrowFunctionExpression(ArrowFunction); +} diff --git a/src/runtime-semantics/AssignmentExpression.mts b/src/runtime-semantics/AssignmentExpression.mts new file mode 100644 index 0000000..04c43bf --- /dev/null +++ b/src/runtime-semantics/AssignmentExpression.mts @@ -0,0 +1,281 @@ +import { JSStringValue, ReferenceRecord, Value } from '../value.mts'; +import { Q, X } from '../completion.mts'; +import { + IsAnonymousFunctionDefinition, + IsIdentifierRef, + type DestructuringParseNode, + type FunctionDeclaration, +} from '../static-semantics/all.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + NamedEvaluation, + ApplyStringOrNumericBinaryOperator, + DestructuringAssignmentEvaluation, +} from './all.mts'; +import { + GetValue, + PutValue, + ToBoolean, +} from '#self'; + + +/** https://tc39.es/ecma262/#sec-destructuring-assignment */ +export function refineLeftHandSideExpression(node: ParseNode.ArrayLiteral | ParseNode.ObjectLiteral | ParseNode.PropertyDefinition | ParseNode.MemberExpression | ParseNode.CoverInitializedName | ParseNode.AssignmentExpression | ParseNode.Elision | ParseNode.IdentifierReference | ParseNode.ElementListElement | DestructuringParseNode, type?: 'array' | 'object'): ParseNode.AssignmentPattern { + switch (node.type) { + case 'ArrayLiteral': { + const refinement: ParseNode.ArrayAssignmentPattern = { + type: 'ArrayAssignmentPattern', + AssignmentElementList: [], + AssignmentRestElement: undefined, + }; + node.ElementList.forEach((n) => { + switch (n.type) { + case 'SpreadElement': + refinement.AssignmentRestElement = { + ...n, + type: 'AssignmentRestElement', + AssignmentExpression: 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: ParseNode.ObjectAssignmentPattern = { + type: 'ObjectAssignmentPattern', + AssignmentPropertyList: [], + AssignmentRestProperty: undefined, + }; + node.PropertyDefinitionList.forEach((p) => { + if ((p as ParseNode.PropertyDefinition).PropertyName === null && (p as ParseNode.PropertyDefinition).AssignmentExpression) { + refined.AssignmentRestProperty = { + type: 'AssignmentRestProperty', + DestructuringAssignmentTarget: (p as ParseNode.PropertyDefinition).AssignmentExpression, + }; + } else { + refined.AssignmentPropertyList.push(refineLeftHandSideExpression(p as ParseNode.PropertyDefinition, '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 node; + default: + throw new OutOfRange('refineLeftHandSideExpression', node.type); + } +} + +/** https://tc39.es/ecma262/#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, +}: ParseNode.AssignmentExpression): ValueEvaluator { + 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 = Q(yield* Evaluate(LeftHandSideExpression)); + Q(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 = Q(yield* NamedEvaluation(AssignmentExpression as FunctionDeclaration, (lref as ReferenceRecord).ReferencedName as JSStringValue)); + } else { // d. Else, + // i. Let rref be the result of evaluating AssignmentExpression. + const rref = Q(yield* Evaluate(AssignmentExpression)); + // ii. Let rval be ? GetValue(rref). + rval = Q(yield* GetValue(rref)); + } + // e. Perform ? PutValue(lref, rval). + Q(yield* 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 = Q(yield* Evaluate(AssignmentExpression)); + // 3. Let rval be ? GetValue(rref). + const rval = Q(yield* GetValue(rref)); + // 4. Perform ? DestructuringAssignmentEvaluation of assignmentPattern using rval as the argument. + Q(yield* DestructuringAssignmentEvaluation(assignmentPattern as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern, rval)); + // 5. Return rval. + return rval; + } else if (AssignmentOperator === '&&=') { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = Q(yield* Evaluate(LeftHandSideExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* 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 = Q(yield* NamedEvaluation(AssignmentExpression as FunctionDeclaration, (lref as ReferenceRecord).ReferencedName as JSStringValue)); + } else { // 6. Else, + // a. Let rref be the result of evaluating AssignmentExpression. + const rref = Q(yield* Evaluate(AssignmentExpression)); + // b. Let rval be ? GetValue(rref). + rval = Q(yield* GetValue(rref)); + } + // 7. Perform ? PutValue(lref, rval). + Q(yield* PutValue(lref, rval)); + // 8. Return rval. + return rval; + } else if (AssignmentOperator === '||=') { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = Q(yield* Evaluate(LeftHandSideExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* 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 = Q(yield* NamedEvaluation(AssignmentExpression as FunctionDeclaration, (lref as ReferenceRecord).ReferencedName as JSStringValue)); + } else { // 6. Else, + // a. Let rref be the result of evaluating AssignmentExpression. + const rref = Q(yield* Evaluate(AssignmentExpression)); + // b. Let rval be ? GetValue(rref). + rval = Q(yield* GetValue(rref)); + } + // 7. Perform ? PutValue(lref, rval). + Q(yield* PutValue(lref, rval)); + // 8. Return rval. + return rval; + } else if (AssignmentOperator === '??=') { + // 1.Let lref be the result of evaluating LeftHandSideExpression. + const lref = Q(yield* Evaluate(LeftHandSideExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* 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 = Q(yield* NamedEvaluation(AssignmentExpression as FunctionDeclaration, (lref as ReferenceRecord).ReferencedName as JSStringValue)); + } else { // 5. Else, + // a. Let rref be the result of evaluating AssignmentExpression. + const rref = Q(yield* Evaluate(AssignmentExpression)); + // b. Let rval be ? GetValue(rref). + rval = Q(yield* GetValue(rref)); + } + // 6. Perform ? PutValue(lref, rval). + Q(yield* PutValue(lref, rval)); + // 7. Return rval. + return rval; + } else { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = Q(yield* Evaluate(LeftHandSideExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* GetValue(lref)); + // 3. Let rref be the result of evaluating AssignmentExpression. + const rref = Q(yield* Evaluate(AssignmentExpression)); + // 4. Let rval be ? GetValue(rref). + const rval = Q(yield* 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 = ({ + '**=': '**', + '*=': '*', + '/=': '/', + '%=': '%', + '+=': '+', + '-=': '-', + '<<=': '<<', + '>>=': '>>', + '>>>=': '>>>', + '&=': '&', + '^=': '^', + '|=': '|', + } as const)[assignmentOpText]; + // 7. Let r be ApplyStringOrNumericBinaryOperator(lval, opText, rval). + const r = Q(yield* ApplyStringOrNumericBinaryOperator(lval, opText, rval)); + // 8. Perform ? PutValue(lref, r). + Q(yield* PutValue(lref, r)); + // 9. Return r. + return r; + } +} diff --git a/src/runtime-semantics/AsyncArrowFunction.mts b/src/runtime-semantics/AsyncArrowFunction.mts new file mode 100644 index 0000000..9ca5f92 --- /dev/null +++ b/src/runtime-semantics/AsyncArrowFunction.mts @@ -0,0 +1,8 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateAsyncArrowFunctionExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-async-arrow-function-definitions-runtime-semantics-evaluation */ +export function Evaluate_AsyncArrowFunction(AsyncArrowFunction: ParseNode.AsyncArrowFunction) { + // 1. Return InstantiateAsyncArrowFunctionExpression of AsyncArrowFunction. + return InstantiateAsyncArrowFunctionExpression(AsyncArrowFunction); +} diff --git a/src/runtime-semantics/AsyncFunctionExpression.mts b/src/runtime-semantics/AsyncFunctionExpression.mts new file mode 100644 index 0000000..8a12706 --- /dev/null +++ b/src/runtime-semantics/AsyncFunctionExpression.mts @@ -0,0 +1,11 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateAsyncFunctionExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-async-function-definitions-runtime-semantics-evaluation */ +// AsyncFunctionExpression : +// `async` `function` `(` FormalParameters `)` `{` AsyncBody `}` +// `async` `function` BindingIdentifier `(` FormalParameters `)` `{` AsyncBody `}` +export function Evaluate_AsyncFunctionExpression(AsyncFunctionExpression: ParseNode.AsyncFunctionExpression) { + // 1. Return InstantiateAsyncFunctionExpression of AsyncFunctionExpression. + return InstantiateAsyncFunctionExpression(AsyncFunctionExpression); +} diff --git a/src/runtime-semantics/AsyncGeneratorExpression.mts b/src/runtime-semantics/AsyncGeneratorExpression.mts new file mode 100644 index 0000000..4d36ce6 --- /dev/null +++ b/src/runtime-semantics/AsyncGeneratorExpression.mts @@ -0,0 +1,11 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateAsyncGeneratorFunctionExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-asyncgenerator-definitions-evaluation */ +// AsyncGeneratorExpression : +// `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +// `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +export function Evaluate_AsyncGeneratorExpression(AsyncGeneratorExpression: ParseNode.AsyncGeneratorExpression) { + // 1. Return InstantiateAsyncGeneratorFunctionExpression of AsyncGeneratorExpression. + return InstantiateAsyncGeneratorFunctionExpression(AsyncGeneratorExpression); +} diff --git a/src/runtime-semantics/AwaitExpression.mts b/src/runtime-semantics/AwaitExpression.mts new file mode 100644 index 0000000..7be2c27 --- /dev/null +++ b/src/runtime-semantics/AwaitExpression.mts @@ -0,0 +1,16 @@ +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Await, Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue, surroundingAgent } from '#self'; + +/** https://tc39.es/ecma262/#sec-async-function-definitions-runtime-semantics-evaluation */ +// AwaitExpression : `await` UnaryExpression +export function* Evaluate_AwaitExpression({ UnaryExpression }: ParseNode.AwaitExpression): ValueEvaluator { + Q(surroundingAgent.debugger_cannotPreview); + // 1. Let exprRef be the result of evaluating UnaryExpression. + const exprRef = Q(yield* Evaluate(UnaryExpression)); + // 2. Let value be ? GetValue(exprRef). + const value = Q(yield* GetValue(exprRef)); + // 3. Return ? Await(value). + return Q(yield* Await(value)); +} diff --git a/src/runtime-semantics/BindingInitialization.mts b/src/runtime-semantics/BindingInitialization.mts new file mode 100644 index 0000000..b218116 --- /dev/null +++ b/src/runtime-semantics/BindingInitialization.mts @@ -0,0 +1,93 @@ +import { JSStringValue, Value } from '../value.mts'; +import { + EnsureCompletion, + EnvironmentRecord, StringValue, UndefinedValue, +} from '../index.mts'; +import { NormalCompletion, Q } from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { + IteratorBindingInitialization_ArrayBindingPattern, + PropertyBindingInitialization, + RestBindingInitialization, +} from './all.mts'; +import { + Assert, + PutValue, + ResolveBinding, + RequireObjectCoercible, + GetIterator, + IteratorClose, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-initializeboundname */ +export function* InitializeBoundName(name: JSStringValue, value: Value, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator { + // 1. Assert: Type(name) is String. + Assert(name instanceof JSStringValue); + // 2. If environment is not undefined, then + if (!(environment instanceof UndefinedValue)) { + // a. Perform environment.InitializeBinding(name, value). + yield* environment.InitializeBinding(name, value); + // b. Return NormalCompletion(undefined). + return NormalCompletion(undefined); + } else { + // a. Let lhs be ResolveBinding(name). + const lhs = Q(yield* ResolveBinding(name, undefined, false)); + // b. Return ? PutValue(lhs, value). + return Q(yield* PutValue(lhs, value)); + } +} + +// ObjectBindingPattern : +// `{` `}` +// `{` BindingPropertyList `}` +// `{` BindingRestProperty `}` +// `{` BindingPropertyList `,` BindingRestProperty `}` +function* BindingInitialization_ObjectBindingPattern({ BindingPropertyList, BindingRestProperty }: ParseNode.ObjectBindingPattern, value: Value, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator { + // 1. Perform ? PropertyBindingInitialization for BindingPropertyList using value and environment as the arguments. + const excludedNames = Q(yield* PropertyBindingInitialization(BindingPropertyList, value, environment)); + if (BindingRestProperty) { + Q(yield* RestBindingInitialization(BindingRestProperty, value, environment, excludedNames)); + } + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} + +export function* BindingInitialization(node: ParseNode.ForBinding | ParseNode.ForDeclaration | ParseNode.BindingIdentifier | ParseNode.ObjectBindingPattern | ParseNode.ArrayBindingPattern | ParseNode.BindingPattern, value: Value, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator { + 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(yield* 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(yield* GetIterator(value, 'sync')); + // 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. + const result = EnsureCompletion(yield* IteratorBindingInitialization_ArrayBindingPattern(node, iteratorRecord, environment)); + // 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result). + if (iteratorRecord.Done === Value.false) { + return Q(yield* IteratorClose(iteratorRecord, result)); + } + // 4. Return ? result. + return result; + } + default: + throw new OutOfRange('BindingInitialization', node); + } +} diff --git a/src/runtime-semantics/BitwiseOperators.mts b/src/runtime-semantics/BitwiseOperators.mts new file mode 100644 index 0000000..345b7a8 --- /dev/null +++ b/src/runtime-semantics/BitwiseOperators.mts @@ -0,0 +1,14 @@ +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#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 }: ParseNode.BitwiseANDExpression | ParseNode.BitwiseXORExpression | ParseNode.BitwiseORExpression): ValueEvaluator { + return Q(yield* EvaluateStringOrNumericBinaryExpression(A, operator, B)); +} diff --git a/src/runtime-semantics/Block.mts b/src/runtime-semantics/Block.mts new file mode 100644 index 0000000..9e3dffc --- /dev/null +++ b/src/runtime-semantics/Block.mts @@ -0,0 +1,72 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { + LexicallyScopedDeclarations, + IsConstantDeclaration, + BoundNames, +} from '../static-semantics/all.mts'; +import { X, NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Evaluate_StatementList, InstantiateFunctionObject } from './all.mts'; +import { Assert, DeclarativeEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-blockdeclarationinstantiation */ +export function* BlockDeclarationInstantiation(code: ParseNode.StatementList | ParseNode.CaseBlock, env: DeclarativeEnvironmentRecord) { + // 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. Let privateEnv be the running execution context's PrivateEnvironment. + const privateEnv = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. 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, Value.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, privateEnv); + // iii. Perform env.InitializeBinding(fn, fo). + yield* env.InitializeBinding(fn, fo); + } + } + } +} + +/** https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation */ +// Block : +// `{` `}` +// `{` StatementList `}` +export function* Evaluate_Block({ StatementList }: ParseNode.Block) { + 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 = new DeclarativeEnvironmentRecord(oldEnv); + // 3. Perform BlockDeclarationInstantiation(StatementList, blockEnv). + yield* 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/src/runtime-semantics/BreakStatement.mts b/src/runtime-semantics/BreakStatement.mts new file mode 100644 index 0000000..49e1c6d --- /dev/null +++ b/src/runtime-semantics/BreakStatement.mts @@ -0,0 +1,18 @@ +import { Completion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { StringValue } from '../static-semantics/all.mts'; + +/** https://tc39.es/ecma262/#sec-break-statement-runtime-semantics-evaluation */ +// BreakStatement : +// `break` `;` +// `break` LabelIdentifier `;` +export function Evaluate_BreakStatement({ LabelIdentifier }: ParseNode.BreakStatement) { + 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/src/runtime-semantics/BreakableStatement.mts b/src/runtime-semantics/BreakableStatement.mts new file mode 100644 index 0000000..ccf7498 --- /dev/null +++ b/src/runtime-semantics/BreakableStatement.mts @@ -0,0 +1,18 @@ +import { JSStringSet } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { LabelledEvaluation } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation */ +// BreakableStatement : +// IterationStatement +// SwitchStatement +// +// IterationStatement : +// (DoStatement) +// (WhileStatement) +export function Evaluate_BreakableStatement(BreakableStatement: ParseNode.BreakableStatement) { + // 1. Let newLabelSet be a new empty List. + const newLabelSet = new JSStringSet(); + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return LabelledEvaluation(BreakableStatement, newLabelSet); +} diff --git a/src/runtime-semantics/CallExpression.mts b/src/runtime-semantics/CallExpression.mts new file mode 100644 index 0000000..1e22dc5 --- /dev/null +++ b/src/runtime-semantics/CallExpression.mts @@ -0,0 +1,57 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value, ReferenceRecord, JSStringValue } from '../value.mts'; +import { IsInTailPosition } from '../static-semantics/all.mts'; +import { Q } from '../completion.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateCall, ArgumentListEvaluation } from './all.mts'; +import { + GetValue, + IsPropertyReference, + PerformEval, + SameValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation */ +// CallExpression : +// CoverCallExpressionAndAsyncArrowHead +// CallExpression Arguments +export function* Evaluate_CallExpression(CallExpression: ParseNode.CallExpression): ValueEvaluator { + // 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 = Q(yield* Evaluate(memberExpr)); + // 5. Let func be ? GetValue(ref). + const func = Q(yield* GetValue(ref)); + // 6. If Type(ref) is Reference, IsPropertyReference(ref) is false, and GetReferencedName(ref) is "eval", then + if (ref instanceof ReferenceRecord + && IsPropertyReference(ref) === Value.false + && (ref.ReferencedName instanceof JSStringValue + && ref.ReferencedName.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; + // vi. Return ? PerformEval(evalText, strictCaller, true). + return Q(yield* PerformEval(evalText, 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/src/runtime-semantics/ClassDeclaration.mts b/src/runtime-semantics/ClassDeclaration.mts new file mode 100644 index 0000000..4e88c1b --- /dev/null +++ b/src/runtime-semantics/ClassDeclaration.mts @@ -0,0 +1,41 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { Q, NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { + InitializeBoundName, ClassDefinitionEvaluation, type DecoratorDefinitionRecord, DecoratorListEvaluation, +} from './all.mts'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-bindingclassdeclarationevaluation */ +// ClassDeclaration : +// `class` BindingIdentifier ClassTail +// `class` ClassTail +export function* BindingClassDeclarationEvaluation(ClassDeclaration: ParseNode.ClassDeclaration, decorators: readonly DecoratorDefinitionRecord[]): ValueEvaluator { + const { BindingIdentifier, ClassTail } = ClassDeclaration; + const sourceText = ClassDeclaration.sourceText; + if (!BindingIdentifier) { + return Q(yield* ClassDefinitionEvaluation(ClassTail, Value.undefined, Value('default'), sourceText, decorators)); + } + // 1. Let className be StringValue of BindingIdentifier. + const className = StringValue(BindingIdentifier); + // 2. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments className, className, decorators. + const value = Q(yield* ClassDefinitionEvaluation(ClassTail, className, className, sourceText, decorators)); + // 4. Let env be the running execution context's LexicalEnvironment. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 5. Perform ? InitializeBoundName(className, value, env). + Q(yield* InitializeBoundName(className, value, env)); + // 6. Return value. + return value; +} + +/** https://tc39.es/ecma262/#sec-class-definitions-runtime-semantics-evaluation */ +// ClassDeclaration : `class` BindingIdentifier ClassTAil +export function* Evaluate_ClassDeclaration(ClassDeclaration: ParseNode.ClassDeclaration): PlainEvaluator { + const decorators = ClassDeclaration.Decorators ? Q(yield* DecoratorListEvaluation(ClassDeclaration.Decorators)) : []; + // 1. Perform ? BindingClassDeclarationEvaluation of this ClassDeclaration. + Q(yield* BindingClassDeclarationEvaluation(ClassDeclaration, decorators)); + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/ClassDefinitionEvaluation.mts b/src/runtime-semantics/ClassDefinitionEvaluation.mts new file mode 100644 index 0000000..e931f72 --- /dev/null +++ b/src/runtime-semantics/ClassDefinitionEvaluation.mts @@ -0,0 +1,805 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, NullValue, ObjectValue, PrivateName, + BooleanValue, + JSStringValue, + type Arguments, + type FunctionCallContext, + UndefinedValue, + type PropertyKeyValue, + ReferenceRecord, + SymbolValue, +} from '../value.mts'; +import { Evaluate, type PlainEvaluator, type ValueEvaluator } from '../evaluator.mts'; +import { + IsStatic, + ConstructorMethod, + NonConstructorElements, + PrivateBoundIdentifiers, +} from '../static-semantics/all.mts'; +import { + Q, X, + AbruptCompletion, +} from '../completion.mts'; +import { __ts_cast__, OutOfRange, type Mutable } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + DefineMethod, + MethodDefinitionEvaluation, + ClassFieldDefinitionEvaluation, + PrivateElementRecord, + ClassFieldDefinitionRecord, + ClassStaticBlockDefinitionEvaluation, + ClassStaticBlockDefinitionRecord, + ClassFieldDefinitionEvaluation_decorator, +} from './all.mts'; +import { + Assert, + Call, + Construct, + CreateBuiltinFunction, + Get, + GetValue, + IsConstructor, + MakeConstructor, + MakeClassConstructor, + SetFunctionName, + CreateMethodProperty, + OrdinaryObjectCreate, + OrdinaryCreateFromConstructor, + PrivateMethodOrAccessorAdd, + InitializeInstanceElements, + DefineField, + type ECMAScriptFunctionObject, + type BuiltinFunctionObject, + type FunctionObject, + DefineMethodProperty, + IsCallable, +} from '#self'; +import { + DeclarativeEnvironmentRecord, + PrivateEnvironmentRecord, + + CreateDataPropertyOrThrow, HasProperty, InitializeFieldOrAccessor, InitializePrivateMethods, IsPropertyKey, markBuiltinFunctionAsConstructor, PrivateElementFind, PrivateGet, PrivateSet, Set, Throw, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-static-semantics-classelementevaluation */ +// -decorator +function ClassElementEvaluation(node: ParseNode.MethodDefinition | ParseNode.GeneratorMethod | ParseNode.AsyncMethod | ParseNode.AsyncGeneratorMethod | ParseNode.FieldDefinition | ParseNode.ClassStaticBlock, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator +// +decorator +function ClassElementEvaluation(node: ParseNode.MethodDefinition | ParseNode.GeneratorMethod | ParseNode.AsyncMethod | ParseNode.AsyncGeneratorMethod | ParseNode.FieldDefinition | ParseNode.ClassStaticBlock, object: ObjectValue): PlainEvaluator +function* ClassElementEvaluation(node: ParseNode.MethodDefinition | ParseNode.GeneratorMethod | ParseNode.AsyncMethod | ParseNode.AsyncGeneratorMethod | ParseNode.FieldDefinition | ParseNode.ClassStaticBlock, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator { + switch (node.type) { + case 'MethodDefinition': + case 'GeneratorMethod': + case 'AsyncMethod': + case 'AsyncGeneratorMethod': { + if (surroundingAgent.feature('decorators')) { + const decorators = node.Decorators ? Q(yield* DecoratorListEvaluation(node.Decorators)) : []; + const methodDefinition = Q(yield* MethodDefinitionEvaluation(node, object)); + methodDefinition.Decorators = decorators; + return methodDefinition; + } else { + return yield* MethodDefinitionEvaluation(node, object, enumerable!); + } + } + case 'FieldDefinition': { + if (surroundingAgent.feature('decorators')) { + const decorators = node.Decorators ? Q(yield* DecoratorListEvaluation(node.Decorators)) : []; + const fieldDefinition = Q(yield* ClassFieldDefinitionEvaluation_decorator(node, object)); + fieldDefinition.Decorators = decorators; + return fieldDefinition; + } else { + return yield* ClassFieldDefinitionEvaluation(node, object); + } + } + case 'ClassStaticBlock': + return ClassStaticBlockDefinitionEvaluation(node, object); + default: + throw new OutOfRange('ClassElementEvaluation', node); + } +} + +export interface DefaultConstructorBuiltinFunction extends BuiltinFunctionObject { + // -decorator + readonly PrivateMethods: ECMAScriptFunctionObject['PrivateMethods']; + readonly Fields: ECMAScriptFunctionObject['Fields']; + // +decorator (PrivateMethods => Initializers, Fields => Elements) + readonly Initializers: ECMAScriptFunctionObject['Initializers']; + readonly Elements: ECMAScriptFunctionObject['Elements']; + readonly SourceText: ECMAScriptFunctionObject['SourceText']; + readonly ConstructorKind: ECMAScriptFunctionObject['ConstructorKind']; + /** + * Note: this is different than InitialName, which is used and observable in Function.prototype.toString. + * This is only used in the inspector. + */ + readonly HostInitialName: PropertyKeyValue | PrivateName; +} + +// ClassTail : ClassHeritage? `{` ClassBody? `}` +/** https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation */ +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-runtime-semantics-classdefinitionevaluation */ +export function* ClassDefinitionEvaluation(ClassTail: ParseNode.ClassTail, classBinding: JSStringValue | UndefinedValue, className: PropertyKeyValue | PrivateName, sourceText: string, decorators: readonly DecoratorDefinitionRecord[]): ValueEvaluator { + 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 = new DeclarativeEnvironmentRecord(env); + // 3. If classBinding is not undefined, then + if (!(classBinding instanceof UndefinedValue)) { + // a. Perform classScopeEnv.CreateImmutableBinding(classBinding, true). + classScope.CreateImmutableBinding(classBinding, Value.true); + } + // 4. Let outerPrivateEnvironment be the running execution context's PrivateEnvironment. + const outerPrivateEnvironment = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 5. Let classPrivateEnvironment be NewPrivateEnvironment(outerPrivateEnvironment). + const classPrivateEnvironment = new PrivateEnvironmentRecord(outerPrivateEnvironment); + // 6. If ClassBody is present, then + if (ClassBody) { + // a. For each String dn of the PrivateBoundIdentifiers of ClassBody, do + for (const dn of PrivateBoundIdentifiers(ClassBody)) { + // i. If classPrivateEnvironment.[[Names]] contains a Private Name whose [[Description]] is dn, then + const existing = classPrivateEnvironment.Names.find((n) => n.Description.stringValue() === dn.stringValue()); + if (existing) { + // 1. Assert: This is only possible for getter/setter pairs. + } else { // ii. Else, + // 1. Let name be a new Private Name whose [[Description]] value is dn. + const name = new PrivateName(dn); + // 2. Append name to classPrivateEnvironment.[[Names]]. + classPrivateEnvironment.Names.push(name); + } + } + } + let protoParent; + let constructorParent: ObjectValue; + // 7. 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 { // 8. 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 = Q(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(yield* GetValue(superclassRef)); + // e. If superclass is null, then + if (superclass instanceof NullValue) { + // i. Let protoParent be null. + protoParent = Value.null; + // ii. Let constructorParent be %Function.prototype%. + constructorParent = surroundingAgent.intrinsic('%Function.prototype%'); + } else if (!IsConstructor(superclass)) { + // 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(yield* Get(superclass as ObjectValue, Value('prototype'))); + // ii. If Type(protoParent) is neither Object nor Null, throw a TypeError exception. + if (!(protoParent instanceof ObjectValue) && !(protoParent instanceof NullValue)) { + return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType'); + } + // iii. Let constructorParent be superclass. + constructorParent = superclass as ObjectValue; + } + } + // 9. Let proto be OrdinaryObjectCreate(protoParent). + const proto = OrdinaryObjectCreate(protoParent); + let constructor; + // 10. If ClassBody is not present, let constructor be empty. + if (!ClassBody) { + constructor = undefined; + } else { // 11. Else, let constructor be ConstructorMethod of ClassBody. + constructor = ConstructorMethod(ClassBody); + } + // 12. Set the running execution context's LexicalEnvironment to classScope. + surroundingAgent.runningExecutionContext.LexicalEnvironment = classScope; + // 13. Set the running execution context's PrivateEnvironment to classPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = classPrivateEnvironment; + let F; + // 14. If constructor is empty, then + if (constructor === undefined) { + // a. Let defaultConstructor be a new Abstract Closure with no parameters that captures nothing and performs the following steps when called: + const defaultConstructor = function* defaultConstructor(args: Arguments, { NewTarget }: FunctionCallContext) { + // i. Let args be the List of arguments that was passed to this function by [[Call]] or [[Construct]]. + // ii. If NewTarget is undefined, throw a TypeError exception. + if (NewTarget instanceof UndefinedValue) { + return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', surroundingAgent.activeFunctionObject); + } + // iii. Let F be the active function object. + const F = surroundingAgent.activeFunctionObject as ECMAScriptFunctionObject; // eslint-disable-line no-shadow + let result; + // iv. If F.[[ConstructorKind]] is derived, then + if (F.ConstructorKind === 'derived') { + // 1. NOTE: This branch behaves similarly to `constructor(...args) { super(...args); }`. The most + // notable distinction is that while the aforementioned ECMAScript source text observably calls + // the @@iterator method on `%Array.prototype%`, a Default Constructor Function does not. + // 2. Let func be ! F.[[GetPrototypeOf]](). + const func = X(yield* F.GetPrototypeOf()); + // 3. If IsConstructor(func) is false, throw a TypeError exception. + if (!IsConstructor(func)) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', func); + } + // 4. Let result be ? Construct(func, args, NewTarget). + result = Q(yield* Construct(func, args, NewTarget)); + } else { // v. Else, + // 1. NOTE: This branch behaves similarly to `constructor() {}`. + // 2. Let result be ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%"). + result = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Object.prototype%')); + } + Q(yield* InitializeInstanceElements(result, F)); + return result; + }; + // b. ! CreateBuiltinFunction(defaultConstructor, 0, className, « [[ConstructorKind]], [[SourceText]], [[PrivateMethods]], [[Fields]] », the current Realm Record, constructorParent). + F = X(CreateBuiltinFunction(markBuiltinFunctionAsConstructor(defaultConstructor), 0, className, ['ConstructorKind', 'SourceText', surroundingAgent.feature('decorators') ? 'Initializers' : 'PrivateMethods', surroundingAgent.feature('decorators') ? 'Elements' : 'Fields'], surroundingAgent.currentRealmRecord, constructorParent)); + } else { // 15. Else, + // a. Let constructorInfo be ! DefineMethod of constructor with arguments proto and constructorParent. + const constructorInfo = X(yield* DefineMethod(constructor, proto, constructorParent)); + // b. Let F be constructorInfo.[[Closure]]. + F = constructorInfo.Closure; + // c. Perform SetFunctionName(F, className). + SetFunctionName(F, className); + } + __ts_cast__>(F); + F.HostInitialName = className; + F.SourceText = sourceText; + // 16. Perform MakeConstructor(F, false, proto). + MakeConstructor(F, Value.false, proto); + // https://github.com/tc39/ecma262/pull/3212/ + // 17. Perform MakeClassConstructor(F). + MakeClassConstructor(F); + // 18. If ClassHeritage is present, set F.[[ConstructorKind]] to derived. + if (ClassHeritage) { + F.ConstructorKind = 'derived'; + } + // 19. Perform CreateMethodProperty(proto, "constructor", F). + X(CreateMethodProperty(proto, Value('constructor'), F)); + // 20. If ClassBody is not present, let elements be a new empty List. + let elements: ParseNode.ClassElement[]; + if (!ClassBody) { + elements = []; + } else { // 20. Else, let elements be NonConstructorElements of ClassBody. + elements = NonConstructorElements(ClassBody); + } + if (surroundingAgent.feature('decorators')) { + const instanceElements: ClassElementDefinitionRecord[] = []; + // 24. Let staticElements be a new empty List. + const staticElements: (ClassElementDefinitionRecord | ClassStaticBlockDefinitionRecord)[] = []; + // 25. For each ClassElement e of elements, do + for (const e of elements) { + let result; + // a. If IsStatic of e is false, then + if (!IsStatic(e)) { + result = yield* ClassElementEvaluation(e, proto); + } else { + result = yield* ClassElementEvaluation(e, F); + } + // c. If field is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // i. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + // ii. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + const element = X(result); + if (element instanceof ClassElementDefinitionRecord) { + if (!IsStatic(e)) { + instanceElements.push(element); + } else { + staticElements.push(element); + } + } else { + Assert(element instanceof ClassStaticBlockDefinitionRecord); + staticElements.push(element); + } + } + // 26. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + const instanceMethodExtraInitializers: FunctionObject[] = []; + const staticMethodExtraInitializers: FunctionObject[] = []; + for (const e of staticElements) { + if (e instanceof ClassElementDefinitionRecord && e.Kind !== 'field') { + let extraInitializers: FunctionObject[]; + if (e.Kind === 'accessor') { + extraInitializers = e.ExtraInitializers; + } else { + extraInitializers = staticMethodExtraInitializers; + } + const result = yield* ApplyDecoratorsAndDefineMethod(F, e, extraInitializers, true); + if (result instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + } + } + for (const e of instanceElements) { + let extraInitializers: FunctionObject[]; + if (e.Kind !== 'field') { + if (e.Kind === 'accessor') { + extraInitializers = e.ExtraInitializers; + } else { + extraInitializers = instanceMethodExtraInitializers; + } + const result = yield* ApplyDecoratorsAndDefineMethod(proto, e, extraInitializers, false); + if (result instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + } + } + for (const e of staticElements) { + if (e instanceof ClassElementDefinitionRecord && e.Kind === 'field') { + const result = yield* ApplyDecoratorsToElementDefinition(F, e, e.ExtraInitializers, true); + if (result instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + } + } + for (const e of instanceElements) { + if (e.Kind === 'field') { + const result = yield* ApplyDecoratorsToElementDefinition(proto, e, e.ExtraInitializers, false); + if (result instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + } + } + F.Elements = instanceElements; + F.Initializers = instanceMethodExtraInitializers; + // TODO(decorator): spec bug? + // Q(yield* InitializePrivateMethods(F, staticElements)); + Q(yield* InitializePrivateMethods(F, staticElements.filter((element): element is ClassElementDefinitionRecord => element instanceof ClassElementDefinitionRecord))); + const classExtraInitializers: FunctionObject[] = []; + const newF = yield* ApplyDecoratorsToClassDefinition(F, decorators, className, classExtraInitializers); + if (newF instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return newF; + } + F = Q(newF); + // 27. If classBinding is not undefined, then + if (!(classBinding instanceof UndefinedValue)) { + // a. Perform classScope.InitializeBinding(classBinding, F). + yield* classScope.InitializeBinding(classBinding, F); + } + for (const initializer of staticMethodExtraInitializers) { + const result = yield* Call(initializer, F); + if (result instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + } + // 31. For each element elementRecord of staticElements, do + for (const elementRecord of staticElements) { + let result; + // a. If elementRecord is a ClassFieldDefinition Record, then + if (elementRecord instanceof ClassElementDefinitionRecord && (elementRecord.Kind === 'field' || elementRecord.Kind === 'accessor')) { + // a. Let result be DefineField(F, elementRecord). + result = yield* InitializeFieldOrAccessor(F, elementRecord); + } else if (elementRecord instanceof ClassStaticBlockDefinitionRecord) { + result = yield* Call(elementRecord.BodyFunction, F); + } + // c. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // i. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + // ii. Return result. + return result; + } + } + for (const initializer of classExtraInitializers) { + const result = yield* Call(initializer, F); + if (result instanceof AbruptCompletion) { + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + return result; + } + } + // 32. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + // 33. Return F. + return F; + } else { + // 21. Let instancePrivateMethods be a new empty List. + const instancePrivateMethods: never[] = []; + // 22. Let staticPrivateMethods be a new empty List. + const staticPrivateMethods: never[] = []; + // 23. Let instanceFields be a new empty List. + const instanceFields: ClassFieldDefinitionRecord[] = []; + // 24. Let staticElements be a new empty List. + const staticElements: (ClassFieldDefinitionRecord | ClassStaticBlockDefinitionRecord)[] = []; + // 25. For each ClassElement e of elements, do + for (const e of elements) { + let field; + // a. If IsStatic of e is false, then + if (IsStatic(e) === false) { + // i. Let field be ClassElementEvaluation of e with arguments proto and false. + field = (yield* ClassElementEvaluation(e, proto, Value.false))!; + } else { // b. Else, + // i. Let field be ClassElementEvaluation of e with arguments F and false. + field = (yield* ClassElementEvaluation(e, F, Value.false))!; + } + // c. If field is an abrupt completion, then + if (field instanceof AbruptCompletion) { + // i. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + // ii. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + // iii. Return Completion(field). + return field; + } + // d. Set field to field.[[Value]]. + Q(field); + // e. If field is a PrivateElement, then + if (field instanceof PrivateElementRecord) { + // i. Assert: field.[[Kind]] is either method or accessor. + Assert(field.Kind === 'method' || field.Kind === 'accessor'); + // ii. If IsStatic of e is false, let container be instancePrivateMethods. + let container: PrivateElementRecord[]; + if (IsStatic(e) === false) { + container = instancePrivateMethods; + } else { // iii. Else, let container be staticPrivateMethods. + container = staticPrivateMethods; + } + // iv. If container contains a PrivateElement whose [[Key]] is field.[[Key]], then + const index = container.findIndex((el) => el.Key === field.Key); + if (index >= 0) { + // 1. Let existing be that PrivateElement. + const existing = container[index]; + // 2. Assert: field.[[Kind]] and existing.[[Kind]] are both accessor. + Assert(field.Kind === 'accessor' && existing.Kind === 'accessor'); + // 3. If field.[[Get]] is undefined, then + let combined; + if (field.Get === Value.undefined) { + combined = PrivateElementRecord({ + Key: field.Key, + Kind: 'accessor', + Get: existing.Get, + Set: field.Set, + }); + } else { // 4. Else + combined = PrivateElementRecord({ + Key: field.Key, + Kind: 'accessor', + Get: field.Get, + Set: existing.Set, + }); + } + // 5. Replace existing in container with combined. + container[index] = combined; + } else { // v. Else, + // 1. Append field to container. + container.push(field); + } + } else if (field instanceof ClassFieldDefinitionRecord) { // f. Else if field is a ClassFieldDefinition Record, then + // i. If IsStatic of e is false, append field to instanceFields. + if (IsStatic(e) === false) { + instanceFields.push(field); + } else { // ii. Else, append field to staticElements. + staticElements.push(field); + } + } else if (field instanceof ClassStaticBlockDefinitionRecord) { // g. Else if element is a ClassStaticBlockDefinition Record, then + // i. Append element to staticElements. + staticElements.push(field); + } + } + // 26. Set the running execution context's LexicalEnvironment to env. + surroundingAgent.runningExecutionContext.LexicalEnvironment = env; + // 27. If classBinding is not undefined, then + if (!(classBinding instanceof UndefinedValue)) { + // a. Perform classScope.InitializeBinding(classBinding, F). + yield* classScope.InitializeBinding(classBinding, F); + } + // 28. Set F.[[PrivateMethods]] to instancePrivateMethods. + F.PrivateMethods = instancePrivateMethods; + // 29. Set F.[[Fields]] to instanceFields. + F.Fields = instanceFields; + // 30. For each PrivateElement method of staticPrivateMethods, do + for (const method of staticPrivateMethods) { + // a. Perform ! PrivateMethodOrAccessorAdd(F, method). + Q(yield* PrivateMethodOrAccessorAdd(F, method)); + } + // 31. For each element elementRecord of staticElements, do + for (const elementRecord of staticElements) { + let result; + // a. If elementRecord is a ClassFieldDefinition Record, then + if (elementRecord instanceof ClassFieldDefinitionRecord) { + // a. Let result be DefineField(F, elementRecord). + result = yield* DefineField(F, elementRecord); + } else { // b. Else, + // i. Assert: elementRecord is a ClassStaticBlockDefinition Record. + Assert(elementRecord instanceof ClassStaticBlockDefinitionRecord); + // ii. Let result be Completion(Call(elementRecord.[[BodyFunction]], F)). + result = yield* Call(elementRecord.BodyFunction, F); + } + // c. If result is an abrupt completion, then + if (result instanceof AbruptCompletion) { + // i. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + // ii. Return result. + return result; + } + } + // 32. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment. + surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment; + // 33. Return F. + return F; + } +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-decoratorevaluation */ +export function* DecoratorEvaluation(decorator: ParseNode.Decorator): PlainEvaluator { + const expr = decorator.MemberExpression || decorator.CallExpression || decorator.ParenthesizedExpression; + const ref = Q(yield* Evaluate(expr)); + const value = Q(yield* GetValue(ref)); + return { Decorator: value, Receiver: ref }; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-decoratorelistvaluation */ +export function* DecoratorListEvaluation(decoratorList: readonly ParseNode.Decorator[]): PlainEvaluator { + const decorators: DecoratorDefinitionRecord[] = []; + for (const decoratorNode of decoratorList) { + const decoratorRecord = Q(yield* DecoratorEvaluation(decoratorNode)); + decorators.unshift(decoratorRecord); + } + return decorators; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createdecoratoraccessobject */ +export function CreateDecoratorAccessObject(kind: ClassElementDefinitionRecord['Kind'], name: PropertyKeyValue | PrivateName): ObjectValue { + const accessObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + if (kind === 'field' || kind === 'method' || kind === 'accessor' || kind === 'getter') { + const getterClosure = function* getter([obj = Value.undefined]: Arguments) { + if (!(obj instanceof ObjectValue)) { + return Throw.TypeError('Invalid receiver'); + } + if (IsPropertyKey(name)) { + return Q(yield* Get(obj, name)); + } else { + return Q(yield* PrivateGet(obj, name)); + } + }; + const getter = CreateBuiltinFunction(getterClosure, 1, Value(''), []); + X(CreateDataPropertyOrThrow(accessObj, Value('get'), getter)); + } + if (kind === 'field' || kind === 'accessor' || kind === 'setter') { + const setterClosure = function* setter([obj = Value.undefined, value = Value.undefined]: Arguments) { + if (!(obj instanceof ObjectValue)) { + return Throw.TypeError('Invalid receiver'); + } + if (IsPropertyKey(name)) { + return Q(yield* Set(obj, name, value, Value.true)); + } else { + return Q(yield* PrivateSet(obj, name, value)); + } + }; + const setter = CreateBuiltinFunction(setterClosure, 2, Value(''), []); + X(CreateDataPropertyOrThrow(accessObj, Value('set'), setter)); + } + const hasClosure = function* has(this: Value, [obj = Value.undefined]: Arguments) { + if (!(obj instanceof ObjectValue)) { + return Throw.TypeError('Invalid receiver'); + } + if (IsPropertyKey(name)) { + return Q(yield* HasProperty(obj, name)); + } + if (PrivateElementFind(name, obj)) { + return Value.true; + } + return Value.false; + }; + const has = CreateBuiltinFunction(hasClosure, 1, Value('has'), []); + X(CreateDataPropertyOrThrow(accessObj, Value('has'), has)); + return accessObj; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createaddinitializerfunction */ +// TODO(decorator): spec bug, initializers should not require ECMAScriptFunctionObject +export function CreateAddInitializerFunction(initializers: FunctionObject[], decorationState: { Finished: boolean }): FunctionObject { + const addInitializerClosure = function* addInitializer(this: Value, [initializer = Value.undefined]: Arguments) { + if (decorationState.Finished) { + return Throw.TypeError('Cannot call addInitializer after decoration is finished'); + } + if (!IsCallable(initializer)) { + return Throw.TypeError('addInitializer must be called with a function, but $1 was passed', initializer); + } + initializers.push(initializer); + return Value.undefined; + }; + return CreateBuiltinFunction(addInitializerClosure, 1, Value('addInitializer'), []); +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createdecoratorcontextobject */ +export function CreateDecoratorContextObject(kind: 'class' | ClassElementDefinitionRecord['Kind'], name: PropertyKeyValue | PrivateName, initializers: FunctionObject[], decorationState: { Finished: boolean }, isStatic?: boolean): ObjectValue { + const contextObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + const kindStr = Value(kind); + X(CreateDataPropertyOrThrow(contextObj, Value('kind'), kindStr)); + if (kind !== 'class') { + X(CreateDataPropertyOrThrow(contextObj, Value('access'), CreateDecoratorAccessObject(kind, name))); + if (isStatic !== undefined) { + X(CreateDataPropertyOrThrow(contextObj, Value('static'), Value(isStatic))); + } + if (name instanceof PrivateName) { + X(CreateDataPropertyOrThrow(contextObj, Value('private'), Value.true)); + X(CreateDataPropertyOrThrow(contextObj, Value('name'), name.Description)); + } else { + X(CreateDataPropertyOrThrow(contextObj, Value('private'), Value.false)); + X(CreateDataPropertyOrThrow(contextObj, Value('name'), name)); + } + } else { + // TODO(decorator): spec bug, no assert to the name + X(CreateDataPropertyOrThrow(contextObj, Value('name'), name as PropertyKeyValue)); + } + const addInitializer = CreateAddInitializerFunction(initializers, decorationState); + X(CreateDataPropertyOrThrow(contextObj, Value('addInitializer'), addInitializer)); + return contextObj; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-applydecoratorstoelementdefinition */ +// TODO(decorator): unused parameter in the spec +export function* ApplyDecoratorsToElementDefinition(_homeObject: ObjectValue, elementRecord: ClassElementDefinitionRecord, extraInitializers: FunctionObject[], isStatic: boolean): PlainEvaluator { + const decorators = elementRecord.Decorators; + if (!decorators || decorators.length === 0) { + return undefined; + } + const key = elementRecord.Key; + const kind = elementRecord.Kind; + for (const decoratorRecord of decorators) { + const decorator = decoratorRecord.Decorator; + const decoratorReceiver = decoratorRecord.Receiver; + const decorationState = { Finished: false }; + const context = CreateDecoratorContextObject(kind, key, extraInitializers, decorationState, isStatic); + let value: Value = Value.undefined; + if (kind === 'method') { + value = elementRecord.Value; + } else if (kind === 'getter') { + value = elementRecord.Get; + } else if (kind === 'setter') { + value = elementRecord.Set; + } else if (kind === 'accessor') { + value = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + X(CreateDataPropertyOrThrow(value, Value('get'), elementRecord.Get)); + X(CreateDataPropertyOrThrow(value, Value('set'), elementRecord.Set)); + } + // TODO(decorator): spec bug, missing GetValue call + // const newValue = Q(yield* Call(decorator, decoratorReceiver), [value, context])); + const newValue = Q(yield* Call(decorator, Q(yield* GetValue(decoratorReceiver)), [value, context])); + decorationState.Finished = true; + if (kind === 'field') { + if (IsCallable(newValue)) { + // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) + elementRecord.Initializers.unshift(newValue); + } else if (newValue !== Value.undefined) { + return Throw.TypeError('Field decorator must return a function or undefined, but $1 was returned', newValue); + } + } else if (kind === 'accessor') { + if (newValue instanceof ObjectValue) { + const newGetter = Q(yield* Get(newValue, Value('get'))); + if (IsCallable(newGetter)) { + elementRecord.Get = newGetter; + } else if (newGetter !== Value.undefined) { + return Throw.TypeError('The get property of the return value of an accessor decorator must be a function or undefined, but $1 was returned', newGetter); + } + const newSetter = Q(yield* Get(newValue, Value('set'))); + if (IsCallable(newSetter)) { + elementRecord.Set = newSetter; + } else if (newSetter !== Value.undefined) { + return Throw.TypeError('The set property of the return value of an accessor decorator must be a function or undefined, but $1 was returned', newSetter); + } + const initializer = Q(yield* Get(newValue, Value('init'))); + if (IsCallable(initializer)) { + // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) + elementRecord.Initializers.unshift(initializer); + } else if (initializer !== Value.undefined) { + return Throw.TypeError('The init property of the return value of an accessor decorator must be a function or undefined, but $1 was returned', initializer); + } + } else if (newValue !== Value.undefined) { + return Throw.TypeError('Accessor decorator must return an object or undefined, but $1 was returned', newValue); + } + } else { + if (IsCallable(newValue)) { + if (kind === 'getter') { + elementRecord.Get = newValue; + } else if (kind === 'setter') { + elementRecord.Set = newValue; + } else { + elementRecord.Value = newValue; + } + } else if (newValue !== Value.undefined) { + return Throw.TypeError('Method decorator must return a function or undefined, but $1 was returned', newValue); + } + } + } + elementRecord.Decorators = undefined; + return undefined; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-applydecoratorstoclassdefinition */ +export function* ApplyDecoratorsToClassDefinition(classDef: FunctionObject, decorators: readonly DecoratorDefinitionRecord[], className: PropertyKeyValue | PrivateName, extraInitializers: FunctionObject[]): PlainEvaluator { + for (const decoratorRecord of decorators) { + const decorator = decoratorRecord.Decorator; + const decoratorReceiver = decoratorRecord.Receiver; + const decorationState = { Finished: false }; + const context = CreateDecoratorContextObject('class', className, extraInitializers, decorationState); + // TODO(decorator): spec bug, missing GetValue call + // const newDef = Q(yield* Call(decorator, decoratorReceiver, [classDef, context])); + const newDef = Q(yield* Call(decorator, Q(yield* GetValue(decoratorReceiver)), [classDef, context])); + decorationState.Finished = true; + if (IsCallable(newDef)) { + classDef = newDef; + } else if (newDef !== Value.undefined) { + return Throw.TypeError('Class decorator must return a function or undefined, but $1 was returned', newDef); + } + } + return classDef; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-applydecoratorsanddefinemethod */ +export function* ApplyDecoratorsAndDefineMethod(homeObject: ObjectValue, methodDefinition: ClassElementDefinitionRecord, extraInitializers: FunctionObject[], isStatic: boolean): PlainEvaluator { + Q(yield* ApplyDecoratorsToElementDefinition(homeObject, methodDefinition, extraInitializers, isStatic)); + // TODO(decorator): spec bug, enumerable of class methods, whether decorated or not, should always be false + // Q(yield* DefineMethodProperty(homeObject, methodDefinition, isStatic)); + Q(yield* DefineMethodProperty(homeObject, methodDefinition, false)); +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-decoratordefinition-record-specification-type */ +export interface DecoratorDefinitionRecord { + readonly Decorator: Value; + readonly Receiver: ReferenceRecord | Value; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-classfielddefinition-record-specification-type */ +export type ClassElementDefinitionRecord = ClassElementDefinitionRecord_Method | ClassElementDefinitionRecord_Field | ClassElementDefinitionRecord_Accessor | ClassElementDefinitionRecord_Getter | ClassElementDefinitionRecord_Setter; +export interface ClassElementDefinitionRecord_Method { + readonly Kind: 'method'; + readonly Key: PrivateName | JSStringValue | SymbolValue; + // TODO(decorator): spec bug, spec is ECMAScriptFunctionObject + Value: FunctionObject; + Decorators: DecoratorDefinitionRecord[] | undefined; +} +export interface ClassElementDefinitionRecord_Field { + readonly Kind: 'field'; + readonly Key: PrivateName | JSStringValue | SymbolValue; + Decorators: DecoratorDefinitionRecord[] | undefined; + readonly Initializers: FunctionObject[]; + readonly ExtraInitializers: FunctionObject[]; +} +export interface ClassElementDefinitionRecord_Accessor { + readonly Kind: 'accessor'; + readonly Key: PrivateName | JSStringValue | SymbolValue; + // https://github.com/tc39/proposal-decorators/issues/572 + Get: FunctionObject; + // https://github.com/tc39/proposal-decorators/issues/572 + Set: FunctionObject; + readonly BackingStorageKey: PrivateName; + Decorators: readonly DecoratorDefinitionRecord[] | undefined; + readonly Initializers: FunctionObject[]; + readonly ExtraInitializers: FunctionObject[]; +} +export interface ClassElementDefinitionRecord_Getter { + readonly Kind: 'getter'; + readonly Key: PrivateName | JSStringValue | SymbolValue; + // https://github.com/tc39/proposal-decorators/issues/572 + Get: FunctionObject; + Decorators: readonly DecoratorDefinitionRecord[] | undefined; +} +export interface ClassElementDefinitionRecord_Setter { + readonly Kind: 'setter'; + readonly Key: PrivateName | JSStringValue | SymbolValue; + // https://github.com/tc39/proposal-decorators/issues/572 + Set: FunctionObject; + Decorators: readonly DecoratorDefinitionRecord[] | undefined; +} + +// This is a struct defined as a marco. +export const ClassElementDefinitionRecord = (function ClassElementDefinitionRecord(record: ClassElementDefinitionRecord) { + Object.setPrototypeOf(record, ClassElementDefinitionRecord.prototype); + return record; +}) as { + (record: ClassElementDefinitionRecord): ClassElementDefinitionRecord; + [Symbol.hasInstance](instance: unknown): instance is ClassElementDefinitionRecord; +}; diff --git a/src/runtime-semantics/ClassExpression.mts b/src/runtime-semantics/ClassExpression.mts new file mode 100644 index 0000000..ecd7cd3 --- /dev/null +++ b/src/runtime-semantics/ClassExpression.mts @@ -0,0 +1,24 @@ +import { Value } from '../value.mts'; +import { Q } from '../completion.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { ClassDefinitionEvaluation, DecoratorListEvaluation } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-class-definitions-runtime-semantics-evaluation */ +// ClassExpression : +// `class` ClassTail +// `class` BindingIdentifier ClassTail +export function* Evaluate_ClassExpression(ClassExpression: ParseNode.ClassExpression): ValueEvaluator { + const { BindingIdentifier, ClassTail, Decorators } = ClassExpression; + const sourceText = ClassExpression.sourceText; + const decorators = Decorators ? Q(yield* DecoratorListEvaluation(Decorators)) : []; + if (!BindingIdentifier) { + // 1. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments undefined and '' + return Q(yield* ClassDefinitionEvaluation(ClassTail, Value.undefined, Value(''), sourceText, decorators)); + } + // 1. Let className be StringValue of BindingIdentifier. + const className = StringValue(BindingIdentifier); + // 2. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments className and className. + return Q(yield* ClassDefinitionEvaluation(ClassTail, className, className, sourceText, decorators)); +} diff --git a/src/runtime-semantics/ClassFieldDefinitionEvaluation.mts b/src/runtime-semantics/ClassFieldDefinitionEvaluation.mts new file mode 100644 index 0000000..57d6c2d --- /dev/null +++ b/src/runtime-semantics/ClassFieldDefinitionEvaluation.mts @@ -0,0 +1,189 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { X, Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; +import { Evaluate_PropertyName } from './PropertyName.mts'; +import { + CreateBuiltinFunction, DefinePropertyOrThrow, MakeMethod, OrdinaryFunctionCreate, PrivateGet, PrivateSet, SymbolDescriptiveString, +} from '#self'; +import { + ClassElementDefinitionRecord, + Descriptor, + JSStringValue, + SymbolValue, + Value, + type Arguments, + type ECMAScriptFunctionObject, type FunctionCallContext, type FunctionObject, type ObjectValue, PrivateName, type PropertyKeyValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type */ +export interface ClassFieldDefinitionRecord { + readonly Name: PropertyKeyValue | PrivateName; + readonly Initializer: ECMAScriptFunctionObject | undefined; +} +export const ClassFieldDefinitionRecord = function ClassFieldDefinitionRecord(value: ClassFieldDefinitionRecord) { + Object.setPrototypeOf(value, ClassFieldDefinitionRecord.prototype); + return value; +} as { + (value: ClassFieldDefinitionRecord): ClassFieldDefinitionRecord; + [Symbol.hasInstance](instance: unknown): instance is ClassFieldDefinitionRecord; +}; + +export function* ClassFieldDefinitionEvaluation(FieldDefinition: ParseNode.FieldDefinition, homeObject: ObjectValue): PlainEvaluator { + const { ClassElementName, Initializer } = FieldDefinition; + // 1. Let name be the result of evaluating ClassElementName. + const name = Q(yield* Evaluate_PropertyName(ClassElementName)); + // 3. If Initializer is present, then + let initializer; + if (Initializer) { + // a. Let formalParameterList be an instance of the production FormalParameters : [empty]. + const formalParameterList: readonly [] = []; + // b. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // c. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // d. Let sourceText be the empty sequence of Unicode code points. + const sourceText = ''; + // e. Let initializer be ! OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameterList, Initializer, non-lexical-this, scope, privateScope). + initializer = X(OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%Function.prototype%'), + sourceText, + formalParameterList, + Initializer, + 'non-lexical-this', + scope, + privateScope, + )); + // f. Perform MakeMethod(initializer, homeObject). + MakeMethod(initializer, homeObject); + // g. Set initializer.[[ClassFieldInitializerName]] to name. + initializer.ClassFieldInitializerName = name; + } else { // 4. Else, + // a. Let initializer be empty. + initializer = undefined; + } + // 5. Return the ClassFieldDefinition Record { [[Name]]: name, [[Initializer]]: initializer }. + return ClassFieldDefinitionRecord({ + Name: name, + Initializer: initializer, + }); +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-runtime-semantics-classfielddefinitionevaluation */ +export function* ClassFieldDefinitionEvaluation_decorator(FieldDefinition: ParseNode.FieldDefinition, homeObject: ObjectValue): PlainEvaluator { + const { ClassElementName, Initializer, accessor } = FieldDefinition; + + if (!accessor) { + const name = Q(yield* Evaluate_PropertyName(ClassElementName)); + const initializers: FunctionObject[] = []; + const extraInitializers: FunctionObject[] = []; + if (Initializer) { + const initializer = CreateFieldInitializerFunction(homeObject, name, Initializer); + // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) + if (surroundingAgent.feature('decorators.no-bugfix.1')) { + initializers.push(initializer); + } else { + initializers[-1] = initializer; + } + } + return ClassElementDefinitionRecord({ + Kind: 'field', + Key: name, + Initializers: initializers, + ExtraInitializers: extraInitializers, + Decorators: undefined, + }); + } else { + const name = Q(yield* Evaluate_PropertyName(ClassElementName)); + let readableName: JSStringValue; + if (name instanceof PrivateName) { + readableName = name.Description; + } else if (name instanceof SymbolValue) { + readableName = SymbolDescriptiveString(name); + } else { + readableName = name; + } + const privateStateDesc = `${readableName.stringValue()} accessor storage`; + const privateStateName = new PrivateName(Value(privateStateDesc)); + const getter = MakeAutoAccessorGetter(homeObject, name, privateStateName); + const setter = MakeAutoAccessorSetter(homeObject, name, privateStateName); + const initializers = []; + const extraInitializers: FunctionObject[] = []; + if (Initializer) { + const initializer = CreateFieldInitializerFunction(homeObject, name, Initializer); + // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) + if (surroundingAgent.feature('decorators.no-bugfix.1')) { + initializers.push(initializer); + } else { + initializers[-1] = initializer; + } + } + if (!(name instanceof PrivateName)) { + const desc = new Descriptor({ + Get: getter, + Set: setter, + Enumerable: Value.true, + Configurable: Value.true, + }); + Q(yield* DefinePropertyOrThrow(homeObject, name, desc)); + } + return ClassElementDefinitionRecord({ + Kind: 'accessor', + Key: name, + Get: getter, + Set: setter, + BackingStorageKey: privateStateName, + Initializers: initializers, + ExtraInitializers: extraInitializers, + Decorators: undefined, + }); + } +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createfieldinitializerfunction */ +export function CreateFieldInitializerFunction(homeObject: ObjectValue, propName: PropertyKeyValue | PrivateName, Initializer: ParseNode.AssignmentExpressionOrHigher) { + const formalParameterList: readonly [] = []; + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + const sourceText = ''; + const initializer = OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%Function.prototype%'), + sourceText, + formalParameterList, + Initializer, + 'non-lexical-this', + scope, + privateScope, + ); + MakeMethod(initializer, homeObject); + initializer.ClassFieldInitializerName = propName; + return initializer; +} + +/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-makeautoaccessorgetter */ +export function MakeAutoAccessorGetter(_homeObject: ObjectValue, _name: PropertyKeyValue | PrivateName, privateStateName: PrivateName) { + const getterClosure = function* getterClosure(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const o = thisValue as ObjectValue; + return Q(yield* PrivateGet(o, privateStateName)); + }; + const getter = CreateBuiltinFunction(getterClosure, 0, Value('get'), []); + // TODO(decorator): spec bug, SetFunctionName only accepts ECMAScriptFunctionObject, but the name is already set when calling CreateBuiltinFunction + // SetFunctionName(getter, name, Value('get')); + // TODO(decorator): https://github.com/tc39/proposal-decorators/issues/568 + // MakeMethod(getter, homeObject); + return getter; +} + +export function MakeAutoAccessorSetter(_homeObject: ObjectValue, _name: PropertyKeyValue | PrivateName, privateStateName: PrivateName) { + const setterClosure = function* setterClosure([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { + const o = thisValue as ObjectValue; + Q(yield* PrivateSet(o, privateStateName, value)); + return Value.undefined; + }; + const setter = CreateBuiltinFunction(setterClosure, 1, Value('set'), []); + // TODO(decorator): spec bug + // SetFunctionName(setter, name, Value('set')); + // TODO(decorator): https://github.com/tc39/proposal-decorators/issues/568 + // MakeMethod(setter, homeObject); + return setter; +} diff --git a/src/runtime-semantics/ClassStaticBlockDefinitionEvaluation.mts b/src/runtime-semantics/ClassStaticBlockDefinitionEvaluation.mts new file mode 100644 index 0000000..45e08d7 --- /dev/null +++ b/src/runtime-semantics/ClassStaticBlockDefinitionEvaluation.mts @@ -0,0 +1,48 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + MakeMethod, + OrdinaryFunctionCreate, + type ECMAScriptFunctionObject, + ObjectValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-classstaticblockdefinition-record-specification-type */ +export interface ClassStaticBlockDefinitionRecord { + readonly BodyFunction: ECMAScriptFunctionObject; +} +export const ClassStaticBlockDefinitionRecord = function ClassStaticBlockDefinitionRecord(value: ClassStaticBlockDefinitionRecord) { + Object.setPrototypeOf(value, ClassStaticBlockDefinitionRecord.prototype); + return value; +} as { + (value: ClassStaticBlockDefinitionRecord): ClassStaticBlockDefinitionRecord; + [Symbol.hasInstance](instance: unknown): instance is ClassStaticBlockDefinitionRecord; +}; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation */ +// ClassStaticBlock : `static` `{` ClassStaticBlockBody `}` +export function ClassStaticBlockDefinitionEvaluation({ ClassStaticBlockBody }: ParseNode.ClassStaticBlock, homeObject: ObjectValue) { + // 1. Let lex be the running execution context's LexicalEnvironment. + const lex = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 2. Let privateEnv be the running execution context's PrivateEnvironment. + const privateEnv = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 3. Let sourceText be the empty sequence of Unicode code points. + const sourceText = ''; + // 4. Let formalParameters be an instance of the production FormalParameters : [empty] . + const formalParameters: readonly [] = []; + // 5. Let bodyFunction be OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameters, ClassStaticBlockBody, non-lexical-this, lex, privateEnv). + const bodyFunction = X(OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%Function.prototype%'), + sourceText, + formalParameters, + ClassStaticBlockBody, + 'non-lexical-this', + lex, + privateEnv, + )); + // 6. Perform MakeMethod(bodyFunction, homeObject). + X(MakeMethod(bodyFunction, homeObject)); + // 7. Return the ClassStaticBlockDefinition Record { [[BodyFunction]]: bodyFunction }. + return ClassStaticBlockDefinitionRecord({ BodyFunction: bodyFunction }); +} diff --git a/src/runtime-semantics/CoalesceExpression.mts b/src/runtime-semantics/CoalesceExpression.mts new file mode 100644 index 0000000..a023825 --- /dev/null +++ b/src/runtime-semantics/CoalesceExpression.mts @@ -0,0 +1,24 @@ +import { Q } from '../completion.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Value } from '../value.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-binary-logical-operators-runtime-semantics-evaluation */ +// CoalesceExpression : +// CoalesceExpressionHead `??` BitwiseORExpression +export function* Evaluate_CoalesceExpression({ CoalesceExpressionHead, BitwiseORExpression }: ParseNode.CoalesceExpression): ValueEvaluator { + // 1. Let lref be the result of evaluating |CoalesceExpressionHead|. + const lref = Q(yield* Evaluate(CoalesceExpressionHead)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* 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 = Q(yield* Evaluate(BitwiseORExpression)); + // b. Return ? GetValue(rref). + return Q(yield* GetValue(rref)); + } + // 4. Otherwise, return lval. + return lval; +} diff --git a/src/runtime-semantics/CommaOperator.mts b/src/runtime-semantics/CommaOperator.mts new file mode 100644 index 0000000..1b2797c --- /dev/null +++ b/src/runtime-semantics/CommaOperator.mts @@ -0,0 +1,18 @@ +import { Evaluate } from '../evaluator.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue } from '#self'; +import type { Value, ValueEvaluator } from '#self'; + +/** https://tc39.es/ecma262/#sec-comma-operator-runtime-semantics-evaluation */ +// Expression : +// AssignmentExpression +// Expression `,` AssignmentExpression +export function* Evaluate_CommaOperator({ ExpressionList }: ParseNode.CommaOperator): ValueEvaluator { + let result!: Value; + for (const Expression of ExpressionList) { + const lref = Q(yield* Evaluate(Expression)); + result = Q(yield* GetValue(lref)); + } + return result; +} diff --git a/src/runtime-semantics/ConditionalExpression.mts b/src/runtime-semantics/ConditionalExpression.mts new file mode 100644 index 0000000..1429db7 --- /dev/null +++ b/src/runtime-semantics/ConditionalExpression.mts @@ -0,0 +1,31 @@ +import { Value } from '../value.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q, X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ToBoolean, GetValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-conditional-operator-runtime-semantics-evaluation */ +// ConditionalExpression : +// ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression +export function* Evaluate_ConditionalExpression({ + ShortCircuitExpression, + AssignmentExpression_a, + AssignmentExpression_b, +}: ParseNode.ConditionalExpression): ValueEvaluator { + // 1. Let lref be the result of evaluating ShortCircuitExpression. + const lref = Q(yield* Evaluate(ShortCircuitExpression)); + // 2. Let lval be ! ToBoolean(? GetValue(lref)). + const lval = X(ToBoolean(Q(yield* 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 = Q(yield* Evaluate(AssignmentExpression_a)); + // b. Return ? GetValue(trueRef). + return Q(yield* GetValue(trueRef)); + } else { // 4. Else, + // a. Let falseRef be the result of evaluating the second AssignmentExpression. + const falseRef = Q(yield* Evaluate(AssignmentExpression_b)); + // b. Return ? GetValue(falseRef). + return Q(yield* GetValue(falseRef)); + } +} diff --git a/src/runtime-semantics/ContinueStatement.mts b/src/runtime-semantics/ContinueStatement.mts new file mode 100644 index 0000000..5f9ca6d --- /dev/null +++ b/src/runtime-semantics/ContinueStatement.mts @@ -0,0 +1,18 @@ +import { Completion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { StringValue } from '../static-semantics/all.mts'; + +/** https://tc39.es/ecma262/#sec-continue-statement-runtime-semantics-evaluation */ +// ContinueStatement : +// `continue` `;` +// `continue` LabelIdentifier `;` +export function Evaluate_ContinueStatement({ LabelIdentifier }: ParseNode.ContinueStatement) { + 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/src/runtime-semantics/CreateDynamicFunction.mts b/src/runtime-semantics/CreateDynamicFunction.mts new file mode 100644 index 0000000..e38f2cd --- /dev/null +++ b/src/runtime-semantics/CreateDynamicFunction.mts @@ -0,0 +1,178 @@ +import { Q, ThrowCompletion, X } from '../completion.mts'; +import { + HostEnsureCanCompileStrings, + surroundingAgent, +} from '../host-defined/engine.mts'; +import { Parser, wrappedParse } from '../parse.mts'; +import { Token } from '../parser/tokens.mts'; +import { + Descriptor, UndefinedValue, Value, + type Arguments, +} from '../value.mts'; +import { __ts_cast__, OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + DefinePropertyOrThrow, + GetPrototypeFromConstructor, + MakeConstructor, + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + ToString, + type FunctionObject, + type Intrinsics, +} from '#self'; + +export function* CreateDynamicFunction(constructor: FunctionObject, newTarget: FunctionObject | UndefinedValue, kind: 'normal' | 'generator' | 'async' | 'asyncGenerator', parameterArgs: Arguments, bodyArg: Value) { + // 6. If newTarget is undefined, set newTarget to constructor. + if (newTarget instanceof UndefinedValue) { + newTarget = constructor; + } + // 7. If kind is normal, then + let fallbackProto: keyof Intrinsics; + let prefix; + if (kind === 'normal') { + prefix = 'function'; + // 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 + prefix = 'function*'; + // a. Let goal be the grammar symbol GeneratorBody. + // b. Let parameterGoal be the grammar symbol FormalParameters[+Yield, ~Await]. + // c. Let fallbackProto be "%GeneratorFunction.prototype%". + fallbackProto = '%GeneratorFunction.prototype%'; + } else if (kind === 'async') { // 9. Else if kind is async, then + prefix = 'async function'; + // a. Let goal be the grammar symbol AsyncBody. + // 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'); + prefix = 'async function*'; + // b. Let goal be the grammar symbol AsyncGeneratorBody. + // c. Let parameterGoal be the grammar symbol FormalParameters[+Yield, +Await]. + // d. Let fallbackProto be "%AsyncGeneratorFunction.prototype%". + fallbackProto = '%AsyncGeneratorFunction.prototype%'; + } + // 11. Let argCount be the number of elements in args. + const argCount = parameterArgs.length; + const parameterStrings: string[] = []; + for (const arg of parameterArgs) { + parameterStrings.push(Q(yield* ToString(arg!)).stringValue()); + } + const bodyString = Q(yield* ToString(bodyArg)).stringValue(); + const currentRealm = surroundingAgent.currentRealmRecord; + Q(yield* HostEnsureCanCompileStrings(currentRealm, parameterStrings, bodyString, false)); + // 12. Let P be the empty String. + let P = ''; + if (argCount > 0) { + P = parameterStrings[0]; + // d. Let k be 1. + let k = 1; + // e. Repeat, while k < argCount - 1 + while (k < argCount) { + const nextArgString = parameterStrings[k]; + // iii. Set P to the string-concatenation of the previous value of P, "," (a comma), and nextArgString. + P = `${P},${nextArgString}`; + // iv. Set k to k + 1. + k += 1; + } + } + const bodyParseString = `\u{000A}${bodyString}\u{000A}`; + // 18. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyString, and "}". + const sourceString = `${prefix} anonymous(${P}\u{000A}) {${bodyParseString}}`; + // 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; + let scriptId; + { + const f = wrappedParse({ source: sourceString }, (p) => { + const r = p.parseExpression(); + p.expect(Token.EOS); + return r; + }); + scriptId = surroundingAgent.addDynamicParsedSource(surroundingAgent.currentRealmRecord, sourceString, f); + if (Array.isArray(f)) { + Parser.decorateSyntaxErrorWithScriptId(f[0], scriptId); + return ThrowCompletion(f[0]); + } + __ts_cast__(f); + parameters = f.FormalParameters; + switch (kind) { + case 'normal': + body = (f as ParseNode.FunctionExpression).FunctionBody; + break; + case 'generator': + body = (f as ParseNode.GeneratorExpression).GeneratorBody; + break; + case 'async': + body = (f as ParseNode.AsyncFunctionExpression).AsyncBody; + break; + case 'asyncGenerator': + body = (f as ParseNode.AsyncGeneratorExpression).AsyncGeneratorBody; + break; + default: + throw new OutOfRange('kind', kind); + } + } + // 21. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto). + const proto = Q(yield* GetPrototypeFromConstructor(newTarget, fallbackProto)); + // 23. Let scope be realmF.[[GlobalEnv]]. + const env = currentRealm.GlobalEnv; + const privateEnv = Value.null; + // 24. Let F be ! OrdinaryFunctionCreate(proto, sourceText, parameters, body, non-lexical-this, scope, privateEnv). + const F = X(OrdinaryFunctionCreate(proto, sourceText, parameters, body, 'non-lexical-this', env, privateEnv)); + F.scriptId = scriptId; + // 25. Perform SetFunctionName(F, "anonymous"). + SetFunctionName(F, Value('anonymous')); + // 26. If kind is generator, then + if (kind === 'generator') { + // a. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype.prototype%')); + // b. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(F, 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(%AsyncGeneratorFunction.prototype.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype.prototype%')); + // b. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(F, 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/src/runtime-semantics/DebuggerStatement.mts b/src/runtime-semantics/DebuggerStatement.mts new file mode 100644 index 0000000..9f4dba6 --- /dev/null +++ b/src/runtime-semantics/DebuggerStatement.mts @@ -0,0 +1,19 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Assert, type StatementEvaluator } from '#self'; + +/** https://tc39.es/ecma262/#sec-debugger-statement-runtime-semantics-evaluation */ +// DebuggerStatement : `debugger` `;` +export function* Evaluate_DebuggerStatement(_node: ParseNode.DebuggerStatement): StatementEvaluator { + // 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. + const completion = yield { type: 'debugger' }; + Assert(completion.type === 'debugger-resume'); + return completion.value; + } + // 2. Return result. + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/DefineMethod.mts b/src/runtime-semantics/DefineMethod.mts new file mode 100644 index 0000000..ddda31d --- /dev/null +++ b/src/runtime-semantics/DefineMethod.mts @@ -0,0 +1,41 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { Evaluate_PropertyName } from './all.mts'; +import { OrdinaryFunctionCreate, MakeMethod, sourceTextMatchedBy } from '#self'; +import type { + ECMAScriptFunctionObject, ObjectValue, PrivateName, PropertyKeyValue, +} from '#self'; + +export interface DefineMethodRecord { + readonly Key: PropertyKeyValue | PrivateName; + readonly Closure: ECMAScriptFunctionObject; +} +/** https://tc39.es/ecma262/#sec-runtime-semantics-definemethod */ +export function* DefineMethod(MethodDefinition: ParseNode.MethodDefinition, object: ObjectValue, functionPrototype?: ObjectValue): PlainEvaluator { + const { ClassElementName, UniqueFormalParameters, FunctionBody } = MethodDefinition; + // 1. Let propKey be the result of evaluating ClassElementName. + const propKey = Q(yield* Evaluate_PropertyName(ClassElementName)); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + let prototype; + // 5. If functionPrototype is present as a parameter, then + if (functionPrototype !== undefined) { + // a. Let prototype be functionPrototype. + prototype = functionPrototype; + } else { // 6. Else, + // a. Let prototype be %Function.prototype%. + prototype = surroundingAgent.intrinsic('%Function.prototype%'); + } + // 7. Let sourceText be the source text matched by MethodDefinition. + const sourceText = sourceTextMatchedBy(MethodDefinition); + // 8. Let closure be OrdinaryFunctionCreate(prototype, sourceText, UniqueFormalParameters, FunctionBody, non-lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate(prototype, sourceText, UniqueFormalParameters!, FunctionBody, 'non-lexical-this', scope, privateScope); + // 9. Perform MakeMethod(closure, object). + MakeMethod(closure, object); + // 10. Return the Record { [[Key]]: propKey, [[Closure]]: closure }. + return { Key: propKey, Closure: closure }; +} diff --git a/src/runtime-semantics/DestructuringAssignmentEvaluation.mts b/src/runtime-semantics/DestructuringAssignmentEvaluation.mts new file mode 100644 index 0000000..624f5c8 --- /dev/null +++ b/src/runtime-semantics/DestructuringAssignmentEvaluation.mts @@ -0,0 +1,306 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + JSStringValue, ReferenceRecord, Value, type PropertyKeyValue, +} from '../value.mts'; +import { + IsAnonymousFunctionDefinition, + IsIdentifierRef, + StringValue, + type FunctionDeclaration, +} from '../static-semantics/all.mts'; +import { + Evaluate, type PlainEvaluator, type StatementEvaluator, +} from '../evaluator.mts'; +import { + Q, X, + Completion, + AbruptCompletion, + NormalCompletion, + EnsureCompletion, +} from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Evaluate_PropertyName, + NamedEvaluation, + refineLeftHandSideExpression, +} from './all.mts'; +import { + ArrayCreate, + CopyDataProperties, + CreateDataPropertyOrThrow, + GetIterator, + GetV, + GetValue, + IteratorClose, + IteratorStep, + OrdinaryObjectCreate, + PutValue, + ResolveBinding, + RequireObjectCoercible, + ToString, + F, + Assert, + type IteratorRecord, + IteratorStepValue, +} from '#self'; + +// ObjectAssignmentPattern : +// `{` `}` +// `{` AssignmentPropertyList `}` +// `{` AssignmentPropertyList `,` `}` +// `{` AssignmentPropertyList `,` AssignmentRestProperty? `}` +function* DestructuringAssignmentEvaluation_ObjectAssignmentPattern({ AssignmentPropertyList, AssignmentRestProperty }: ParseNode.ObjectAssignmentPattern, value: Value): PlainEvaluator { + // 1. Perform ? RequireObjectCoercible(value). + Q(RequireObjectCoercible(value)); + // 2. Perform ? PropertyDestructuringAssignmentEvaluation for AssignmentPropertyList using value as the argument. + const excludedNames: readonly PropertyKeyValue[] = Q(yield* PropertyDestructuringAssignmentEvaluation(AssignmentPropertyList, value)); + if (AssignmentRestProperty) { + Q(yield* RestDestructuringAssignmentEvaluation(AssignmentRestProperty, value, excludedNames)); + } + // 3. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-restdestructuringassignmentevaluation */ +// AssignmentRestProperty : `...` DestructuringAssignmentTarget +function* RestDestructuringAssignmentEvaluation({ DestructuringAssignmentTarget }: ParseNode.AssignmentRestProperty, value: Value, excludedNames: readonly PropertyKeyValue[]): StatementEvaluator { + // 1. Let lref be the result of evaluating DestructuringAssignmentTarget. + const lref = Q(yield* Evaluate(DestructuringAssignmentTarget)); + Q(lref); + // 3. Let restObj be OrdinaryObjectCreate(%Object.prototype%). + const restObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')); + // 4. Perform ? CopyDataProperties(restObj, value, excludedNames). + Q(yield* CopyDataProperties(restObj, value, excludedNames)); + // 5. Return PutValue(lref, restObj). + return yield* PutValue(lref, restObj); +} + +function* PropertyDestructuringAssignmentEvaluation(AssignmentPropertyList: ParseNode.ObjectAssignmentPattern['AssignmentPropertyList'], value: Value): PlainEvaluator { + const propertyNames: PropertyKeyValue[] = []; + for (const AssignmentProperty of AssignmentPropertyList) { + if ('IdentifierReference' in AssignmentProperty) { + // 1. Let P be StringValue of IdentifierReference. + const P = StringValue(AssignmentProperty.IdentifierReference); + // 2. Let lref be ? ResolveBinding(P). + const lref = Q(yield* ResolveBinding(P, undefined, AssignmentProperty.IdentifierReference.strict)); + // 3. Let v be ? GetV(value, P). + let v = Q(yield* 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 = Q(yield* NamedEvaluation(AssignmentProperty.Initializer as FunctionDeclaration, P)); + } else { // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = Q(yield* Evaluate(AssignmentProperty.Initializer)); + // ii. Set v to ? GetValue(defaultValue) + v = Q(yield* GetValue(defaultValue)); + } + } + // 5. Perform ? PutValue(lref, v). + Q(yield* PutValue(lref, v)); + // 6. Return a new List containing P. + propertyNames.push(P); + } else { + Assert('PropertyName' in AssignmentProperty); + // 1. Let name be the result of evaluating PropertyName. + const name = yield* Evaluate_PropertyName(AssignmentProperty.PropertyName!); + Q(name); + // 3. Perform ? KeyedDestructuringAssignmentEvaluation of AssignmentElement with value and name as the arguments. + Q(yield* KeyedDestructuringAssignmentEvaluation(AssignmentProperty.AssignmentElement, value, name as PropertyKeyValue)); + // 4. Return a new List containing name. + propertyNames.push(name as PropertyKeyValue); + } + } + return propertyNames; +} + +// AssignmentElement : DestructuringAssignmentTarget Initializer? +function* KeyedDestructuringAssignmentEvaluation({ + DestructuringAssignmentTarget, + Initializer, +}: ParseNode.AssignmentElement, value: Value, propertyName: PropertyKeyValue) { + // 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 = Q(yield* Evaluate(DestructuringAssignmentTarget)); + } + // 2. Let v be ? GetV(value, propertyName). + const v = Q(yield* GetV(value, propertyName)); + // 3. If Initializer is present and v is undefined, then + let rhsValue: Value; + 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 = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, (lref as ReferenceRecord).ReferencedName as JSStringValue)); + } else { + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = Q(yield* Evaluate(Initializer)); + // ii. Let rhsValue be ? GetValue(defaultValue). + rhsValue = Q(yield* 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) as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern; + // b. Return the result of performing DestructuringAssignmentEvaluation of assignmentPattern with rhsValue as the argument. + return yield* DestructuringAssignmentEvaluation(assignmentPattern, X(rhsValue)); + } + // 6. Return ? PutValue(lref, rhsValue). + return Q(yield* PutValue(X(lref)!, rhsValue)); +} + +// ArrayAssignmentPattern : +// `[` `]` +// `[` AssignmentElementList `]` +// `[` AssignmentElementList `,` AssignmentRestElement? `]` +function* DestructuringAssignmentEvaluation_ArrayAssignmentPattern({ AssignmentElementList, AssignmentRestElement }: ParseNode.ArrayAssignmentPattern, value: Value) { + // 1. Let iteratorRecord be ? GetIterator(value). + const iteratorRecord = Q(yield* GetIterator(value, 'sync')); + // 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(yield* IteratorClose(iteratorRecord, status)); + } + // b. Return Completion(status). + return 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(yield* IteratorClose(iteratorRecord, status)); + } + return Completion(status); +} + +function* IteratorDestructuringAssignmentEvaluation(node: ParseNode.AssignmentElisionElement[] | ParseNode.AssignmentElisionElement | ParseNode.AssignmentRestElement, iteratorRecord: IteratorRecord): StatementEvaluator { + 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. Perform ? IteratorStep(iteratorRecord). + Q(yield* IteratorStep(iteratorRecord)); + } + // 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 = Q(yield* Evaluate(DestructuringAssignmentTarget)); + } + let value: Value = Value.undefined; + // 2. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is not done, set value to next. + if (next !== 'done') { + value = next; + } + } + let v: Value; + // 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 target be the StringValue of DestructuringAssignmentTarget. + const target = (lref as ReferenceRecord).ReferencedName as JSStringValue; + // i. ii. Let v be ? NamedEvaluation of Initializer with argument target. + v = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, target)); + } else { // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = Q(yield* Evaluate(Initializer)); + // ii. Let v be ? GetValue(defaultValue). + v = Q(yield* GetValue(defaultValue)); + } + } else { // 5. Else, let v be value. + v = Q(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) as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern; + // b. Return the result of performing DestructuringAssignmentEvaluation of nestedAssignmentPattern with v as the argument. + return yield* DestructuringAssignmentEvaluation(nestedAssignmentPattern, X(v)); + } + // 7. Return ? PutValue(lref, v). + return Q(yield* PutValue(Q(lref) as ReferenceRecord, v)); + } + case 'AssignmentRestElement': { + const { AssignmentExpression: 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); + Q(lref); + } + // 2. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(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 = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is not done, then + if (next !== 'done') { + // i. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), next). + X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), X(next))); + // 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(yield* PutValue(Q(lref) as ReferenceRecord, A)); + } + // 6. Let nestedAssignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget. + const nestedAssignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget) as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern; + // 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: ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern, value: Value): StatementEvaluator { + 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/src/runtime-semantics/EmptyStatement.mts b/src/runtime-semantics/EmptyStatement.mts new file mode 100644 index 0000000..3850fc0 --- /dev/null +++ b/src/runtime-semantics/EmptyStatement.mts @@ -0,0 +1,9 @@ +import { NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-empty-statement-runtime-semantics-evaluation */ +// EmptyStatement : `;` +export function Evaluate_EmptyStatement(_EmptyStatement: ParseNode.EmptyStatement) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/EqualityExpression.mts b/src/runtime-semantics/EqualityExpression.mts new file mode 100644 index 0000000..4ec8dfe --- /dev/null +++ b/src/runtime-semantics/EqualityExpression.mts @@ -0,0 +1,60 @@ +import { Q, X } from '../completion.mts'; +import { Evaluate } from '../evaluator.mts'; +import { Value } from '../value.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + IsLooselyEqual, + GetValue, + IsStrictlyEqual, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-equality-operators-runtime-semantics-evaluation */ +// EqualityExpression : +// EqualityExpression `==` RelationalExpression +// EqualityExpression `!=` RelationalExpression +// EqualityExpression `===` RelationalExpression +// EqualityExpression `!==` RelationalExpression +export function* Evaluate_EqualityExpression({ EqualityExpression, operator, RelationalExpression }: ParseNode.EqualityExpression) { + // 1. Let lref be the result of evaluating EqualityExpression. + const lref = Q(yield* Evaluate(EqualityExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* GetValue(lref)); + // 3. Let rref be the result of evaluating RelationalExpression. + const rref = Q(yield* Evaluate(RelationalExpression)); + // 4. Let rval be ? GetValue(rref). + const rval = Q(yield* GetValue(rref)); + switch (operator) { + case '==': + // 5. Return the result of performing Abstract Equality Comparison rval == lval. + return yield* IsLooselyEqual(rval, lval); + case '!=': { + // 5. Let r be the result of performing Abstract Equality Comparison rval == lval. + const r = yield* IsLooselyEqual(rval, lval); + Q(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 IsStrictlyEqual(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(IsStrictlyEqual(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/src/runtime-semantics/EvaluateBody.mts b/src/runtime-semantics/EvaluateBody.mts new file mode 100644 index 0000000..c5e1090 --- /dev/null +++ b/src/runtime-semantics/EvaluateBody.mts @@ -0,0 +1,206 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value, type Arguments } from '../value.mts'; +import { + Completion, + AbruptCompletion, + Q, X, + EnsureCompletion, + ReturnCompletion, +} from '../completion.mts'; +import { Evaluate, type StatementEvaluator } from '../evaluator.mts'; +import { IsAnonymousFunctionDefinition, type FunctionDeclaration } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { Mutable } from '../helpers.mts'; +import { + Evaluate_FunctionStatementList, + FunctionDeclarationInstantiation, + NamedEvaluation, +} from './all.mts'; +import { + Assert, + AsyncFunctionStart, + Call, + GeneratorStart, + NewPromiseCapability, + OrdinaryCreateFromConstructor, + AsyncGeneratorStart, + GetValue, + type ECMAScriptFunctionObject, + type GeneratorObject, + type AsyncGeneratorObject, + type Body, +} from '#self'; + +export function Evaluate_AnyFunctionBody({ FunctionStatementList }: ParseNode.FunctionBody | ParseNode.AsyncBody | ParseNode.GeneratorBody | ParseNode.AsyncGeneratorBody) { + return Evaluate_FunctionStatementList(FunctionStatementList); +} + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluatebody */ +// FunctionBody : FunctionStatementList +export function* EvaluateBody_FunctionBody({ FunctionStatementList }: ParseNode.FunctionBody, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Return the result of evaluating FunctionStatementList. + return yield* Evaluate_FunctionStatementList(FunctionStatementList); +} + +/** https://tc39.es/ecma262/#sec-arrow-function-definitions-runtime-semantics-evaluation */ +// ExpressionBody : AssignmentExpression +export function* Evaluate_ExpressionBody({ AssignmentExpression }: ParseNode.ExpressionBody): StatementEvaluator { + // 1. Let exprRef be the result of evaluating AssignmentExpression. + const exprRef = Q(yield* Evaluate(AssignmentExpression)); + // 2. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(yield* GetValue(exprRef)); + // 3. Return Completion { [[Type]]: return, [[Value]]: exprValue, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: exprValue, Target: undefined }); +} + +/** https://tc39.es/ecma262/#sec-arrow-function-definitions-runtime-semantics-evaluatebody */ +// ConciseBody : ExpressionBody +export function* EvaluateBody_ConciseBody({ ExpressionBody }: ParseNode.ConciseBody, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Return the result of evaluating ExpressionBody. + return yield* Evaluate(ExpressionBody); +} + +/** https://tc39.es/ecma262/#sec-async-arrow-function-definitions-EvaluateBody */ +// AsyncConciseBody : ExpressionBody +function* EvaluateBody_AsyncConciseBody({ ExpressionBody }: ParseNode.AsyncConciseBody, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments) { + // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 2. Let declResult be FunctionDeclarationInstantiation(functionObject, argumentsList). + const declResult = EnsureCompletion(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 3. If declResult is not an abrupt completion, then + if (declResult.Type === 'normal') { + // a. Perform ! AsyncFunctionStart(promiseCapability, ExpressionBody). + X(yield* AsyncFunctionStart(promiseCapability, ExpressionBody)); + } else { // 4. Else + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »). + X(yield* 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 }); +} + +/** https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluatebody */ +// GeneratorBody : FunctionBody +export function* EvaluateBody_GeneratorBody(GeneratorBody: ParseNode.GeneratorBody, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments): StatementEvaluator { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Let G be ? OrdinaryCreateFromConstructor(functionObject, "%GeneratorPrototype%", « [[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]] »). + const G = Q(yield* OrdinaryCreateFromConstructor(functionObject, '%GeneratorFunction.prototype.prototype%', ['GeneratorState', 'GeneratorContext', 'GeneratorBrand'])) as Mutable; + // 3. Set G.[[GeneratorBrand]] to empty. + G.GeneratorBrand = undefined; + // 4. Set G.[[GeneratorState]] to suspended-start. + G.GeneratorState = 'suspendedStart'; + // 5. Perform GeneratorStart(G, FunctionBody). + GeneratorStart(G, GeneratorBody); + // 6. Return ReturnCompletion(G). + return ReturnCompletion(G); +} + +/** https://tc39.es/ecma262/#sec-asyncgenerator-definitions-evaluatebody */ +// AsyncGeneratorBody : FunctionBody +export function* EvaluateBody_AsyncGeneratorBody(FunctionBody: ParseNode.AsyncGeneratorBody, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments): StatementEvaluator { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). + Q(yield* FunctionDeclarationInstantiation(functionObject, argumentsList)); + // 2. Let generator be ? OrdinaryCreateFromConstructor(functionObject, "%AsyncGeneratorFunction.prototype.prototype%", « [[AsyncGeneratorState]], [[AsyncGeneratorContext]], [[AsyncGeneratorQueue]], [[GeneratorBrand]] »). + const generator = Q(yield* OrdinaryCreateFromConstructor(functionObject, '%AsyncGeneratorFunction.prototype.prototype%', [ + 'AsyncGeneratorState', + 'AsyncGeneratorContext', + 'AsyncGeneratorQueue', + 'GeneratorBrand', + ])) as Mutable; + // 3. Set generator.[[GeneratorBrand]] to empty. + generator.GeneratorBrand = undefined; + generator.AsyncGeneratorState = 'suspendedStart'; + // 4. Perform ! AsyncGeneratorStart(generator, FunctionBody). + X(AsyncGeneratorStart(generator, FunctionBody)); + // 5. Return Completion { [[Type]]: return, [[Value]]: generator, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: generator, Target: undefined }); +} + +/** https://tc39.es/ecma262/#sec-async-function-definitions-EvaluateBody */ +// AsyncBody : FunctionBody +export function* EvaluateBody_AsyncFunctionBody(FunctionBody: ParseNode.AsyncBody, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments) { + // 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(yield* AsyncFunctionStart(promiseCapability, FunctionBody)); + } else { // 4. Else, + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »). + X(yield* 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 }); +} + +// Initializer : +// `=` AssignmentExpression +export function* EvaluateBody_AssignmentExpression(AssignmentExpression: ParseNode.Initializer, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments): StatementEvaluator { + // 1. Assert: argumentsList is empty. + if (surroundingAgent.feature('decorators') && surroundingAgent.feature('decorators.no-bugfix.1')) { + // TODO(decorator): spec bug + // eslint-disable-next-line no-console + console.assert(argumentsList.length === 0, 'Assert: argumentsList is empty.'); + } else { + Assert(argumentsList.length === 0); + } + // 2. Assert: functionObject.[[ClassFieldInitializerName]] is not empty. + Assert(functionObject.ClassFieldInitializerName !== undefined); + let value; + // 3. If IsAnonymousFunctionDefinition(AssignmentExpression) is true, then + if (IsAnonymousFunctionDefinition(AssignmentExpression)) { + // a. Let value be NamedEvaluation of Initializer with argument functionObject.[[ClassFieldInitializerName]]. + value = yield* NamedEvaluation(AssignmentExpression as FunctionDeclaration, functionObject.ClassFieldInitializerName); + } else { // 4. Else, + // a. Let rhs be the result of evaluating AssignmentExpression. + const rhs = Q(yield* Evaluate(AssignmentExpression)); + // b. Let value be ? GetValue(rhs). + value = Q(yield* GetValue(rhs)); + } + // 5. Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + return new Completion({ Type: 'return', Value: X(value), Target: undefined }); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-evaluateclassstaticblockbody */ +// ClassStaticBlockBody : ClassStaticBlockStatementList +function* EvaluateClassStaticBlockBody({ ClassStaticBlockStatementList }: ParseNode.ClassStaticBlockBody, functionObject: ECMAScriptFunctionObject) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, « »). + Q(yield* FunctionDeclarationInstantiation(functionObject, [])); + // 2. Return the result of evaluating ClassStaticBlockStatementList. + return yield* Evaluate_FunctionStatementList(ClassStaticBlockStatementList); +} + +// FunctionBody : FunctionStatementList +// ConciseBody : ExpressionBody +// GeneratorBody : FunctionBody +// AsyncGeneratorBody : FunctionBody +// AsyncBody : FunctionBody +// AsyncConciseBody : ExpressionBody +// ClassStaticBlockBody : ClassStaticBlockStatementList +export function EvaluateBody(Body: Body, functionObject: ECMAScriptFunctionObject, argumentsList: Arguments) { + 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 'AsyncBody': + return EvaluateBody_AsyncFunctionBody(Body, functionObject, argumentsList); + case 'AsyncConciseBody': + return EvaluateBody_AsyncConciseBody(Body, functionObject, argumentsList); + case 'ClassStaticBlockBody': + return EvaluateClassStaticBlockBody(Body, functionObject); + default: + return EvaluateBody_AssignmentExpression(Body, functionObject, argumentsList); + } +} diff --git a/src/runtime-semantics/EvaluateCall.mts b/src/runtime-semantics/EvaluateCall.mts new file mode 100644 index 0000000..36471db --- /dev/null +++ b/src/runtime-semantics/EvaluateCall.mts @@ -0,0 +1,63 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + ObjectValue, Value, ReferenceRecord, +} from '../value.mts'; +import { Q, Completion, AbruptCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ArgumentListEvaluation } from './all.mts'; +import { + Assert, + IsPropertyReference, + IsCallable, + GetThisValue, + PrepareForTailCall, + Call, + EnvironmentRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-evaluatecall */ +export function* EvaluateCall(func: Value, ref: ReferenceRecord | Value, args: ParseNode | ParseNode.Arguments, tailPosition: boolean) { + // 1. If Type(ref) is Reference, then + let thisValue; + if (ref instanceof ReferenceRecord) { + // a. If IsPropertyReference(ref) is true, then + if (IsPropertyReference(ref) === Value.true) { + // i. Let thisValue be GetThisValue(ref). + thisValue = GetThisValue(ref); + } else { + // i. Let refEnv be ref.[[Base]]. + const refEnv = ref.Base; + // ii. Assert: refEnv is an Environment Record. + Assert(refEnv instanceof EnvironmentRecord); + // iii. Let thisValue be refEnv.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 (!(func instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', func); + } + // 5. If IsCallable(func) is false, throw a TypeError exception. + if (!IsCallable(func)) { + 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 = yield* 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. + // 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/src/runtime-semantics/EvaluatePropertyAccess.mts b/src/runtime-semantics/EvaluatePropertyAccess.mts new file mode 100644 index 0000000..a2c50b9 --- /dev/null +++ b/src/runtime-semantics/EvaluatePropertyAccess.mts @@ -0,0 +1,39 @@ +import { Value, ReferenceRecord } from '../value.mts'; +import { Evaluate, type ReferenceEvaluator } from '../evaluator.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { Q, type PlainCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + GetValue, + Assert, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-evaluate-expression-key-property-access */ +export function* EvaluatePropertyAccessWithExpressionKey(baseValue: Value, expression: ParseNode.Expression, strict: boolean): ReferenceEvaluator { + // 1. Let propertyNameReference be the result of evaluating expression. + const propertyNameReference = Q(yield* Evaluate(expression)); + // 2. Let propertyNameValue be ? GetValue(propertyNameReference). + const propertyNameValue = Q(yield* GetValue(propertyNameReference)); + // 3. Return the Reference Record { [[Base]]: bv, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: empty }. + return new ReferenceRecord({ + Base: baseValue, + ReferencedName: propertyNameValue, + Strict: strict ? Value.true : Value.false, + ThisValue: undefined, + }); +} + +/** https://tc39.es/ecma262/#sec-evaluate-identifier-key-property-access */ +export function EvaluatePropertyAccessWithIdentifierKey(baseValue: Value, identifierName: ParseNode.IdentifierName, strict: boolean): PlainCompletion { + // 1. Assert: identifierName is an IdentifierName. + Assert(identifierName.type === 'IdentifierName'); + // 3. Let propertyNameString be StringValue of IdentifierName + const propertyNameString = StringValue(identifierName); + // 4. Return the Reference Record { [[Base]]: bv, [[ReferencedName]]: propertyNameString, [[Strict]]: strict, [[ThisValue]]: empty }. + return new ReferenceRecord({ + Base: baseValue, + ReferencedName: propertyNameString, + Strict: strict ? Value.true : Value.false, + ThisValue: undefined, + }); +} diff --git a/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mts b/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mts new file mode 100644 index 0000000..1e1abef --- /dev/null +++ b/src/runtime-semantics/EvaluateStringOrNumericBinaryExpression.mts @@ -0,0 +1,19 @@ +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ApplyStringOrNumericBinaryOperator, type BinaryOperator } from './all.mts'; +import { GetValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-evaluatestringornumericbinaryexpression */ +export function* EvaluateStringOrNumericBinaryExpression(leftOperand: ParseNode.Expression, opText: BinaryOperator, rightOperand: ParseNode.Expression): ValueEvaluator { + // 1. Let lref be the result of evaluating leftOperand. + const lref = Q(yield* Evaluate(leftOperand)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* GetValue(lref)); + // 3. Let rref be the result of evaluating rightOperand. + const rref = Q(yield* Evaluate(rightOperand)); + // 4. Let rval be ? GetValue(rref). + const rval = Q(yield* GetValue(rref)); + // 5. Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval). + return Q(yield* ApplyStringOrNumericBinaryOperator(lval, opText, rval)); +} diff --git a/src/runtime-semantics/ExponentiationExpression.mts b/src/runtime-semantics/ExponentiationExpression.mts new file mode 100644 index 0000000..4c3d1c4 --- /dev/null +++ b/src/runtime-semantics/ExponentiationExpression.mts @@ -0,0 +1,11 @@ +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-exp-operator-runtime-semantics-evaluation */ +// ExponentiationExpression : UpdateExpression ** ExponentiationExpression +export function* Evaluate_ExponentiationExpression({ UpdateExpression, ExponentiationExpression }: ParseNode.ExponentiationExpression): ValueEvaluator { + // 1. Return ? EvaluateStringOrNumericBinaryExpression(UpdateExpression, **, ExponentiationExpression). + return Q(yield* EvaluateStringOrNumericBinaryExpression(UpdateExpression, '**', ExponentiationExpression)); +} diff --git a/src/runtime-semantics/ExportDeclaration.mts b/src/runtime-semantics/ExportDeclaration.mts new file mode 100644 index 0000000..0db6ac0 --- /dev/null +++ b/src/runtime-semantics/ExportDeclaration.mts @@ -0,0 +1,100 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { Evaluate } from '../evaluator.mts'; +import { BoundNames, IsAnonymousFunctionDefinition } from '../static-semantics/all.mts'; +import { NormalCompletion, Q } from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + NamedEvaluation, + InitializeBoundName, + BindingClassDeclarationEvaluation, + DecoratorListEvaluation, +} from './all.mts'; +import { + Assert, GetValue, type ECMAScriptFunctionObject, type FunctionDeclaration, +} from '#self'; + +/** https://tc39.es/ecma262/#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: ParseNode.ExportDeclaration) { + const { + FromClause, NamedExports, + VariableStatement, + Declaration, + default: isDefault, + HoistableDeclaration, + ClassDeclaration, + AssignmentExpression, + Decorators, + } = 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) { + if (Decorators) { + Assert(Declaration.type === 'ClassDeclaration' && !Declaration.Decorators); + const decorators = Q(yield* DecoratorListEvaluation(Decorators)); + Q(yield* BindingClassDeclarationEvaluation(Declaration, decorators)); + return undefined; + } else { + // 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) { + const decorators = Decorators ? Q(yield* DecoratorListEvaluation(Decorators)) : []; + const value = Q(yield* BindingClassDeclarationEvaluation(ClassDeclaration, decorators)) as ECMAScriptFunctionObject; + // 2. Let className be the sole element of BoundNames of ClassDeclaration. + const className = BoundNames(ClassDeclaration)[0]; + // If className is "*default*", then + if (className.stringValue() === '*default*') { + // a. Let env be the running execution context's LexicalEnvironment. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // b. Perform ? InitializeBoundName("*default*", value, env). + Q(yield* InitializeBoundName(Value('*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 as FunctionDeclaration, Value('default')); + } else { // 2. Else, + // a. Let rhs be the result of evaluating AssignmentExpression. + const rhs = Q(yield* Evaluate(AssignmentExpression)); + // a. Let value be ? GetValue(rhs). + value = Q(yield* GetValue(rhs)); + } + // 3. Let env be the running execution context's LexicalEnvironment. + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Perform ? InitializeBoundName("*default*", value, env). + Q(yield* InitializeBoundName(Value('*default*'), value as ECMAScriptFunctionObject, env)); + // 5. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + throw new OutOfRange('Evaluate_ExportDeclaration', ExportDeclaration); +} diff --git a/src/runtime-semantics/ExpressionStatement.mts b/src/runtime-semantics/ExpressionStatement.mts new file mode 100644 index 0000000..cb8a66a --- /dev/null +++ b/src/runtime-semantics/ExpressionStatement.mts @@ -0,0 +1,14 @@ +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-expression-statement-runtime-semantics-evaluation */ +// ExpressionStatement : +// Expression `;` +export function* Evaluate_ExpressionStatement({ Expression }: ParseNode.ExpressionStatement): ValueEvaluator { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = Q(yield* Evaluate(Expression)); + // 2. Return ? GetValue(exprRef). + return Q(yield* GetValue(exprRef)); +} diff --git a/src/runtime-semantics/FunctionDeclaration.mts b/src/runtime-semantics/FunctionDeclaration.mts new file mode 100644 index 0000000..bc9521f --- /dev/null +++ b/src/runtime-semantics/FunctionDeclaration.mts @@ -0,0 +1,11 @@ +import { NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation */ +// FunctionDeclaration : +// function BindingIdentifier ( FormalParameters ) { FunctionBody } +// function ( FormalParameters ) { FunctionBody } +export function Evaluate_FunctionDeclaration(_FunctionDeclaration: ParseNode.FunctionDeclaration) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/FunctionDeclarationInstantiation.mts b/src/runtime-semantics/FunctionDeclarationInstantiation.mts new file mode 100644 index 0000000..c909d7e --- /dev/null +++ b/src/runtime-semantics/FunctionDeclarationInstantiation.mts @@ -0,0 +1,274 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value, type Arguments } from '../value.mts'; +import { + BoundNames, + IsConstantDeclaration, + IsSimpleParameterList, + ContainsExpression, + VarDeclaredNames, + VarScopedDeclarations, + LexicallyDeclaredNames, + LexicallyScopedDeclarations, +} from '../static-semantics/all.mts'; +import { Q, X, NormalCompletion } from '../completion.mts'; +import { JSStringSet } from '../helpers.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { + InstantiateFunctionObject, + IteratorBindingInitialization_FormalParameters, +} from './all.mts'; +import { + Assert, + CreateListIteratorRecord, + CreateMappedArgumentsObject, + CreateUnmappedArgumentsObject, + type ECMAScriptFunctionObject, +} from '#self'; +import { DeclarativeEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-functiondeclarationinstantiation */ +export function* FunctionDeclarationInstantiation(func: ECMAScriptFunctionObject, argumentsList: Arguments): PlainEvaluator { + // 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 JSStringSet(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 JSStringSet(LexicallyDeclaredNames(code)); + // 12. Let functionNames be a new empty List. + const functionNames = new JSStringSet(); + // 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 JSStringSet(parameterNames).has('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('arguments') || lexicalNames.has('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 = new DeclarativeEnvironmentRecord(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 = yield* 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: JSStringSet; + 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(Value('arguments'), Value.false)); + } else { + // i. Perform ! env.CreateMutableBinding("arguments", false). + X(env.CreateMutableBinding(Value('arguments'), Value.false)); + } + // e. Call env.InitializeBinding("arguments", ao). + yield* env.InitializeBinding(Value('arguments'), ao); + // f. Let parameterBindings be a new List of parameterNames with "arguments" appended. + parameterBindings = new JSStringSet(parameterNames); + parameterBindings.add('arguments'); + } else { + // a. Let parameterBindings be parameterNames. + parameterBindings = new JSStringSet(parameterNames); + } + // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList). + const iteratorRecord = CreateListIteratorRecord(argumentsList.values()); + let usedEnv; + // 25. If hasDuplicates is true, then + if (hasDuplicates) { + usedEnv = Value.undefined; + } else { + usedEnv = env; + } + // 1. NOTE: The following step cannot return a ReturnCompletion because the only way such a completion can arise in expression position is by use of |YieldExpression|, which is forbidden in parameter lists by Early Error rules in and . + // Perform ? IteratorBindingInitialization of _formals_ with arguments _iteratorRecord_ and _usedEnv_. + Q(yield* IteratorBindingInitialization_FormalParameters(formals, iteratorRecord, usedEnv)); + 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 JSStringSet(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). + yield* 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 = new DeclarativeEnvironmentRecord(env); + // c. Set the VariableEnvironment of calleeContext to varEnv. + calleeContext.VariableEnvironment = varEnv; + // d. Let instantiatedVarNames be a new empty List. + const instantiatedVarNames = new JSStringSet(); + // 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). + yield* 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 = new DeclarativeEnvironmentRecord(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. Let privateEnv be the PrivateEnvironment of calleeContext. + const privateEnv = calleeContext.PrivateEnvironment; + // 36. 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 and privateEnv. + const fo = InstantiateFunctionObject(f, lexEnv, privateEnv); + // c. Perform ! varEnv.SetMutableBinding(fn, fo, false). + X(varEnv.SetMutableBinding(fn, fo, Value.false)); + } + // 37. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/FunctionExpression.mts b/src/runtime-semantics/FunctionExpression.mts new file mode 100644 index 0000000..6c6707c --- /dev/null +++ b/src/runtime-semantics/FunctionExpression.mts @@ -0,0 +1,11 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateOrdinaryFunctionExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation */ +// FunctionExpression : +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` +export function Evaluate_FunctionExpression(FunctionExpression: ParseNode.FunctionExpression) { + // 1. Return InstantiateOrdinaryFunctionExpression of FunctionExpression. + return InstantiateOrdinaryFunctionExpression(FunctionExpression); +} diff --git a/src/runtime-semantics/FunctionStatementList.mts b/src/runtime-semantics/FunctionStatementList.mts new file mode 100644 index 0000000..48ff4ea --- /dev/null +++ b/src/runtime-semantics/FunctionStatementList.mts @@ -0,0 +1,11 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Evaluate_StatementList } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation */ +// FunctionStatementList : [empty] +// +// (implicit) +// FunctionStatementList : StatementList +export function Evaluate_FunctionStatementList(FunctionStatementList: ParseNode.FunctionStatementList) { + return Evaluate_StatementList(FunctionStatementList); +} diff --git a/src/runtime-semantics/GeneratorExpression.mts b/src/runtime-semantics/GeneratorExpression.mts new file mode 100644 index 0000000..4bab5f8 --- /dev/null +++ b/src/runtime-semantics/GeneratorExpression.mts @@ -0,0 +1,11 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateGeneratorFunctionExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation */ +// GeneratorExpression : +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +// `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` +export function Evaluate_GeneratorExpression(GeneratorExpression: ParseNode.GeneratorExpression) { + // 1. Return InstantiateGeneratorFunctionExpression of GeneratorExpression. + return InstantiateGeneratorFunctionExpression(GeneratorExpression); +} diff --git a/src/runtime-semantics/GetSubstitution.mts b/src/runtime-semantics/GetSubstitution.mts new file mode 100644 index 0000000..f4ddafc --- /dev/null +++ b/src/runtime-semantics/GetSubstitution.mts @@ -0,0 +1,90 @@ +import { + ObjectValue, UndefinedValue, JSStringValue, Value, +} from '../value.mts'; +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { + Assert, + Get, + ToString, + surroundingAgent, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-getsubstitution */ +export function* GetSubstitution(matched: JSStringValue, str: JSStringValue, position: number, captures: readonly (JSStringValue | UndefinedValue)[], namedCaptures: UndefinedValue | ObjectValue, replacementTemplate: JSStringValue): ValueEvaluator { + const stringLength = str.stringValue().length; + Assert(position <= stringLength); + const result: string[] = []; + let templateRemainder = replacementTemplate.stringValue(); + let ref: string; + let refReplacement: string; + while (templateRemainder.length) { + if (templateRemainder.startsWith('$$')) { + ref = '$$'; + refReplacement = '$'; + } else if (templateRemainder.startsWith('$`')) { + ref = '$`'; + refReplacement = str.stringValue().slice(0, position); + } else if (templateRemainder.startsWith('$&')) { + ref = '$&'; + refReplacement = matched.stringValue(); + } else if (templateRemainder.startsWith("$'")) { + ref = "$'"; + const matchLength = matched.stringValue().length; + const tailPos = position + matchLength; + refReplacement = str.stringValue().slice(Math.min(tailPos, stringLength)); + } else if (templateRemainder.match(/^\$\d+/)) { + let digitCount = templateRemainder.match(/^\$\d\d/) ? 2 : 1; + let digits = templateRemainder.slice(1, 1 + digitCount); + let index = parseInt(digits, 10); + Assert(index >= 0 && index <= 99); + const captureLen = captures.length; + if (index > captureLen && digitCount === 2) { + digitCount = 1; + digits = digits[0]; + index = parseInt(digits, 10); + } + ref = templateRemainder.slice(0, 1 + digitCount); + if (index >= 1 && index <= captureLen) { + const capture = captures[index - 1]; + if (capture instanceof UndefinedValue) { + refReplacement = ''; + } else { + refReplacement = capture.stringValue(); + } + } else { + refReplacement = ref; + } + } else if (templateRemainder.startsWith('$<')) { + const gtPos = templateRemainder.indexOf('>', 0); + if (gtPos === -1 || namedCaptures instanceof UndefinedValue) { + ref = '$<'; + refReplacement = ref; + } else { + ref = templateRemainder.slice(0, gtPos + 1); + const groupName = templateRemainder.slice(2, gtPos); + Assert(namedCaptures instanceof ObjectValue); + const capture = Q(yield* Get(namedCaptures, Value(groupName))); + if (capture instanceof UndefinedValue) { + refReplacement = ''; + } else { + refReplacement = (Q(yield* ToString(capture))).stringValue(); + } + } + } else { + ref = templateRemainder[0]; + refReplacement = ref; + } + const refLength = ref.length; + templateRemainder = templateRemainder.slice(refLength); + result.push(refReplacement); + } + let result_str; + try { + result_str = result.join(''); + } catch (e) { + // test262/test/staging/sm/String/replace-math.js + return surroundingAgent.Throw('RangeError', 'OutOfRange', 'String too long'); + } + return Value(result_str); +} diff --git a/src/runtime-semantics/GlobalDeclarationInstantiation.mts b/src/runtime-semantics/GlobalDeclarationInstantiation.mts new file mode 100644 index 0000000..6eb5750 --- /dev/null +++ b/src/runtime-semantics/GlobalDeclarationInstantiation.mts @@ -0,0 +1,141 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + BoundNames, + IsConstantDeclaration, + LexicallyDeclaredNames, + LexicallyScopedDeclarations, + VarDeclaredNames, + VarScopedDeclarations, +} from '../static-semantics/all.mts'; +import { Value } from '../value.mts'; +import { Q, NormalCompletion } from '../completion.mts'; +import { JSStringSet } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { InstantiateFunctionObject } from './all.mts'; +import { Assert, GlobalEnvironmentRecord } from '#self'; + +export function* GlobalDeclarationInstantiation(script: ParseNode.Script, env: GlobalEnvironmentRecord) { + // 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.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if ((yield* env.HasLexicalDeclaration(name)) === Value.true) { + return surroundingAgent.Throw('SyntaxError', 'AlreadyDeclared', name); + } + // 1. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). + const hasRestrictedGlobal = Q(yield* 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 ((yield* 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 JSStringSet(); + // 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(yield* 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 JSStringSet(); + // 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(yield* 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. Let privateEnv be null. + const privateEnv = Value.null; + // 16. 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(yield* env.CreateMutableBinding(dn, Value.false)); + } + } + } + // 17. 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 and privateEnv. + const fo = InstantiateFunctionObject(f, env, privateEnv); + // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). + Q(yield* env.CreateGlobalFunctionBinding(fn, fo, Value.false)); + } + // 18. For each String vn in declaredVarNames, in list order, do + for (const vn of declaredVarNames) { + // a. Perform ? env.CreateGlobalVarBinding(vn, false). + Q(yield* env.CreateGlobalVarBinding(vn, Value.false)); + } + // 19. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/HoistableDeclaration.mts b/src/runtime-semantics/HoistableDeclaration.mts new file mode 100644 index 0000000..16c4e07 --- /dev/null +++ b/src/runtime-semantics/HoistableDeclaration.mts @@ -0,0 +1,12 @@ +import { NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation */ +// HoistableDeclaration : +// GeneratorDeclaration +// AsyncFunctionDeclaration +// AsyncGeneratorDeclaration +export function Evaluate_HoistableDeclaration(_HoistableDeclaration: ParseNode.HoistableDeclaration) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/IdentifierReference.mts b/src/runtime-semantics/IdentifierReference.mts new file mode 100644 index 0000000..2f08b30 --- /dev/null +++ b/src/runtime-semantics/IdentifierReference.mts @@ -0,0 +1,15 @@ +import { StringValue } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { ReferenceRecord } from '../value.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { ResolveBinding } from '#self'; + +/** https://tc39.es/ecma262/#sec-identifiers-runtime-semantics-evaluation */ +// IdentifierReference : +// Identifier +// `yield` +// `await` +export function* Evaluate_IdentifierReference(IdentifierReference: ParseNode.IdentifierReference): PlainEvaluator { + // 1. Return ? ResolveBinding(StringValue of Identifier). + return yield* ResolveBinding(StringValue(IdentifierReference), undefined, IdentifierReference.strict); +} diff --git a/src/runtime-semantics/IfStatement.mts b/src/runtime-semantics/IfStatement.mts new file mode 100644 index 0000000..955ff36 --- /dev/null +++ b/src/runtime-semantics/IfStatement.mts @@ -0,0 +1,49 @@ +import { Evaluate } from '../evaluator.mts'; +import { + Completion, + EnsureCompletion, + NormalCompletion, + Q, + UpdateEmpty, +} from '../completion.mts'; +import { Value } from '../value.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + GetValue, + ToBoolean, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-if-statement-runtime-semantics-evaluation */ +// IfStatement : +// `if` `(` Expression `)` Statement `else` Statement +// `if` `(` Expression `)` Statement +export function* Evaluate_IfStatement({ Expression, Statement_a, Statement_b }: ParseNode.IfStatement) { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = Q(yield* Evaluate(Expression)); + // 2. Let exprValue be ! ToBoolean(? GetValue(exprRef)). + const exprValue = ToBoolean(Q(yield* 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/src/runtime-semantics/ImportCall.mts b/src/runtime-semantics/ImportCall.mts new file mode 100644 index 0000000..9a543e7 --- /dev/null +++ b/src/runtime-semantics/ImportCall.mts @@ -0,0 +1,134 @@ +import { surroundingAgent, HostLoadImportedModule } from '../host-defined/engine.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { + Q, X, IfAbruptRejectPromise, +} from '../completion.mts'; +import { + AbstractModuleRecord, AllImportAttributesSupported, Call, CyclicModuleRecord, EnumerableOwnProperties, Get, JSStringValue, NullValue, ObjectValue, Realm, Value, type ModuleRequestRecord, type PromiseObject, type ScriptRecord, +} from '../index.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { + GetValue, + ToString, + NewPromiseCapability, + GetActiveScriptOrModule, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-import-calls */ +// ImportCall : `import` `(` AssignmentExpression `)` +export function* Evaluate_ImportCall(ImportCall: ParseNode.ImportCall): ValueEvaluator { + Q(surroundingAgent.debugger_cannotPreview); + return yield* EvaluateImportCall(ImportCall.AssignmentExpression, ImportCall.OptionsExpression, ImportCall.Phase); +} + +/** https://tc39.es/ecma262/#sec-evaluate-import-call */ +function* EvaluateImportCall( + specifiersExpression: ParseNode.AssignmentExpressionOrHigher, + optionsExpression: undefined | ParseNode.AssignmentExpressionOrHigher, + phase: 'defer' | 'evaluation', +): ValueEvaluator { + // 1. Let referrer be ! GetActiveScriptOrModule(). + let referrer: NullValue | AbstractModuleRecord | ScriptRecord | Realm = X(GetActiveScriptOrModule()); + // 2. If referrer is null, set referrer to the current Realm Record. + if (referrer instanceof NullValue) { + referrer = surroundingAgent.currentRealmRecord; + } + // 3. Let specifierRef be ? Evaluation of AssignmentExpression. + const specifierRef = Q(yield* Evaluate(specifiersExpression)); + // 4. Let specifier be ? GetValue(specifierRef). + const specifier = Q(yield* GetValue(specifierRef)); + let options: Value; + // 5. If optionsExpression is present, then + if (optionsExpression) { + // a. Let optionsRef be ? Evaluation of optionsExpression. + const optionsRef = Q(yield* Evaluate(optionsExpression)); + // b. Let options be ? GetValue(optionsRef). + options = Q(yield* GetValue(optionsRef)); + } else { // 6. Else, + // a. Let options be undefined. + options = Value.undefined; + } + // 7. Let promiseCapability be ! NewPromiseCapability(%Promise%). + const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%'))); + // 8. Let specifierString be ToString(specifier). + const specifierString = yield* ToString(specifier); + // 9. IfAbruptRejectPromise(specifierString, promiseCapability). + IfAbruptRejectPromise(specifierString, promiseCapability); + __ts_cast__(specifierString); + // 10. Let attributes nw a new empty List. + const attributes = []; + // 11. If options is not undefined, then + if (options !== Value.undefined) { + // a. If options is not an Object, then + if (!(options instanceof ObjectValue)) { + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + X(Call(promiseCapability.Reject, Value.undefined, [ + surroundingAgent.Throw('TypeError', 'NotAnObject', options).Value, + ])); + // ii. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // b. Let attributesObj be Completion(Get(options, "with")). + const attributesObj = yield* Get(options, Value('with')); + // c. IfAbruptRejectPromise(attributesObj, promiseCapability). + IfAbruptRejectPromise(attributesObj, promiseCapability); + __ts_cast__(attributesObj); + // d. If attributesObj is not undefined, then + if (attributesObj !== Value.undefined) { + // i. If attributesObj is not an Object, then + if (!(attributesObj instanceof ObjectValue)) { + // 1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + X(Call(promiseCapability.Reject, Value.undefined, [ + surroundingAgent.Throw('TypeError', 'NotAnObject', attributesObj).Value, + ])); + // 2. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // ii. Let entries be Completion(EnumerableOwnProperties(attributesObj, key+value)). + const entries = yield* EnumerableOwnProperties(attributesObj, 'key+value'); + // iii. IfAbruptRejectPromise(entries, promiseCapability). + IfAbruptRejectPromise(entries, promiseCapability); + __ts_cast__(entries); + // iv. For each element entry of entries, do + for (const entry of entries) { + // 1. Let key be ! Get(entry, "0"). + const key = Q(yield* Get(entry, Value('0'))); + // 2. Let value be ! Get(entry, "1"). + const value = Q(yield* Get(entry, Value('1'))); + // 3. If key is a String, then + if (key instanceof JSStringValue) { + // a. If value is not a String, then + if (!(value instanceof JSStringValue)) { + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + X(Call(promiseCapability.Reject, Value.undefined, [ + surroundingAgent.Throw('TypeError', 'NotAString', value).Value, + ])); + // ii. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // b. Append the ImportAttribute Record { [[Key]]: key, [[Value]]: value } to attributes. + attributes.push({ Key: key, Value: value }); + } + } + // e. If AllImportAttributesSupported(attributes) is false, then + const unsupportedAttributeKey = AllImportAttributesSupported(attributes); + if (unsupportedAttributeKey) { + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + X(Call(promiseCapability.Reject, Value.undefined, [ + surroundingAgent.Throw('TypeError', 'UnsupportedImportAttribute', unsupportedAttributeKey).Value, + ])); + // ii. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; + } + // f. Sort attributes according to the lexicographic order of their [[Key]] field, treating the value of each such field as a sequence of UTF-16 code unit values. + attributes.sort((a, b) => (a.Key.value < b.Key.value ? -1 : 1)); + } + } + // 12. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Attributes]]: attributes }. + const moduleRequest: ModuleRequestRecord = { Specifier: specifierString, Attributes: attributes, Phase: phase }; + // 10. Perform HostLoadImportedModule(referrer, specifierString, ~empty~, promiseCapability). + HostLoadImportedModule(referrer as CyclicModuleRecord | ScriptRecord | Realm, moduleRequest, undefined, promiseCapability); + // 9. Return promiseCapability.[[Promise]]. + return promiseCapability.Promise; +} diff --git a/src/runtime-semantics/ImportDeclaration.mts b/src/runtime-semantics/ImportDeclaration.mts new file mode 100644 index 0000000..ec30368 --- /dev/null +++ b/src/runtime-semantics/ImportDeclaration.mts @@ -0,0 +1,9 @@ +import { NormalCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-module-semantics-runtime-semantics-evaluation */ +// ModuleItem : ImportDeclaration +export function Evaluate_ImportDeclaration(_ImportDeclaration: ParseNode.ImportDeclaration) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/ImportMeta.mts b/src/runtime-semantics/ImportMeta.mts new file mode 100644 index 0000000..5a22f33 --- /dev/null +++ b/src/runtime-semantics/ImportMeta.mts @@ -0,0 +1,45 @@ +import { HostGetImportMetaProperties, HostFinalizeImportMeta } from '../host-defined/engine.mts'; +import { ObjectValue, Value } from '../value.mts'; +import { X } from '../completion.mts'; +import { SourceTextModuleRecord } from '../modules.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + GetActiveScriptOrModule, + OrdinaryObjectCreate, + CreateDataPropertyOrThrow, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-meta-properties */ +// ImportMeta : `import` `.` `meta` +export function Evaluate_ImportMeta(_ImportMeta: ParseNode.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(importMeta instanceof ObjectValue); + // b. Return importMeta. + return importMeta; + } +} diff --git a/src/runtime-semantics/InstantiateArrowFunctionExpression.mts b/src/runtime-semantics/InstantiateArrowFunctionExpression.mts new file mode 100644 index 0000000..a70a341 --- /dev/null +++ b/src/runtime-semantics/InstantiateArrowFunctionExpression.mts @@ -0,0 +1,35 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { OrdinaryFunctionCreate, SetFunctionName, sourceTextMatchedBy } from '#self'; +import type { PrivateName, PropertyKeyValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-instantiatearrowfunctionexpression */ +// ArrowFunction : ArrowParameters `=>` ConciseBody +export function InstantiateArrowFunctionExpression(ArrowFunction: ParseNode.ArrowFunction, name?: PropertyKeyValue | PrivateName) { + const { ArrowParameters, ConciseBody } = ArrowFunction; + // 1. If name is not present, set name to "". + if (name === undefined) { + name = Value(''); + } + // 2. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. Let sourceText be the source text matched by ArrowFunction. + const sourceText = sourceTextMatchedBy(ArrowFunction); + // 5. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, ArrowParameters, ConciseBody, lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%Function.prototype%'), + sourceText, + ArrowParameters, + ConciseBody, + 'lexical-this', + scope, + privateScope, + ); + // 6. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 7. Return closure. + return closure; +} diff --git a/src/runtime-semantics/InstantiateAsyncArrowFunctionExpression.mts b/src/runtime-semantics/InstantiateAsyncArrowFunctionExpression.mts new file mode 100644 index 0000000..8a09226 --- /dev/null +++ b/src/runtime-semantics/InstantiateAsyncArrowFunctionExpression.mts @@ -0,0 +1,37 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { OrdinaryFunctionCreate, SetFunctionName, sourceTextMatchedBy } from '#self'; +import type { PrivateName, PropertyKeyValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncarrowfunctionexpression */ +// AsyncArrowFunction : ArrowParameters `=>` AsyncConciseBody +export function InstantiateAsyncArrowFunctionExpression(AsyncArrowFunction: ParseNode.AsyncArrowFunction, name?: PropertyKeyValue | PrivateName) { + const { ArrowParameters, AsyncConciseBody } = AsyncArrowFunction; + // 1. If name is not present, set name to "". + if (name === undefined) { + name = Value(''); + } + // 2. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. Let sourceText be the source text matched by AsyncArrowFunction. + const sourceText = sourceTextMatchedBy(AsyncArrowFunction); + // 5. Let parameters be AsyncArrowBindingIdentifier. + const parameters = ArrowParameters; + // 6. Let closure be OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, ArrowParameters, AsyncConciseBody, lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%AsyncFunction.prototype%'), + sourceText, + parameters, + AsyncConciseBody, + 'lexical-this', + scope, + privateScope, + ); + // 7. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 8. Return closure. + return closure; +} diff --git a/src/runtime-semantics/InstantiateAsyncFunctionExpression.mts b/src/runtime-semantics/InstantiateAsyncFunctionExpression.mts new file mode 100644 index 0000000..beb8c85 --- /dev/null +++ b/src/runtime-semantics/InstantiateAsyncFunctionExpression.mts @@ -0,0 +1,75 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + PrivateName, Value, type PropertyKeyValue, +} from '../value.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + OrdinaryFunctionCreate, + SetFunctionName, + sourceTextMatchedBy, + DeclarativeEnvironmentRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncfunctionexpression */ +export function InstantiateAsyncFunctionExpression(AsyncFunctionExpression: ParseNode.AsyncFunctionExpression, name?: PropertyKeyValue | PrivateName) { + const { BindingIdentifier, FormalParameters, AsyncBody } = AsyncFunctionExpression; + if (BindingIdentifier) { + // 1. Assert: name is not present. + Assert(name === undefined); + // 2. Set name to StringValue of BindingIdentifier. + name = StringValue(BindingIdentifier); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let funcEnv be ! NewDeclarativeEnvironment(scope). + const funcEnv = X(new DeclarativeEnvironmentRecord(scope)); + // 5. Perform ! funcEnv.CreateImmutableBinding(name, false). + X(funcEnv.CreateImmutableBinding(name, Value.false)); + // 6. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 7. Let sourceText be the source text matched by AsyncFunctionExpression. + const sourceText = sourceTextMatchedBy(AsyncFunctionExpression); + // 8. Let closure be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, FormalParameters, AsyncBody, non-lexical-this, funcEnv, privateScope). + const closure = X(OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%AsyncFunction.prototype%'), + sourceText, + FormalParameters, + AsyncBody, + 'non-lexical-this', + funcEnv, + privateScope, + )); + // 9. Perform ! SetFunctionName(closure, name). + X(SetFunctionName(closure, name)); + // 10. Perform ! funcEnv.InitializeBinding(name, closure). + X(funcEnv.InitializeBinding(name, closure)); + // 11. Return closure. + return closure; + } + // 1. If name is not present, set name to "". + if (name === undefined) { + name = Value(''); + } + // 2. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. Let sourceText be the source text matched by AsyncFunctionExpression. + const sourceText = sourceTextMatchedBy(AsyncFunctionExpression); + // 5. Let closure be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, FormalParameters, AsyncBody, non-lexical-this, scope, privateScope). + const closure = X(OrdinaryFunctionCreate( + surroundingAgent.intrinsic('%AsyncFunction.prototype%'), + sourceText, + FormalParameters, + AsyncBody, + 'non-lexical-this', + scope, + privateScope, + )); + // 6. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 7. Return closure. + return closure; +} diff --git a/src/runtime-semantics/InstantiateAsyncGeneratorFunctionExpression.mts b/src/runtime-semantics/InstantiateAsyncGeneratorFunctionExpression.mts new file mode 100644 index 0000000..9fff361 --- /dev/null +++ b/src/runtime-semantics/InstantiateAsyncGeneratorFunctionExpression.mts @@ -0,0 +1,90 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, Descriptor, type PropertyKeyValue, PrivateName, +} from '../value.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + DefinePropertyOrThrow, + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + sourceTextMatchedBy, + DeclarativeEnvironmentRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncgeneratorfunctionexpression */ +// AsyncGeneratorExpression : +// `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +// `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +export function InstantiateAsyncGeneratorFunctionExpression(AsyncGeneratorExpression: ParseNode.AsyncGeneratorExpression, name?: PropertyKeyValue | PrivateName) { + const { BindingIdentifier, FormalParameters, AsyncGeneratorBody } = AsyncGeneratorExpression; + if (BindingIdentifier) { + // 1. Assert: name is not present. + Assert(name === undefined); + // 2. Set name to StringValue of BindingIdentifier. + name = StringValue(BindingIdentifier); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let funcEnv be NewDeclarativeEnvironment(scope). + const funcEnv = new DeclarativeEnvironmentRecord(scope); + // 5. Perform funcEnv.CreateImmutableBinding(name, false). + funcEnv.CreateImmutableBinding(name, Value.false); + // 6. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 7. Let source text be the source textmatched by AsyncGeneratorExpression. + const sourceText = sourceTextMatchedBy(AsyncGeneratorExpression); + // 8. Let closure be OrdinaryFunctionCreate(%AsyncGeneratorFunction.prototype%, sourceText, FormalParameters, AsyncGeneratorBody, non-lexical-this, funcEnv, privateScope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, FormalParameters, AsyncGeneratorBody, 'non-lexical-this', funcEnv, privateScope)); + // 9. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 10. Let prototype be OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype.prototype%')); + // 11. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow( + closure, + Value('prototype'), + Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }), + )); + // 12. Perform funcEnv.InitializeBinding(name, closure). + X(funcEnv.InitializeBinding(name, closure)); + // 13. Return closure. + return closure; + } + // 1. If name is not present, set name to "". + if (name === undefined) { + name = Value(''); + } + // 2. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. Let sourceText be the source text matched by AsyncGeneratorExpression. + const sourceText = sourceTextMatchedBy(AsyncGeneratorExpression); + // 5. Let closure be ! OrdinaryFunctionCreate(%AsyncGeneratorFunction.prototype%, sourceText, FormalParameters, AsyncGeneratorBody, non-lexical-this, scope, privateScope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, FormalParameters, AsyncGeneratorBody, 'non-lexical-this', scope, privateScope)); + // 6. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 7. Let prototype be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype.prototype%')); + // 8. Perform ! DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow( + closure, + Value('prototype'), + Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }), + )); + // 9. Return closure. + return closure; +} diff --git a/src/runtime-semantics/InstantiateFunctionObject.mts b/src/runtime-semantics/InstantiateFunctionObject.mts new file mode 100644 index 0000000..45e9c16 --- /dev/null +++ b/src/runtime-semantics/InstantiateFunctionObject.mts @@ -0,0 +1,123 @@ +import { X } from '../completion.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { OutOfRange } from '../helpers.mts'; +import { Descriptor, Value } from '../value.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + DefinePropertyOrThrow, + MakeConstructor, + OrdinaryObjectCreate, + SetFunctionName, + OrdinaryFunctionCreate, + sourceTextMatchedBy, +} from '#self'; +import type { EnvironmentRecord, NullValue, PrivateEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-instantiatefunctionobject */ +// FunctionDeclaration : +// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +export function InstantiateFunctionObject_FunctionDeclaration(FunctionDeclaration: ParseNode.FunctionDeclaration, env: EnvironmentRecord, privateEnv: PrivateEnvironmentRecord | NullValue) { + const { BindingIdentifier, FormalParameters, FunctionBody } = FunctionDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : 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, privateScope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, FormalParameters, FunctionBody, 'non-lexical-this', env, privateEnv)); + // 4. Perform SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Perform MakeConstructor(F). + MakeConstructor(F); + // 6. Return F. + return F; +} + +/** https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-instantiatefunctionobject */ +// GeneratorDeclaration : +// `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +export function InstantiateFunctionObject_GeneratorDeclaration(GeneratorDeclaration: ParseNode.GeneratorDeclaration, env: EnvironmentRecord, privateEnv: PrivateEnvironmentRecord | NullValue) { + const { BindingIdentifier, FormalParameters, GeneratorBody } = GeneratorDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : Value('default'); + // 2. Let sourceText be the source text matched by GeneratorDeclaration. + const sourceText = sourceTextMatchedBy(GeneratorDeclaration); + // 3. Let F be OrdinaryFunctionCreate(%GeneratorFunction.prototype%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, scope, privateScope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype%'), sourceText, FormalParameters, GeneratorBody, 'non-lexical-this', env, privateEnv)); + // 4. Perform SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%). + const prototype = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype.prototype%'))); + // 6. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(F, Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 7. Return F. + return F; +} + +/** https://tc39.es/ecma262/#sec-async-function-definitions-InstantiateFunctionObject */ +// AsyncFunctionDeclaration : +// `async` `function` BindingIdentifier `(` FormalParameters `)` `{` AsyncBody `}` +// `async` `function` `(` FormalParameters `)` `{` AsyncBody `}` +export function InstantiateFunctionObject_AsyncFunctionDeclaration(AsyncFunctionDeclaration: ParseNode.AsyncFunctionDeclaration, env: EnvironmentRecord, privateEnv: PrivateEnvironmentRecord | NullValue) { + const { BindingIdentifier, FormalParameters, AsyncBody } = AsyncFunctionDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : 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, AsyncBody, non-lexical-this, scope, privateScope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, FormalParameters, AsyncBody, 'non-lexical-this', env, privateEnv)); + // 4. Perform ! SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Return F. + return F; +} + +/** https://tc39.es/ecma262/#sec-asyncgenerator-definitions-evaluatebody */ +// AsyncGeneratorDeclaration : +// `async` `function` `*` BindingIdentifier `(` FormalParameters`)` `{` AsyncGeneratorBody `}` +// `async` `function` `*` `(` FormalParameters`)` `{` AsyncGeneratorBody `}` +export function InstantiateFunctionObject_AsyncGeneratorDeclaration(AsyncGeneratorDeclaration: ParseNode.AsyncGeneratorDeclaration, env: EnvironmentRecord, privateEnv: PrivateEnvironmentRecord | NullValue) { + const { BindingIdentifier, FormalParameters, AsyncGeneratorBody } = AsyncGeneratorDeclaration; + // 1. Let name be StringValue of BindingIdentifier. + const name = BindingIdentifier ? StringValue(BindingIdentifier) : Value('default'); + // 2. Let sourceText be the source text matched by AsyncGeneratorDeclaration. + const sourceText = sourceTextMatchedBy(AsyncGeneratorDeclaration); + // 3. Let F be ! OrdinaryFunctionCreate(%AsyncGeneratorFunction.prototype%, sourceText, FormalParameters, AsyncGeneratorBody, non-lexical-this, scope, privateScope). + const F = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, FormalParameters, AsyncGeneratorBody, 'non-lexical-this', env, privateEnv)); + // 4. Perform ! SetFunctionName(F, name). + SetFunctionName(F, name); + // 5. Let prototype be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%). + const prototype = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype.prototype%'))); + // 6. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(F, Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 7. Return F. + return F; +} + +export function InstantiateFunctionObject(AnyFunctionDeclaration: ParseNode.FunctionDeclaration | ParseNode.GeneratorDeclaration | ParseNode.AsyncFunctionDeclaration | ParseNode.AsyncGeneratorDeclaration, env: EnvironmentRecord, privateEnv: PrivateEnvironmentRecord | NullValue) { + switch (AnyFunctionDeclaration.type) { + case 'FunctionDeclaration': + return InstantiateFunctionObject_FunctionDeclaration(AnyFunctionDeclaration, env, privateEnv); + case 'GeneratorDeclaration': + return InstantiateFunctionObject_GeneratorDeclaration(AnyFunctionDeclaration, env, privateEnv); + case 'AsyncFunctionDeclaration': + return InstantiateFunctionObject_AsyncFunctionDeclaration(AnyFunctionDeclaration, env, privateEnv); + case 'AsyncGeneratorDeclaration': + return InstantiateFunctionObject_AsyncGeneratorDeclaration(AnyFunctionDeclaration, env, privateEnv); + + default: + throw new OutOfRange('InstantiateFunctionObject', AnyFunctionDeclaration); + } +} diff --git a/src/runtime-semantics/InstantiateGeneratorFunctionExpression.mts b/src/runtime-semantics/InstantiateGeneratorFunctionExpression.mts new file mode 100644 index 0000000..81bc449 --- /dev/null +++ b/src/runtime-semantics/InstantiateGeneratorFunctionExpression.mts @@ -0,0 +1,82 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, Descriptor, type PropertyKeyValue, PrivateName, +} from '../value.mts'; +import { X } from '../completion.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + DefinePropertyOrThrow, + OrdinaryFunctionCreate, + OrdinaryObjectCreate, + SetFunctionName, + sourceTextMatchedBy, + DeclarativeEnvironmentRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-instantiategeneratorfunctionexpression */ +// GeneratorExpression : +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +// `function` `* `BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` +export function InstantiateGeneratorFunctionExpression(GeneratorExpression: ParseNode.GeneratorExpression, name?: PropertyKeyValue | PrivateName) { + const { BindingIdentifier, FormalParameters, GeneratorBody } = GeneratorExpression; + if (BindingIdentifier) { + // 1. Assert: name is not present. + Assert(name === undefined); + // 2. Set name to StringValue of BindingIdentifier. + name = StringValue(BindingIdentifier); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let funcEnv be NewDeclarativeEnvironment(scope). + const funcEnv = new DeclarativeEnvironmentRecord(scope); + // 5. Perform funcEnv.CreateImmutableBinding(name, false). + funcEnv.CreateImmutableBinding(name, Value.false); + // 6. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 7. Let sourceText be the source text matched by GeneratorExpression. + const sourceText = sourceTextMatchedBy(GeneratorExpression); + // 8. Let closure be OrdinaryFunctionCreate(%GeneratorFunction.prototype%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, funcEnv, privateScope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype%'), sourceText, FormalParameters, GeneratorBody, 'non-lexical-this', funcEnv, privateScope); + // 9. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 10. Let prototype be ! OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%). + const prototype = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype.prototype%'))); + // 11. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(closure, Value('prototype'), new Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 12. Perform funcEnv.InitializeBinding(name, closure). + X(funcEnv.InitializeBinding(name, closure)); + // 13. Return closure. + return closure; + } + // 1. If name is not present, set name to "". + if (name === undefined) { + name = Value(''); + } + // 2. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. Let sourceText be the source text matched by GeneratorExpression. + const sourceText = sourceTextMatchedBy(GeneratorExpression); + // 5. Let closure be OrdinaryFunctionCreate(%GeneratorFunction.prototype%, sourceText, FormalParameters, GeneratorBody, non-lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype%'), sourceText, FormalParameters, GeneratorBody, 'non-lexical-this', scope, privateScope); + // 6. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 7. Let prototype be ! OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%). + const prototype = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype.prototype%'))); + // 8. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(closure, Value('prototype'), new Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + // 9. Return closure. + return closure; +} diff --git a/src/runtime-semantics/InstantiateOrdinaryFunctionExpression.mts b/src/runtime-semantics/InstantiateOrdinaryFunctionExpression.mts new file mode 100644 index 0000000..584f68f --- /dev/null +++ b/src/runtime-semantics/InstantiateOrdinaryFunctionExpression.mts @@ -0,0 +1,64 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { PrivateName, Value, type PropertyKeyValue } from '../value.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + OrdinaryFunctionCreate, + SetFunctionName, + MakeConstructor, + sourceTextMatchedBy, + DeclarativeEnvironmentRecord, X, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression */ +// FunctionExpression : +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` +export function InstantiateOrdinaryFunctionExpression(FunctionExpression: ParseNode.FunctionExpression, name?: PropertyKeyValue | PrivateName) { + const { BindingIdentifier, FormalParameters, FunctionBody } = FunctionExpression; + if (BindingIdentifier) { + // 1. Assert: name is not present. + Assert(name === undefined); + // 2. Set name to StringValue of BindingIdentifier. + name = StringValue(BindingIdentifier); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let funcEnv be NewDeclarativeEnvironment(scope). + const funcEnv = new DeclarativeEnvironmentRecord(scope); + // 5. Perform funcEnv.CreateImmutableBinding(name, false). + funcEnv.CreateImmutableBinding(name, Value.false); + // 6. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 7. Let sourceText be the source text matched by FunctionExpression. + const sourceText = sourceTextMatchedBy(FunctionExpression); + // 8. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, FormalParameters, FunctionBody, non-lexical-this, funcEnv, privateScope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, FormalParameters, FunctionBody, 'non-lexical-this', funcEnv, privateScope); + // 9. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 10. Perform MakeConstructor(closure). + MakeConstructor(closure); + // 11. Perform funcEnv.InitializeBinding(name, closure). + X(funcEnv.InitializeBinding(name, closure)); + // 12. Return closure. + return closure; + } + // 1. If name is not present, set name to "". + if (name === undefined) { + name = Value(''); + } + // 2. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 4. Let sourceText be the source text matched by FunctionExpression. + const sourceText = sourceTextMatchedBy(FunctionExpression); + // 5. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, FormalParameters, FunctionBody, non-lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, FormalParameters, FunctionBody, 'non-lexical-this', scope, privateScope); + // 6. Perform SetFunctionName(closure, name). + SetFunctionName(closure, name); + // 7. Perform MakeConstructor(closure). + MakeConstructor(closure); + // 8. Return closure. + return closure; +} diff --git a/src/runtime-semantics/IteratorBindingInitialization.mts b/src/runtime-semantics/IteratorBindingInitialization.mts new file mode 100644 index 0000000..2cdb761 --- /dev/null +++ b/src/runtime-semantics/IteratorBindingInitialization.mts @@ -0,0 +1,212 @@ +import { Value } from '../value.mts'; +import { + NormalCompletion, + Q, X, +} from '../completion.mts'; +import { Evaluate, type PlainEvaluator } from '../evaluator.mts'; +import { + StringValue, + IsAnonymousFunctionDefinition, +} from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { __ts_cast__ } from '../helpers.mts'; +import { NamedEvaluation, BindingInitialization } from './all.mts'; +import { + Assert, + GetValue, + InitializeReferencedBinding, + IteratorStep, + PutValue, + ResolveBinding, + ArrayCreate, + CreateDataPropertyOrThrow, + ToString, + F, + type IteratorRecord, + + IteratorStepValue, + UndefinedValue, type EnvironmentRecord, type FunctionDeclaration, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-iteratorbindinginitialization */ +// FormalParameters : +// [empty] +// FormalParameterList `,` FunctionRestParameter +export function* IteratorBindingInitialization_FormalParameters(FormalParameters: ParseNode.FormalParameters, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) { + 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: ParseNode.FormalParametersElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) { + // TODO + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return IteratorBindingInitialization_BindingElement(BindingElement as any, iteratorRecord, environment); +} + +// FunctionRestParameter : BindingRestElement +function IteratorBindingInitialization_FunctionRestParameter(FunctionRestParameter: ParseNode.FunctionRestParameter, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) { + return IteratorBindingInitialization_BindingRestElement(FunctionRestParameter, iteratorRecord, environment); +} + +// BindingElement : +// SingleNameBinding +// BindingPattern +function IteratorBindingInitialization_BindingElement(BindingElement: ParseNode.BindingElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) { + if ('BindingPattern' in BindingElement) { + return IteratorBindingInitialization_BindingPattern(BindingElement, iteratorRecord, environment); + } + return IteratorBindingInitialization_SingleNameBinding(BindingElement, iteratorRecord, environment); +} + +// SingleNameBinding : BindingIdentifier Initializer? +function* IteratorBindingInitialization_SingleNameBinding({ BindingIdentifier, Initializer }: ParseNode.SingleNameBinding, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator { + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(BindingIdentifier); + // 2. Let lhs be ? ResolveBinding(bindingId, environment). + const lhs = Q(yield* ResolveBinding(bindingId, environment, BindingIdentifier.strict)); + let v: Value = Value.undefined; + // 3. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + // d. If next is not DONE, + if (next !== 'done') { + v = next; + } + } + // 5. If Initializer is present and v is undefined, then + if (Initializer && v === Value.undefined) { + if (IsAnonymousFunctionDefinition(Initializer)) { + v = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, bindingId)); + } else { + const defaultValue = Q(yield* Evaluate(Initializer)); + v = Q(yield* GetValue(defaultValue)); + } + } + // 6. If environment is undefined, return ? PutValue(lhs, v). + if (environment === Value.undefined) { + return Q(yield* PutValue(lhs, v)); + } + // 7. Return InitializeReferencedBinding(lhs, v). + return yield* InitializeReferencedBinding(lhs, X(v)); +} + +// BindingRestElement : +// `...` BindingIdentifier +// `...` BindingPattern +function* IteratorBindingInitialization_BindingRestElement({ BindingIdentifier, BindingPattern }: ParseNode.BindingRestElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) { + if (BindingIdentifier) { + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + const lhs = Q(yield* ResolveBinding(StringValue(BindingIdentifier), environment, BindingIdentifier.strict)); + // 2. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(0)); + // 3. Let n be 0. + let n = 0; + // 4. Repeat, + while (true) { + let next: 'done' | Value = 'done'; + // a. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // i. Let next be ? IteratorStepValue(iteratorRecord). + next = Q(yield* IteratorStepValue(iteratorRecord)); + } + if (next === 'done') { + // i. If environment is undefined, return ? PutValue(lhs, A). + if (environment === Value.undefined) { + return Q(yield* PutValue(lhs, A)); + } + // ii. Return InitializeReferencedBinding(lhs, A). + return yield* InitializeReferencedBinding(lhs, A); + } + // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), next). + X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), next)); + // g. Set n to n + 1. + n += 1; + } + } else { + // 1. Let A be ! ArrayCreate(0). + const A = X(ArrayCreate(0)); + // 2. Let n be 0. + let n = 0; + // 3. Repeat, + while (true) { + let next: 'done' | Value = 'done'; + // a. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // i. Let next be ? IteratorStepValue(iteratorRecord). + next = Q(yield* IteratorStepValue(iteratorRecord)); + } + // b. If next is done, then + if (next === 'done') { + // i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments. + return yield* BindingInitialization(BindingPattern!, A, environment); + } + // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), next). + X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), Q(next))); + // g. Set n to n + 1. + n += 1; + } + } +} + +function* IteratorBindingInitialization_BindingPattern({ BindingPattern, Initializer }: ParseNode.BindingElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) { + let v: Value = Value.undefined; + // 1. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Let next be ? IteratorStepValue(iteratorRecord). + const next = Q(yield* IteratorStepValue(iteratorRecord)); + if (next !== 'done') { + v = next; + } + } + // 3. If Initializer is present and v is undefined, then + if (Initializer && v instanceof UndefinedValue) { + // a. Let defaultValue be the result of evaluating Initializer. + const defaultValue = Q(yield* Evaluate(Initializer)); + // b. Set v to ? GetValue(defaultValue). + v = Q(yield* GetValue(defaultValue)); + } + // 4. Return the result of performing BindingInitialization of BindingPattern with v and environment as the arguments. + return yield* BindingInitialization(BindingPattern, X(v), environment); +} + +function* IteratorDestructuringAssignmentEvaluation(node: ParseNode.Elision, iteratorRecord: IteratorRecord): PlainEvaluator { + Assert(node.type === 'Elision'); + // 1. If iteratorRecord.[[Done]] is false, then + if (iteratorRecord.Done === Value.false) { + // a. Perform ? IteratorStep(iteratorRecord). + Q(yield* IteratorStep(iteratorRecord)); + } + // 2. Return NormalCompletion(empty). + return NormalCompletion(undefined); +} + +export function* IteratorBindingInitialization_ArrayBindingPattern({ BindingElementList, BindingRestElement }: ParseNode.ArrayBindingPattern, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator { + for (const BindingElement of BindingElementList) { + if (BindingElement.type === 'Elision') { + Q(yield* IteratorDestructuringAssignmentEvaluation(BindingElement, iteratorRecord)); + } else { + // TODO + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Q(yield* IteratorBindingInitialization_BindingElement(BindingElement as any, iteratorRecord, environment)); + } + } + + if (BindingRestElement) { + return Q(yield* IteratorBindingInitialization_BindingRestElement(BindingRestElement, iteratorRecord, environment)); + } + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/KeyedBindingInitialization.mts b/src/runtime-semantics/KeyedBindingInitialization.mts new file mode 100644 index 0000000..614786c --- /dev/null +++ b/src/runtime-semantics/KeyedBindingInitialization.mts @@ -0,0 +1,61 @@ +import { Value } from '../value.mts'; +import { Evaluate } from '../evaluator.mts'; +import { StringValue, IsAnonymousFunctionDefinition } from '../static-semantics/all.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + NamedEvaluation, + BindingInitialization, +} from './all.mts'; +import { + GetV, + GetValue, + PutValue, + ResolveBinding, + InitializeReferencedBinding, +} from '#self'; +import type { + EnvironmentRecord, FunctionDeclaration, PropertyKeyValue, UndefinedValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization */ +export function* KeyedBindingInitialization(node: ParseNode.BindingElement | ParseNode.SingleNameBinding, value: Value, environment: EnvironmentRecord | UndefinedValue, propertyName: PropertyKeyValue) { + if (node.type === 'BindingElement') { + // 1. Let v be ? GetV(value, propertyName). + let v = Q(yield* 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 = Q(yield* Evaluate(node.Initializer)); + // b. Set v to ? GetValue(defaultValue). + v = Q(yield* 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(yield* ResolveBinding(bindingId, environment, node.BindingIdentifier.strict)); + // 3. Let v be ? GetV(value, propertyName). + let v = Q(yield* 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 as FunctionDeclaration, bindingId)) as Value; + } else { // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + const defaultValue = Q(yield* Evaluate(node.Initializer)); + // ii. Set v to ? GetValue(defaultValue). + v = Q(yield* GetValue(defaultValue)); + } + } + // 5. If environment is undefined, return ? PutValue(lhs, v). + if (environment === Value.undefined) { + return Q(yield* PutValue(lhs, v)); + } + // 6. Return InitializeReferencedBinding(lhs, v). + return yield* InitializeReferencedBinding(lhs, v); + } +} diff --git a/src/runtime-semantics/LabelledEvaluation.mts b/src/runtime-semantics/LabelledEvaluation.mts new file mode 100644 index 0000000..aa09c12 --- /dev/null +++ b/src/runtime-semantics/LabelledEvaluation.mts @@ -0,0 +1,753 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + JSStringValue, ObjectValue, ReferenceRecord, Value, +} from '../value.mts'; +import { + Evaluate, type Evaluator, type PlainEvaluator, type StatementEvaluator, +} from '../evaluator.mts'; +import { + BoundNames, + IsConstantDeclaration, + IsDestructuring, + StringValue, + type DestructuringParseNode, +} from '../static-semantics/all.mts'; +import { CreateForInIterator, type ForInIteratorInstance } from '../intrinsics/ForInIteratorPrototype.mts'; +import { + Completion, + NormalCompletion, + AbruptCompletion, + UpdateEmpty, + EnsureCompletion, + Await, + Q, X, + type PlainCompletion, + BreakCompletion, +} from '../completion.mts'; +import { JSStringSet, OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Evaluate_SwitchStatement, + Evaluate_VariableDeclarationList, + BindingInitialization, + DestructuringAssignmentEvaluation, + refineLeftHandSideExpression, +} from './all.mts'; +import { + Assert, + Call, + GetIterator, + GetValue, + PutValue, + GetV, + ResolveBinding, + InitializeReferencedBinding, + IteratorComplete, + IteratorValue, + IteratorClose, + AsyncIteratorClose, + ToBoolean, + ToObject, + SameValue, + type IteratorRecord, +} from '#self'; +import { DeclarativeEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-loopcontinues */ +function LoopContinues(completion: Completion, labelSet: JSStringSet) { + // 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: ParseNode.LabelledStatement | ParseNode.BreakableStatement, labelSet: JSStringSet): StatementEvaluator { + 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); + } +} + +/** https://tc39.es/ecma262/#sec-labelled-statements-runtime-semantics-labelledevaluation */ +// LabelledStatement : LabelIdentifier `:` LabelledItem +function* LabelledEvaluation_LabelledStatement({ LabelIdentifier, LabelledItem }: ParseNode.LabelledStatement, labelSet: JSStringSet) { + // 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)) as Completion; + // 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: ParseNode.LabelledItem, labelSet: JSStringSet) { + 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); + } +} + +/** https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-labelledevaluation */ +// BreakableStatement : +// IterationStatement +// SwitchStatement +// +// IterationStatement : +// (DoWhileStatement) +// (WhileStatement) +function* LabelledEvaluation_BreakableStatement(BreakableStatement: ParseNode.BreakableStatement, labelSet: JSStringSet): StatementEvaluator { + 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) as Completion; + } + default: + throw new OutOfRange('LabelledEvaluation_BreakableStatement', BreakableStatement); + } +} + +function LabelledEvaluation_IterationStatement(IterationStatement: ParseNode.IterationStatement, labelSet: JSStringSet): StatementEvaluator { + 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); + } +} + +/** https://tc39.es/ecma262/#sec-do-while-statement-runtime-semantics-labelledevaluation */ +// IterationStatement : +// `do` Statement `while` `(` Expression `)` `;` +function* LabelledEvaluation_IterationStatement_DoWhileStatement({ Statement, Expression }: ParseNode.DoWhileStatement, labelSet: JSStringSet) { + // 1. Let V be undefined. + let V: Value = Value.undefined; + // 2. Repeat, + while (true) { + // a. Let stmtResult be the result of evaluating Statement. + const stmtResult = EnsureCompletion(yield* Evaluate(Statement)) as Completion; + // 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 = Q(yield* Evaluate(Expression)); + // e. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(yield* GetValue(exprRef)); + // f. If ! ToBoolean(exprValue) is false, return NormalCompletion(V). + if (X(ToBoolean(exprValue)) === Value.false) { + return NormalCompletion(V); + } + } +} + + +/** https://tc39.es/ecma262/#sec-while-statement-runtime-semantics-labelledevaluation */ +// IterationStatement : +// `while` `(` Expression `)` Statement +function* LabelledEvaluation_IterationStatement_WhileStatement({ Expression, Statement }: ParseNode.WhileStatement, labelSet: JSStringSet) { + // 1. Let V be undefined. + let V: Value = Value.undefined; + // 2. Repeat, + while (true) { + // a. Let exprRef be the result of evaluating Expression. + const exprRef = Q(yield* Evaluate(Expression)); + // b. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(yield* 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; + } + } +} + +/** https://tc39.es/ecma262/#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: ParseNode.ForStatement, labelSet: JSStringSet) { + 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 = new DeclarativeEnvironmentRecord(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: JSStringValue[]; + 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); + Q(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 = Q(yield* Evaluate(Expression_a)); + // b. Perform ? GetValue(exprRef). + Q(yield* 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: ParseNode.ForInStatement, labelSet: JSStringSet): StatementEvaluator { + 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 as IteratorRecord, '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 as IteratorRecord, '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 as IteratorRecord, '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: ParseNode.ForAwaitStatement, labelSet: JSStringSet): StatementEvaluator { + 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 as IteratorRecord, '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 as IteratorRecord, '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 as IteratorRecord, 'iterate', 'lexicalBinding', labelSet, 'async')); + } + default: + throw new OutOfRange('LabelledEvaluation_IterationStatement_ForAwaitStatement', ForAwaitStatement); + } +} + +/** https://tc39.es/ecma262/#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: ParseNode.ForOfStatement, labelSet: JSStringSet): StatementEvaluator { + 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 as IteratorRecord, '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 as IteratorRecord, '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 as IteratorRecord, 'iterate', 'lexicalBinding', labelSet)); + } + default: + throw new OutOfRange('LabelledEvaluation_BreakableStatement_ForOfStatement', ForOfStatement); + } +} + +/** https://tc39.es/ecma262/#sec-forbodyevaluation */ +function* ForBodyEvaluation(test: ParseNode.Expression | undefined, increment: ParseNode.Expression | undefined, stmt: ParseNode.Statement, perIterationBindings: readonly JSStringValue[], labelSet: JSStringSet) { + // 1. Let V be undefined. + let V: Value = Value.undefined; + // 2. Perform ? CreatePerIterationEnvironment(perIterationBindings). + Q(yield* 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 = Q(yield* Evaluate(test)); + // ii. Let testValue be ? GetValue(testRef). + const testValue = Q(yield* 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(yield* CreatePerIterationEnvironment(perIterationBindings)); + // f. If increment is not [empty], then + if (increment) { + // i. Let incRef be the result of evaluating increment. + const incRef = Q(yield* Evaluate(increment)); + // ii. Perform ? GetValue(incRef). + Q(yield* GetValue(incRef)); + } + } +} + +/** https://tc39.es/ecma262/#sec-createperiterationenvironment */ +function* CreatePerIterationEnvironment(perIterationBindings: readonly JSStringValue[]): PlainEvaluator { + // 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 = new DeclarativeEnvironmentRecord(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(yield* lastIterationEnv.GetBindingValue(bn, Value.true)); + // iii. Perform thisIterationEnv.InitializeBinding(bn, lastValue). + yield* thisIterationEnv.InitializeBinding(bn, lastValue); + } + // f. Set the running execution context's LexicalEnvironment to thisIterationEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = thisIterationEnv; + } + // 2. Return undefined. + return undefined; +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation */ +function* ForInOfHeadEvaluation(uninitializedBoundNames: readonly JSStringValue[], expr: ParseNode.Expression | ParseNode.AssignmentExpression, iterationKind: 'enumerate' | 'iterate' | 'async-iterate'): Evaluator | BreakCompletion> { + // 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 = new DeclarativeEnvironmentRecord(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 = Q(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(yield* 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, 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(yield* GetIterator(exprValue, iteratorHint)); + } +} +interface ForInOfHeadEvaluationResult { + readonly Iterator: ForInIteratorInstance; + readonly NextMethod: Value; + readonly Done: Value; +} + +/** https://tc39.es/ecma262/#sec-enumerate-object-properties */ +function EnumerateObjectProperties(O: ObjectValue) { + return CreateForInIterator(O); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset */ +function* ForInOfBodyEvaluation(lhs: ParseNode, stmt: ParseNode.Statement, iteratorRecord: IteratorRecord, iterationKind: 'enumerate' | 'iterate', lhsKind: 'assignment' | 'lexicalBinding' | 'varBinding', labelSet: JSStringSet, iteratorKind?: 'sync' | 'async'): StatementEvaluator { + // 1. If iteratorKind is not present, set iteratorKind to sync. + if (iteratorKind === undefined) { + iteratorKind = 'sync'; + } + // 2. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 3. Let V be undefined. + let V: Value = 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 as DestructuringParseNode); + } + // 6. Repeat, + while (true) { + // a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + let nextResult = Q(yield* 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 (!(nextResult instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', nextResult); + } + // d. Let done be ? IteratorComplete(nextResult). + const done = Q(yield* 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(yield* 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 = new DeclarativeEnvironmentRecord(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: PlainCompletion; + // 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 = yield* InitializeReferencedBinding(Q(lhsRef) as ReferenceRecord, nextValue); + } else { // iii. Else, + status = yield* PutValue(Q(lhsRef) as ReferenceRecord, 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 as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern, 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 iterationKind is enumerate, then + if (iterationKind === 'enumerate') { + // 1. Return status. + return status as Completion; + } else { // iv. Else, + // 1. Assert: iterationKind is iterate. + Assert(iterationKind === 'iterate'); + // 2. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). + if (iteratorKind === 'async') { + return Q(yield* AsyncIteratorClose(iteratorRecord, status)) as Completion; + } + // 3 .Return ? IteratorClose(iteratorRecord, status). + return Q(yield* IteratorClose(iteratorRecord, EnsureCompletion(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) { + // Set _status_ to Completion(UpdateEmpty(_result_, _V_)). + status = UpdateEmpty(result, V); + // i. If iterationKind is enumerate, then + if (iterationKind === 'enumerate') { + // 1. Return ? _status_. + return Q(status as Completion); + } else { // ii. Else, + // 1. Assert: iterationKind is iterate. + Assert(iterationKind === 'iterate'); + // 2. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). + if (iteratorKind === 'async') { + return Q(yield* AsyncIteratorClose(iteratorRecord, status)) as Completion; + } + // 3. Return ? IteratorClose(iteratorRecord, status). + return Q(yield* IteratorClose(iteratorRecord, EnsureCompletion(status))); + } + } + // o. If result.[[Value]] is not empty, set V to result.[[Value]]. + if (result.Value !== undefined) { + V = result.Value; + } + } +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-bindinginstantiation */ +// ForDeclaration : LetOrConst ForBinding +function BindingInstantiation({ LetOrConst, ForBinding }: ParseNode.ForDeclaration, environment: DeclarativeEnvironmentRecord) { + // 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)); + } + } +} + +/** https://tc39.es/ecma262/#sec-for-in-and-for-of-statements-runtime-semantics-evaluation */ +// ForBinding : BindingIdentifier +export function Evaluate_ForBinding({ BindingIdentifier, strict }: ParseNode.ForBinding) { + // 1. Let bindingId be StringValue of BindingIdentifier. + const bindingId = StringValue(BindingIdentifier!); + // 2. Return ? ResolveBinding(bindingId). + return ResolveBinding(bindingId, undefined, strict); +} diff --git a/src/runtime-semantics/LabelledStatement.mts b/src/runtime-semantics/LabelledStatement.mts new file mode 100644 index 0000000..727e4b5 --- /dev/null +++ b/src/runtime-semantics/LabelledStatement.mts @@ -0,0 +1,11 @@ +import { JSStringSet } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { LabelledEvaluation } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-labelled-statements-runtime-semantics-evaluation */ +export function Evaluate_LabelledStatement(LabelledStatement: ParseNode.LabelledStatement) { + // 1. Let newLabelSet be a new empty List. + const newLabelSet = new JSStringSet(); + // 2. Return LabelledEvaluation of this LabelledStatement with argument newLabelSet. + return LabelledEvaluation(LabelledStatement, newLabelSet); +} diff --git a/src/runtime-semantics/LexicalDeclaration.mts b/src/runtime-semantics/LexicalDeclaration.mts new file mode 100644 index 0000000..3dd1e23 --- /dev/null +++ b/src/runtime-semantics/LexicalDeclaration.mts @@ -0,0 +1,92 @@ +import { Evaluate, type PlainEvaluator } from '../evaluator.mts'; +import { + Q, X, +} from '../completion.mts'; +import { Value } from '../value.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { IsAnonymousFunctionDefinition, StringValue, type FunctionDeclaration } from '../static-semantics/all.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { NamedEvaluation, BindingInitialization } from './all.mts'; +import { + GetValue, + InitializeReferencedBinding, + ResolveBinding, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-let-and-const-declarations-runtime-semantics-evaluation */ +// LexicalBinding : +// BindingIdentifier +// BindingIdentifier Initializer +function* Evaluate_LexicalBinding_BindingIdentifier({ BindingIdentifier, Initializer, strict }: ParseNode.LexicalBinding): PlainEvaluator { + 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: Value; + // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + if (IsAnonymousFunctionDefinition(Initializer)) { + // a. Let value be NamedEvaluation of Initializer with argument bindingId. + value = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, bindingId)); + } else { // 4. Else, + // a. Let rhs be the result of evaluating Initializer. + const rhs = Q(yield* Evaluate(Initializer)); + // b. Let value be ? GetValue(rhs). + value = Q(yield* GetValue(rhs)); + } + // 5. Return InitializeReferencedBinding(lhs, value). + return yield* InitializeReferencedBinding(lhs, value); + } else { + // 1. Let lhs be ResolveBinding(StringValue of BindingIdentifier). + const lhs = yield* ResolveBinding(StringValue(BindingIdentifier!), undefined, strict); + // 2. Return InitializeReferencedBinding(lhs, undefined). + return yield* InitializeReferencedBinding(lhs, Value.undefined); + } +} + +/** https://tc39.es/ecma262/#sec-let-and-const-declarations-runtime-semantics-evaluation */ +// LexicalBinding : BindingPattern Initializer +function* Evaluate_LexicalBinding_BindingPattern(LexicalBinding: ParseNode.LexicalBinding) { + const { BindingPattern, Initializer } = LexicalBinding; + const rhs = Q(yield* Evaluate(Initializer!)); + const value = Q(yield* GetValue(rhs)); + const env = surroundingAgent.runningExecutionContext.LexicalEnvironment; + return yield* BindingInitialization(BindingPattern!, value, env); +} + +export function* Evaluate_LexicalBinding(LexicalBinding: ParseNode.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); + } +} + +/** https://tc39.es/ecma262/#sec-let-and-const-declarations-runtime-semantics-evaluation */ +// BindingList : BindingList `,` LexicalBinding +// +// (implicit) +// BindingList : LexicalBinding +export function* Evaluate_BindingList(BindingList: ParseNode.BindingList) { + // 1. Let next be the result of evaluating BindingList. + // 3. Return the result of evaluating LexicalBinding. + let next; + for (const LexicalBinding of BindingList) { + next = yield* Evaluate_LexicalBinding(LexicalBinding); + Q(next); + } + return next; +} + +/** https://tc39.es/ecma262/#sec-let-and-const-declarations-runtime-semantics-evaluation */ +// LexicalDeclaration : LetOrConst BindingList `;` +export function* Evaluate_LexicalDeclaration({ BindingList }: ParseNode.LexicalDeclaration): PlainEvaluator { + // 1. Let next be the result of evaluating BindingList. + Q(yield* Evaluate_BindingList(BindingList)); + // 3. Return NormalCompletion(empty). + return undefined; +} diff --git a/src/runtime-semantics/Literal.mts b/src/runtime-semantics/Literal.mts new file mode 100644 index 0000000..2a8c294 --- /dev/null +++ b/src/runtime-semantics/Literal.mts @@ -0,0 +1,36 @@ +import { Value } from '../value.mts'; +import { StringValue, NumericValue } from '../static-semantics/all.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { NormalCompletion } from '../completion.mts'; + +/** https://tc39.es/ecma262/#sec-literals-runtime-semantics-evaluation */ +// Literal : +// NullLiteral +// BooleanLiteral +// NumericLiteral +// StringLiteral +export function Evaluate_Literal(Literal: ParseNode.Literal): NormalCompletion { + switch (Literal.type) { + case 'NullLiteral': + // 1. Return null. + return NormalCompletion(Value.null); + case 'BooleanLiteral': + // 1. If BooleanLiteral is the token false, return false. + if (Literal.value === false) { + return NormalCompletion(Value.false); + } + // 2. If BooleanLiteral is the token true, return true. + if (Literal.value === true) { + return NormalCompletion(Value.true); + } + throw new OutOfRange('Evaluate_Literal', Literal); + case 'NumericLiteral': + // 1. Return the NumericValue of NumericLiteral as defined in 11.8.3. + return NormalCompletion(NumericValue(Literal)); + case 'StringLiteral': + return NormalCompletion(StringValue(Literal)); + default: + throw new OutOfRange('Evaluate_Literal', Literal); + } +} diff --git a/src/runtime-semantics/LogicalANDExpression.mts b/src/runtime-semantics/LogicalANDExpression.mts new file mode 100644 index 0000000..f268191 --- /dev/null +++ b/src/runtime-semantics/LogicalANDExpression.mts @@ -0,0 +1,25 @@ +import { Value } from '../value.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q, X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue, ToBoolean } from '#self'; + +/** https://tc39.es/ecma262/#sec-binary-logical-operators-runtime-semantics-evaluation */ +// LogicalANDExpression : +// LogicalANDExpression `&&` BitwiseORExpression +export function* Evaluate_LogicalANDExpression({ LogicalANDExpression, BitwiseORExpression }: ParseNode.LogicalANDExpression): ValueEvaluator { + // 1. Let lref be the result of evaluating LogicalANDExpression. + const lref = Q(yield* Evaluate(LogicalANDExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* 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 = Q(yield* Evaluate(BitwiseORExpression)); + // 6. Return ? GetValue(rref). + return Q(yield* GetValue(rref)); +} diff --git a/src/runtime-semantics/LogicalORExpression.mts b/src/runtime-semantics/LogicalORExpression.mts new file mode 100644 index 0000000..c001318 --- /dev/null +++ b/src/runtime-semantics/LogicalORExpression.mts @@ -0,0 +1,25 @@ +import { Value } from '../value.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q, X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue, ToBoolean } from '#self'; + +/** https://tc39.es/ecma262/#sec-binary-logical-operators-runtime-semantics-evaluation */ +// LogicalORExpression : +// LogicalORExpression `||` LogicalANDExpression +export function* Evaluate_LogicalORExpression({ LogicalORExpression, LogicalANDExpression }: ParseNode.LogicalORExpression): ValueEvaluator { + // 1. Let lref be the result of evaluating LogicalORExpression. + const lref = Q(yield* Evaluate(LogicalORExpression)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* 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 = Q(yield* Evaluate(LogicalANDExpression)); + // 6. Return ? GetValue(rref). + return Q(yield* GetValue(rref)); +} diff --git a/src/runtime-semantics/MV.mts b/src/runtime-semantics/MV.mts new file mode 100644 index 0000000..9178520 --- /dev/null +++ b/src/runtime-semantics/MV.mts @@ -0,0 +1,10 @@ +import { F } from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-mv-s */ +// StringNumericLiteral ::: +// [empty] +// StrWhiteSpace +// StrWhiteSpace_opt StrNumericLiteral StrWhiteSpace_opt +export function MV_StringNumericLiteral(StringNumericLiteral: string) { + return F(Number(StringNumericLiteral)); +} diff --git a/src/runtime-semantics/MemberExpression.mts b/src/runtime-semantics/MemberExpression.mts new file mode 100644 index 0000000..60a659b --- /dev/null +++ b/src/runtime-semantics/MemberExpression.mts @@ -0,0 +1,71 @@ +import { Evaluate } from '../evaluator.mts'; +import { Q, X } from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + EvaluatePropertyAccessWithExpressionKey, + EvaluatePropertyAccessWithIdentifierKey, +} from './all.mts'; +import { GetValue, MakePrivateReference } from '#self'; +import type { PlainEvaluator, ReferenceRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-property-accessors-runtime-semantics-evaluation */ +// MemberExpression : MemberExpression `[` Expression `]` +// CallExpression : CallExpression `[` Expression `]` +function* Evaluate_MemberExpression_Expression({ strict, MemberExpression, Expression }: ParseNode.MemberExpression): PlainEvaluator { + // 1. Let baseReference be the result of evaluating |MemberExpression|. + const baseReference = Q(yield* Evaluate(MemberExpression)); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(yield* 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)); +} + +/** https://tc39.es/ecma262/#sec-property-accessors-runtime-semantics-evaluation */ +// MemberExpression : MemberExpression `.` IdentifierName +// CallExpression : CallExpression `.` IdentifierName +function* Evaluate_MemberExpression_IdentifierName({ strict, MemberExpression, IdentifierName }: ParseNode.MemberExpression): PlainEvaluator { + // 1. Let baseReference be the result of evaluating |MemberExpression|. + const baseReference = Q(yield* Evaluate(MemberExpression)); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(yield* 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 X(EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName!, strict)); +} + +/** https://tc39.es/ecma262/#sec-property-accessors-runtime-semantics-evaluation */ +// MemberExpression : MemberExpression `.` PrivateIdentifier +// CallExpression : CallExpression `.` PrivateIdentifier +function* Evaluate_MemberExpression_PrivateIdentifier({ MemberExpression, PrivateIdentifier }: ParseNode.MemberExpression): PlainEvaluator { + // 1. Let baseReference be the result of evaluating MemberExpression. + const baseReference = Q(yield* Evaluate(MemberExpression)); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(yield* GetValue(baseReference)); + // 3. Let fieldNameString be the StringValue of PrivateIdentifier. + const fieldNameString = StringValue(PrivateIdentifier!); + // 4. Return ! MakePrivateReference(bv, fieldNameString). + return X(MakePrivateReference(baseValue, fieldNameString)); +} + +/** https://tc39.es/ecma262/#sec-property-accessors-runtime-semantics-evaluation */ +// MemberExpression : +// MemberExpression `[` Expression `]` +// MemberExpression `.` IdentifierName +// CallExpression : +// CallExpression `[` Expression `]` +// CallExpression `.` IdentifierName +export function Evaluate_MemberExpression(MemberExpression: ParseNode.MemberExpression) { + switch (true) { + case !!MemberExpression.Expression: + return Evaluate_MemberExpression_Expression(MemberExpression); + case !!MemberExpression.IdentifierName: + return Evaluate_MemberExpression_IdentifierName(MemberExpression); + case !!MemberExpression.PrivateIdentifier: + return Evaluate_MemberExpression_PrivateIdentifier(MemberExpression); + default: + throw new OutOfRange('Evaluate_MemberExpression', MemberExpression); + } +} diff --git a/src/runtime-semantics/MethodDefinitionEvaluation.mts b/src/runtime-semantics/MethodDefinitionEvaluation.mts new file mode 100644 index 0000000..01b5304 --- /dev/null +++ b/src/runtime-semantics/MethodDefinitionEvaluation.mts @@ -0,0 +1,356 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, Descriptor, PrivateName, UndefinedValue, type PropertyKeyValue, ObjectValue, BooleanValue, +} from '../value.mts'; +import { + Q, X, +} from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { ClassElementDefinitionRecord, DefineMethod, Evaluate_PropertyName } from './all.mts'; +import { + OrdinaryObjectCreate, + OrdinaryFunctionCreate, + DefinePropertyOrThrow, + SetFunctionName, + MakeMethod, + sourceTextMatchedBy, + type FunctionObject, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-privateelement-specification-type */ +export interface PrivateElementRecord_Value { + readonly Key: PrivateName; + readonly Kind: 'method' | 'field'; + Value?: Value; + readonly Get?: undefined; + readonly Set?: undefined; +} +export interface PrivateElementRecord_Accessor { + readonly Key: PrivateName; + readonly Kind: 'accessor'; + Value?: Value; + readonly Get?: FunctionObject | UndefinedValue; + readonly Set?: FunctionObject | UndefinedValue; +} +export type PrivateElementRecord = PrivateElementRecord_Value | PrivateElementRecord_Accessor; +export const PrivateElementRecord = function PrivateElementRecord(value: PrivateElementRecord) { + Object.setPrototypeOf(value, PrivateElementRecord.prototype); + return value; +} as { + (value: PrivateElementRecord): PrivateElementRecord; + [Symbol.hasInstance](instance: unknown): instance is PrivateElementRecord; +}; + +// -decorator +// +decorator: remove this function +/** https://tc39.es/ecma262/#sec-definemethodproperty */ +function* DefineMethodProperty(key: PropertyKeyValue | PrivateName, homeObject: ObjectValue, closure: FunctionObject, enumerable: BooleanValue): PlainEvaluator { + // 1. If key is a Private Name, then + if (key instanceof PrivateName) { + // a. Return PrivateElement { [[Key]]: key, [[Kind]]: method, [[Value]]: closure }. + return PrivateElementRecord({ + Key: key, + Kind: 'method', + Value: closure, + }); + } else { // 2. Else, + // a. 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, + }); + // b. Perform ? DefinePropertyOrThrow(homeObject, key, desc). + Q(yield* DefinePropertyOrThrow(homeObject, key, desc)); + // c. Return empty. + return undefined; + } +} + +// MethodDefinition : +// ClassElementName `(` UniqueFormalParameters `)` `{` FunctionBody `}` +// `get` ClassElementName `(` `)` `{` FunctionBody `}` +// `set` ClassElementName `(` PropertySetParameterList `)` `{` FunctionBody `}` +// -decorator signature +function MethodDefinitionEvaluation_MethodDefinition(MethodDefinition: ParseNode.MethodDefinition, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator +// +decorator signature +function MethodDefinitionEvaluation_MethodDefinition(MethodDefinition: ParseNode.MethodDefinition, object: ObjectValue): PlainEvaluator +function* MethodDefinitionEvaluation_MethodDefinition(MethodDefinition: ParseNode.MethodDefinition, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator { + 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]]). + X(SetFunctionName(methodDef.Closure, methodDef.Key)); + // 3. Return ? DefineMethodProperty(methodDef.[[Key]], object, methodDef.[[Closure]], enumerable). + if (enumerable) { + return Q(yield* DefineMethodProperty(methodDef.Key, object, methodDef.Closure, enumerable)); + } else { + return ClassElementDefinitionRecord({ + Key: methodDef.Key, + Kind: 'method', + Value: methodDef.Closure, + Decorators: undefined, + }); + } + } + case !!MethodDefinition.PropertySetParameterList: { + const { ClassElementName, PropertySetParameterList, FunctionBody } = MethodDefinition; + // 1. Let propKey be the result of evaluating ClassElementName. + const propKey = Q(yield* Evaluate_PropertyName(ClassElementName)); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 5. Let sourceText be the source text matched by MethodDefinition. + const sourceText = sourceTextMatchedBy(MethodDefinition); + // 6. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, PropertySetParameterList, FunctionBody, non-lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, PropertySetParameterList, FunctionBody, 'non-lexical-this', scope, privateScope); + // 7. Perform MakeMethod(closure, object). + MakeMethod(closure, object); + // 8. Perform SetFunctionName(closure, propKey, "get"). + SetFunctionName(closure, propKey, Value('set')); + if (enumerable) { + // 9. If propKey is a Private Name, then + if (propKey instanceof PrivateName) { + // a. Return PrivateElement { [[Key]]: propKey, [[Kind]]: accessor, [[Get]]: undefined, [[Set]]: closure }. + return PrivateElementRecord({ + Key: propKey, + Kind: 'accessor', + Get: Value.undefined, + Set: closure, + }); + } else { // 10. Else, + // a. Let desc be the PropertyDescriptor { [[Get]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Set: closure, + Enumerable: enumerable, + Configurable: Value.true, + }); + // b. Perform ? DefinePropertyOrThrow(object, propKey, desc). + Q(yield* DefinePropertyOrThrow(object, propKey, desc)); + // c. Return empty. + return undefined; + } + } else { + return ClassElementDefinitionRecord({ + Key: propKey, + Kind: 'setter', + Set: closure, + Decorators: undefined, + }); + } + } + case !MethodDefinition.UniqueFormalParameters && !MethodDefinition.PropertySetParameterList: { + const { ClassElementName, FunctionBody } = MethodDefinition; + // 1. Let propKey be the result of evaluating ClassElementName. + const propKey = Q(yield* Evaluate_PropertyName(ClassElementName)); + // 3. Let scope be the running execution context's LexicalEnvironment. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 5. Let formalParameterList be an instance of the production FormalParameters : [empty]. + const formalParameterList: ParseNode.FormalParameters = []; + // 6. Let sourceText be the source text matched by MethodDefinition. + const sourceText = sourceTextMatchedBy(MethodDefinition); + // 7. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameterList, FunctionBody, non-lexical-this, scope, privateScope). + const closure = OrdinaryFunctionCreate(surroundingAgent.intrinsic('%Function.prototype%'), sourceText, formalParameterList, FunctionBody, 'non-lexical-this', scope, privateScope); + // 8. Perform MakeMethod(closure, object). + MakeMethod(closure, object); + // 9. Perform SetFunctionName(closure, propKey, "get"). + SetFunctionName(closure, propKey, Value('get')); + if (enumerable) { + // 10. If propKey is a Private Name, then + if (propKey instanceof PrivateName) { + return PrivateElementRecord({ + Key: propKey, + Kind: 'accessor', + Get: closure, + Set: Value.undefined, + }); + } else { // 11. Else, + // a. Let desc be the PropertyDescriptor { [[Get]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true }. + const desc = Descriptor({ + Get: closure, + Enumerable: enumerable, + Configurable: Value.true, + }); + // b. Perform ? DefinePropertyOrThrow(object, propKey, desc). + Q(yield* DefinePropertyOrThrow(object, propKey, desc)); + // c. Return empty. + return undefined; + } + } else { + return ClassElementDefinitionRecord({ + Key: propKey, + Kind: 'getter', + Get: closure, + Decorators: undefined, + }); + } + } + default: + throw new OutOfRange('MethodDefinitionEvaluation_MethodDefinition', MethodDefinition); + } +} + +/** https://tc39.es/ecma262/#sec-async-function-definitions-MethodDefinitionEvaluation */ +// AsyncMethod : +// `async` ClassElementName `(` UniqueFormalParameters `)` `{` AsyncBody `}` +// -decorator signature +function MethodDefinitionEvaluation_AsyncMethod(AsyncMethod: ParseNode.AsyncMethod, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator +// +decorator signature +function MethodDefinitionEvaluation_AsyncMethod(AsyncMethod: ParseNode.AsyncMethod, object: ObjectValue): PlainEvaluator +function* MethodDefinitionEvaluation_AsyncMethod(AsyncMethod: ParseNode.AsyncMethod, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator { + const { ClassElementName, UniqueFormalParameters, AsyncBody } = AsyncMethod; + // 1. Let propKey be the result of evaluating ClassElementName. + const propKey = Q(yield* Evaluate_PropertyName(ClassElementName)); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 5. Let sourceText be the source text matched by AsyncMethod. + const sourceText = sourceTextMatchedBy(AsyncMethod); + // 6. Let closure be ! OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, UniqueFormalParameters, AsyncBody, non-lexical-this, scope, privateScope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncFunction.prototype%'), sourceText, UniqueFormalParameters, AsyncBody, 'non-lexical-this', scope, privateScope)); + // 7. Perform ! MakeMethod(closure, object). + X(MakeMethod(closure, object)); + // 8. Perform ! SetFunctionName(closure, propKey). + X(SetFunctionName(closure, propKey)); + if (enumerable) { + // 9. Return ? DefineMethodProperty(propKey, object, closure, enumerable). + return Q(yield* DefineMethodProperty(propKey, object, closure, enumerable)); + } else { + return ClassElementDefinitionRecord({ + Key: propKey, + Kind: 'method', + Value: closure, + Decorators: undefined, + }); + } +} + +/** https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-propertydefinitionevaluation */ +// GeneratorMethod : +// `*` ClassElementName `(` UniqueFormalParameters `)` `{` GeneratorBody `}` +function MethodDefinitionEvaluation_GeneratorMethod(GeneratorMethod: ParseNode.GeneratorMethod, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator +function MethodDefinitionEvaluation_GeneratorMethod(GeneratorMethod: ParseNode.GeneratorMethod, object: ObjectValue): PlainEvaluator +function* MethodDefinitionEvaluation_GeneratorMethod(GeneratorMethod: ParseNode.GeneratorMethod, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator { + const { ClassElementName, UniqueFormalParameters, GeneratorBody } = GeneratorMethod; + // 1. Let propKey be the result of evaluating ClassElementName. + let propKey = yield* Evaluate_PropertyName(ClassElementName); + propKey = Q(propKey); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let privateScope be the running execution context's PrivateEnvironment. + const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 5. Let sourceText be the source text matched by GeneratorMethod. + const sourceText = sourceTextMatchedBy(GeneratorMethod); + // 6. Let closure be ! OrdinaryFunctionCreate(%GeneratorFunction.prototype%, sourceText, UniqueFormalParameters, AsyncBody, non-lexical-this, scope, privateScope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype%'), sourceText, UniqueFormalParameters, GeneratorBody, 'non-lexical-this', scope, privateScope)); + // 7. Perform ! MakeMethod(closure, object). + X(MakeMethod(closure, object)); + // 8. Perform ! SetFunctionName(closure, propKey). + X(SetFunctionName(closure, propKey)); + // 9. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%GeneratorFunction.prototype.prototype%')); + // 10. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(closure, Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + if (enumerable) { + // 11. Return ? DefineMethodProperty(propKey, object, closure, enumerable). + return Q(yield* DefineMethodProperty(propKey, object, closure, enumerable)); + } else { + return ClassElementDefinitionRecord({ + Key: propKey, + Kind: 'method', + Value: closure, + Decorators: undefined, + }); + } +} + +/** https://tc39.es/ecma262/#sec-asyncgenerator-definitions-propertydefinitionevaluation */ +// AsyncGeneratorMethod : +// `async` `*` PropertyName `(` UniqueFormalParameters `)` `{` AsyncGeneratorBody `}` +function MethodDefinitionEvaluation_AsyncGeneratorMethod(AsyncGeneratorMethod: ParseNode.AsyncGeneratorMethod, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator +function MethodDefinitionEvaluation_AsyncGeneratorMethod(AsyncGeneratorMethod: ParseNode.AsyncGeneratorMethod, object: ObjectValue): PlainEvaluator +function* MethodDefinitionEvaluation_AsyncGeneratorMethod(AsyncGeneratorMethod: ParseNode.AsyncGeneratorMethod, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator { + const { ClassElementName, UniqueFormalParameters, AsyncGeneratorBody } = AsyncGeneratorMethod; + // 1. Let propKey be the result of evaluating ClassElementName. + let propKey = yield* Evaluate_PropertyName(ClassElementName); + propKey = Q(propKey); + // 3. Let scope be the LexicalEnvironment of the running execution context. + const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let privateScope be the running execution context's PrivateEnvironment. + const privateEnv = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 5. Let sourceText be the source text matched by AsyncGeneratorMethod. + const sourceText = sourceTextMatchedBy(AsyncGeneratorMethod); + // 6. Let closure be ! OrdinaryFunctionCreate(%AsyncGeneratorFunction.prototype%, sourceText, UniqueFormalParameters, AsyncGeneratorBody, non-lexical-this, scope, privateScope). + const closure = X(OrdinaryFunctionCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype%'), sourceText, UniqueFormalParameters, AsyncGeneratorBody, 'non-lexical-this', scope, privateEnv)); + // 7. Perform ! MakeMethod(closure, object). + X(MakeMethod(closure, object)); + // 9. Perform ! SetFunctionName(closure, propKey). + X(SetFunctionName(closure, propKey)); + // 9. Let prototype be OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%). + const prototype = OrdinaryObjectCreate(surroundingAgent.intrinsic('%AsyncGeneratorFunction.prototype.prototype%')); + // 10. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + X(DefinePropertyOrThrow(closure, Value('prototype'), Descriptor({ + Value: prototype, + Writable: Value.true, + Enumerable: Value.false, + Configurable: Value.false, + }))); + if (enumerable) { + // 11. Return ? DefineMethodProperty(propKey, object, closure, enumerable). + return Q(yield* DefineMethodProperty(propKey, object, closure, enumerable)); + } else { + return ClassElementDefinitionRecord({ + Key: propKey, + Kind: 'method', + Value: closure, + Decorators: undefined, + }); + } +} + +// -decorator +export function MethodDefinitionEvaluation(node: ParseNode.MethodDefinitionLike, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator +// +decorator +export function MethodDefinitionEvaluation(node: ParseNode.MethodDefinitionLike, object: ObjectValue): PlainEvaluator +export function MethodDefinitionEvaluation(node: ParseNode.MethodDefinitionLike, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator { + if (enumerable) { + switch (node.type) { + case 'MethodDefinition': + return MethodDefinitionEvaluation_MethodDefinition(node, object, enumerable); + case 'AsyncMethod': + return MethodDefinitionEvaluation_AsyncMethod(node, object, enumerable); + case 'GeneratorMethod': + return MethodDefinitionEvaluation_GeneratorMethod(node, object, enumerable); + case 'AsyncGeneratorMethod': + return MethodDefinitionEvaluation_AsyncGeneratorMethod(node, object, enumerable); + default: + throw new OutOfRange('MethodDefinitionEvaluation', node); + } + } else { + switch (node.type) { + case 'MethodDefinition': + return MethodDefinitionEvaluation_MethodDefinition(node, object); + case 'AsyncMethod': + return MethodDefinitionEvaluation_AsyncMethod(node, object); + case 'GeneratorMethod': + return MethodDefinitionEvaluation_GeneratorMethod(node, object); + case 'AsyncGeneratorMethod': + return MethodDefinitionEvaluation_AsyncGeneratorMethod(node, object); + default: + throw new OutOfRange('MethodDefinitionEvaluation', node); + } + } +} diff --git a/src/runtime-semantics/Module.mts b/src/runtime-semantics/Module.mts new file mode 100644 index 0000000..c3a8f84 --- /dev/null +++ b/src/runtime-semantics/Module.mts @@ -0,0 +1,15 @@ +import { Value } from '../value.mts'; +import { NormalCompletion } from '../completion.mts'; +import { Evaluate } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-module-semantics-runtime-semantics-evaluation */ +// Module : +// [empty] +// ModuleBody +export function* Evaluate_Module({ ModuleBody }: ParseNode.Module) { + if (!ModuleBody) { + return NormalCompletion(Value.undefined); + } + return yield* Evaluate(ModuleBody); +} diff --git a/src/runtime-semantics/ModuleBody.mts b/src/runtime-semantics/ModuleBody.mts new file mode 100644 index 0000000..690e5c2 --- /dev/null +++ b/src/runtime-semantics/ModuleBody.mts @@ -0,0 +1,10 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Evaluate_StatementList } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-module-semantics-runtime-semantics-evaluation */ +// ModuleBody : ModuleItemList +export function Evaluate_ModuleBody({ ModuleItemList }: ParseNode.ModuleBody) { + // TODO(ts): ModuleItemList might contain ImportDeclaration or ExportDeclaration which is not accepted by Evaluate_StatementList. + // @ts-expect-error + return Evaluate_StatementList(ModuleItemList); +} diff --git a/src/runtime-semantics/MultiplicativeExpression.mts b/src/runtime-semantics/MultiplicativeExpression.mts new file mode 100644 index 0000000..21a6526 --- /dev/null +++ b/src/runtime-semantics/MultiplicativeExpression.mts @@ -0,0 +1,18 @@ +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-multiplicative-operators-runtime-semantics-evaluation */ +// MultiplicativeExpression : +// MultiplicativeExpression MultiplicativeOperator ExponentiationExpression +export function* Evaluate_MultiplicativeExpression({ + MultiplicativeExpression, + MultiplicativeOperator, + ExponentiationExpression, +}: ParseNode.MultiplicativeExpression): ValueEvaluator { + // 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/src/runtime-semantics/NamedEvaluation.mts b/src/runtime-semantics/NamedEvaluation.mts new file mode 100644 index 0000000..7d156e0 --- /dev/null +++ b/src/runtime-semantics/NamedEvaluation.mts @@ -0,0 +1,97 @@ +import { Value } from '../value.mts'; +import { Q } from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { + ClassDefinitionEvaluation, + InstantiateOrdinaryFunctionExpression, + InstantiateAsyncFunctionExpression, + InstantiateGeneratorFunctionExpression, + InstantiateAsyncGeneratorFunctionExpression, + InstantiateArrowFunctionExpression, + InstantiateAsyncArrowFunctionExpression, + DecoratorListEvaluation, +} from './all.mts'; +import type { + FunctionDeclaration, FunctionObject, PrivateName, PropertyKeyValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-namedevaluation */ +// FunctionExpression : +// `function` `(` FormalParameters `)` `{` FunctionBody `}` +function NamedEvaluation_FunctionExpression(FunctionExpression: ParseNode.FunctionExpression, name: PropertyKeyValue | PrivateName) { + return InstantiateOrdinaryFunctionExpression(FunctionExpression, name); +} + + +/** https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-namedevaluation */ +// GeneratorExpression : +// `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` +function NamedEvaluation_GeneratorExpression(GeneratorExpression: ParseNode.GeneratorExpression, name: PropertyKeyValue | PrivateName) { + return InstantiateGeneratorFunctionExpression(GeneratorExpression, name); +} + +/** https://tc39.es/ecma262/#sec-async-function-definitions-runtime-semantics-namedevaluation */ +// AsyncFunctionExpression : +// `async` `function` `(` FormalParameters `)` `{` AsyncBody `}` +function NamedEvaluation_AsyncFunctionExpression(AsyncFunctionExpression: ParseNode.AsyncFunctionExpression, name: PropertyKeyValue | PrivateName) { + return InstantiateAsyncFunctionExpression(AsyncFunctionExpression, name); +} + +/** https://tc39.es/ecma262/#sec-asyncgenerator-definitions-namedevaluation */ +// AsyncGeneratorExpression : +// `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` +function NamedEvaluation_AsyncGeneratorExpression(AsyncGeneratorExpression: ParseNode.AsyncGeneratorExpression, name: PropertyKeyValue | PrivateName) { + return InstantiateAsyncGeneratorFunctionExpression(AsyncGeneratorExpression, name); +} + +/** https://tc39.es/ecma262/#sec-arrow-function-definitions-runtime-semantics-namedevaluation */ +// ArrowFunction : +// ArrowParameters `=>` ConciseBody +function NamedEvaluation_ArrowFunction(ArrowFunction: ParseNode.ArrowFunction, name: PropertyKeyValue | PrivateName) { + return InstantiateArrowFunctionExpression(ArrowFunction, name); +} + +/** https://tc39.es/ecma262/#sec-arrow-function-definitions-runtime-semantics-namedevaluation */ +// AsyncArrowFunction : +// ArrowParameters `=>` AsyncConciseBody +function NamedEvaluation_AsyncArrowFunction(AsyncArrowFunction: ParseNode.AsyncArrowFunction, name: PropertyKeyValue | PrivateName) { + return InstantiateAsyncArrowFunctionExpression(AsyncArrowFunction, name); +} + +/** https://tc39.es/ecma262/#sec-class-definitions-runtime-semantics-namedevaluation */ +// ClassExpression : `class` ClassTail +function* NamedEvaluation_ClassExpression(ClassExpression: ParseNode.ClassExpression, name: PropertyKeyValue | PrivateName) { + const { ClassTail, Decorators } = ClassExpression; + const decorators = Decorators ? Q(yield* DecoratorListEvaluation(Decorators)) : []; + const sourceText = ClassExpression.sourceText; + // 1. Let value be the result of ClassDefinitionEvaluation of ClassTail with arguments undefined and name. + const value = yield* ClassDefinitionEvaluation(ClassTail, Value.undefined, name, sourceText, decorators); + Q(value); + // 4. Return value. + return value; +} + +export function* NamedEvaluation(F: FunctionDeclaration, name: PropertyKeyValue | PrivateName): ValueEvaluator { + 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/src/runtime-semantics/NewExpression.mts b/src/runtime-semantics/NewExpression.mts new file mode 100644 index 0000000..b1bd593 --- /dev/null +++ b/src/runtime-semantics/NewExpression.mts @@ -0,0 +1,50 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ArgumentListEvaluation } from './all.mts'; +import { + Assert, + Construct, + GetValue, + IsConstructor, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-evaluatenew */ +function* EvaluateNew(constructExpr: ParseNode.LeftHandSideExpression, args: undefined | ParseNode.Arguments) { + // 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 = Q(yield* Evaluate(constructExpr)); + // 4. Let constructor be ? GetValue(ref). + const constructor = Q(yield* 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)) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', constructor); + } + // 8. Return ? Construct(constructor, argList). + return Q(yield* Construct(constructor, argList)); +} + +/** https://tc39.es/ecma262/#sec-new-operator-runtime-semantics-evaluation */ +// NewExpression : +// `new` NewExpression +// `new` MemberExpression Arguments +export function* Evaluate_NewExpression({ MemberExpression, Arguments }: ParseNode.NewExpression): ValueEvaluator { + 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/src/runtime-semantics/NewTarget.mts b/src/runtime-semantics/NewTarget.mts new file mode 100644 index 0000000..faae6e2 --- /dev/null +++ b/src/runtime-semantics/NewTarget.mts @@ -0,0 +1,8 @@ +import { GetNewTarget } from '#self'; + +/** https://tc39.es/ecma262/#sec-meta-properties-runtime-semantics-evaluation */ +// NewTarget : `new` `.` `target` +export function Evaluate_NewTarget() { + // 1. Return GetNewTarget(). + return GetNewTarget(); +} diff --git a/src/runtime-semantics/NumberToBigInt.mts b/src/runtime-semantics/NumberToBigInt.mts new file mode 100644 index 0000000..3647891 --- /dev/null +++ b/src/runtime-semantics/NumberToBigInt.mts @@ -0,0 +1,17 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value, NumberValue } from '../value.mts'; +import { + Assert, IsIntegralNumber, Z, R, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-numbertobigint */ +export function NumberToBigInt(number: NumberValue) { + // 1. Assert: Type(number) is Number. + Assert(number instanceof NumberValue); + // 2. If IsIntegralNumber(number) is false, throw a RangeError exception. + if (IsIntegralNumber(number) === Value.false) { + return surroundingAgent.Throw('RangeError', 'CannotConvertDecimalToBigInt', number); + } + // 3. Return the BigInt value that represents the mathematical value of number. + return Z(BigInt(R(number))); +} diff --git a/src/runtime-semantics/ObjectLiteral.mts b/src/runtime-semantics/ObjectLiteral.mts new file mode 100644 index 0000000..893e42a --- /dev/null +++ b/src/runtime-semantics/ObjectLiteral.mts @@ -0,0 +1,26 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { + PropertyDefinitionEvaluation_PropertyDefinitionList, +} from './all.mts'; +import { OrdinaryObjectCreate } from '#self'; + +/** https://tc39.es/ecma262/#sec-object-initializer-runtime-semantics-evaluation */ +// ObjectLiteral : +// `{` `}` +// `{` PropertyDefinitionList `}` +// `{` PropertyDefinitionList `,` `}` +export function* Evaluate_ObjectLiteral({ PropertyDefinitionList }: ParseNode.ObjectLiteral): ValueEvaluator { + // 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/src/runtime-semantics/OptionalExpression.mts b/src/runtime-semantics/OptionalExpression.mts new file mode 100644 index 0000000..6ba1096 --- /dev/null +++ b/src/runtime-semantics/OptionalExpression.mts @@ -0,0 +1,128 @@ +import { ReferenceRecord, Value } from '../value.mts'; +import { Evaluate, type ExpressionEvaluator } from '../evaluator.mts'; +import { Q, X } from '../completion.mts'; +import { IsInTailPosition, StringValue } from '../static-semantics/all.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + EvaluateCall, + EvaluatePropertyAccessWithExpressionKey, + EvaluatePropertyAccessWithIdentifierKey, +} from './all.mts'; +import { GetValue, MakePrivateReference } from '#self'; + +/** https://tc39.es/ecma262/#sec-optional-chaining-evaluation */ +// OptionalExpression : +// MemberExpression OptionalChain +// CallExpression OptionalChain +// OptionalExpression OptionalChain +export function* Evaluate_OptionalExpression({ MemberExpression, OptionalChain }: ParseNode.OptionalExpression) { + // 1. Let baseReference be the result of evaluating MemberExpression. + const baseReference = Q(yield* Evaluate(MemberExpression)); + // 2. Let baseValue be ? GetValue(baseReference). + const baseValue = Q(yield* 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, X(baseReference)); +} + +/** https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation */ +// OptionalChain : +// `?.` Arguments +// `?.` `[` Expression `]` +// `?.` IdentifierName +// `?.` PrivateIdentifier +// OptionalChain Arguments +// OptionalChain `[` Expression `]` +// OptionalChain `.` IdentifierName +// OptionalChain `.` PrivateIdentifier +function* ChainEvaluation(node: ParseNode.OptionalChain, baseValue: Value, baseReference: Value | ReferenceRecord): ExpressionEvaluator { + const { + OptionalChain, + Arguments, + Expression, + IdentifierName, + PrivateIdentifier, + } = 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(yield* 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(yield* 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(yield* 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 X(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 X(EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict)); + } + if (PrivateIdentifier) { + 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(yield* GetValue(newReference)); + // 4. Let fieldNameString be the StringValue of PrivateIdentifier. + const fieldNameString = StringValue(PrivateIdentifier); + // 5. Return ! MakePrivateReference(nv, fieldNameString). + return X(MakePrivateReference(newValue, fieldNameString)); + } + // 1. Let fieldNameString be the StringValue of PrivateIdentifier. + const fieldNameString = StringValue(PrivateIdentifier); + // 2. Return ! MakePrivateReference(bv, fieldNameString). + return X(MakePrivateReference(baseValue, fieldNameString)); + } + throw new OutOfRange('ChainEvaluation', node); +} diff --git a/src/runtime-semantics/ParenthesizedExpression.mts b/src/runtime-semantics/ParenthesizedExpression.mts new file mode 100644 index 0000000..884e066 --- /dev/null +++ b/src/runtime-semantics/ParenthesizedExpression.mts @@ -0,0 +1,8 @@ +import { Evaluate } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-grouping-operator-runtime-semantics-evaluation */ +export function* Evaluate_ParenthesizedExpression({ Expression }: ParseNode.ParenthesizedExpression) { + // 1. Return the result of evaluating Expression. This may be of type Reference. + return yield* Evaluate(Expression); +} diff --git a/src/runtime-semantics/PropertyBindingInitialization.mts b/src/runtime-semantics/PropertyBindingInitialization.mts new file mode 100644 index 0000000..96d6a06 --- /dev/null +++ b/src/runtime-semantics/PropertyBindingInitialization.mts @@ -0,0 +1,45 @@ +import { BoundNames } from '../static-semantics/all.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { isArray } from '../helpers.mts'; +import type { PlainEvaluator } from '../evaluator.mts'; +import { Evaluate_PropertyName, KeyedBindingInitialization } from './all.mts'; +import type { + EnvironmentRecord, PlainCompletion, PropertyKeyValue, UndefinedValue, Value, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-propertybindinginitialization */ +// BindingPropertyList : BIndingPropertyList `,` BindingProperty +// BindingProperty : +// SingleNameBinding +// PropertyName `:` BindingElement +export function* PropertyBindingInitialization(node: ParseNode.BindingPropertyList | ParseNode.BindingPropertyLike, value: Value, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator { + if (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: PlainCompletion = []; + for (const item of node) { + const nextNames = Q(yield* PropertyBindingInitialization(item, value, environment)); + boundNames.push(...nextNames); + } + return boundNames; + } + if ('PropertyName' in node && node.PropertyName) { + // 1. Let P be the result of evaluating PropertyName. + const P = yield* Evaluate_PropertyName(node.PropertyName); + Q(P); + // 3. Perform ? KeyedBindingInitialization of BindingElement with value, environment, and P as the arguments. + Q(yield* KeyedBindingInitialization(node.BindingElement, value, environment, P as PropertyKeyValue)); + // 4. Return a new List containing P. + return [P as PropertyKeyValue]; + } 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 as ParseNode.SingleNameBinding, value, environment, name)); + // 3. Return a new List containing name. + return [name]; + } +} diff --git a/src/runtime-semantics/PropertyDefinitionEvaluation.mts b/src/runtime-semantics/PropertyDefinitionEvaluation.mts new file mode 100644 index 0000000..0398b9f --- /dev/null +++ b/src/runtime-semantics/PropertyDefinitionEvaluation.mts @@ -0,0 +1,129 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { + Value, NullValue, ObjectValue, type PropertyKeyValue, JSStringValue, BooleanValue, +} from '../value.mts'; +import { + StringValue, + IsAnonymousFunctionDefinition, + IsComputedPropertyKey, + type FunctionDeclaration, +} from '../static-semantics/all.mts'; +import { Evaluate, type PlainEvaluator, type ValueEvaluator } from '../evaluator.mts'; +import { + Q, X, + NormalCompletion, +} from '../completion.mts'; +import { OutOfRange, kInternal } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { NamedEvaluation, MethodDefinitionEvaluation, Evaluate_PropertyName } from './all.mts'; +import { + Assert, + GetValue, + CreateDataPropertyOrThrow, + CopyDataProperties, + DefineMethodProperty, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-object-initializer-runtime-semantics-propertydefinitionevaluation */ +// PropertyDefinitionList : +// PropertyDefinitionList `,` PropertyDefinition +export function* PropertyDefinitionEvaluation_PropertyDefinitionList(PropertyDefinitionList: ParseNode.PropertyDefinitionList, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator { + for (const PropertyDefinition of PropertyDefinitionList) { + Q(yield* PropertyDefinitionEvaluation_PropertyDefinition(PropertyDefinition, object, enumerable)); + } +} + +// PropertyDefinition : +// `...` AssignmentExpression +// IdentifierReference +// PropertyName `:` AssignmentExpression +function* PropertyDefinitionEvaluation_PropertyDefinition(PropertyDefinition: ParseNode.PropertyDefinitionLike, object: ObjectValue, enumerable: BooleanValue) { + switch (PropertyDefinition.type) { + case 'IdentifierReference': + return yield* PropertyDefinitionEvaluation_PropertyDefinition_IdentifierReference(PropertyDefinition, object, enumerable); + case 'PropertyDefinition': + break; + case 'MethodDefinition': + case 'GeneratorMethod': + case 'AsyncMethod': + case 'AsyncGeneratorMethod': { + if (surroundingAgent.feature('decorators')) { + const methodDefinition = Q(yield* MethodDefinitionEvaluation(PropertyDefinition, object)); + Q(yield* DefineMethodProperty(object, methodDefinition, true)); + return undefined; + } else { + return yield* MethodDefinitionEvaluation(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 = Q(yield* Evaluate(AssignmentExpression)); + // 2. Let fromValue be ? GetValue(exprValue). + const fromValue = Q(yield* GetValue(exprValue)); + // 3. Let excludedNames be a new empty List. + const excludedNames: PropertyKeyValue[] = []; + // 4. Return ? CopyDataProperties(object, fromValue, excludedNames). + return Q(yield* CopyDataProperties(object, fromValue, excludedNames)); + } + // 1. Let propKey be the result of evaluating PropertyName. + const propKey = Q(yield* Evaluate_PropertyName(PropertyName)); + // 3. If this PropertyDefinition is contained within a Script which is being evaluated for JSON.parse, then + let isProtoSetter; + if (surroundingAgent.runningExecutionContext?.HostDefined?.[kInternal]?.json) { + isProtoSetter = false; + } else if (!IsComputedPropertyKey(PropertyName) && (propKey as JSStringValue).stringValue() === '__proto__') { // 3. Else, If _propKey_ is the String value *"__proto__"* and if IsComputedPropertyKey(|PropertyName|) is *false*, + // a. Let isProtoSetter be true. + isProtoSetter = true; + } else { // 4. Else, + // a. Let isProtoSetter be false. + isProtoSetter = false; + } + let propValue; + // 5. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and isProtoSetter is false, then + if (IsAnonymousFunctionDefinition(AssignmentExpression) && !isProtoSetter) { + // a. Let propValue be NamedEvaluation of AssignmentExpression with argument propKey. + propValue = yield* NamedEvaluation(AssignmentExpression as FunctionDeclaration, propKey); + } else { // 6. Else, + // a. Let exprValueRef be the result of evaluating AssignmentExpression. + const exprValueRef = Q(yield* Evaluate(AssignmentExpression)); + // b. Let propValue be ? GetValue(exprValueRef). + propValue = Q(yield* GetValue(exprValueRef)); + } + // 7. If isProtoSetter is true, then + if (isProtoSetter) { + // a. If Type(propValue) is either Object or Null, then + if (propValue instanceof ObjectValue || propValue instanceof NullValue) { + // i. Return object.[[SetPrototypeOf]](propValue). + return yield* object.SetPrototypeOf(propValue); + } + // b. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + // 8. Assert: enumerable is true. + Assert(enumerable === Value.true); + // 9. Assert: object is an ordinary, extensible object with no non-configurable properties. + // 10. Return ! CreateDataPropertyOrThrow(object, propKey, propValue). + return X(CreateDataPropertyOrThrow(object, propKey as PropertyKeyValue, X(propValue))); +} + +// PropertyDefinition : IdentifierReference +function* PropertyDefinitionEvaluation_PropertyDefinition_IdentifierReference(IdentifierReference: ParseNode.IdentifierReference, object: ObjectValue, enumerable: BooleanValue): ValueEvaluator { + // 1. Let propName be StringValue of IdentifierReference. + const propName = StringValue(IdentifierReference); + // 2. Let exprValue be the result of evaluating IdentifierReference. + const exprValue = Q(yield* Evaluate(IdentifierReference)); + // 3. Let propValue be ? GetValue(exprValue). + const propValue = Q(yield* 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)); +} diff --git a/src/runtime-semantics/PropertyName.mts b/src/runtime-semantics/PropertyName.mts new file mode 100644 index 0000000..1dfee56 --- /dev/null +++ b/src/runtime-semantics/PropertyName.mts @@ -0,0 +1,62 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { Evaluate } from '../evaluator.mts'; +import { StringValue, NumericValue } from '../static-semantics/all.mts'; +import { Q, X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + ToString, + GetValue, + ToPropertyKey, +} from '#self'; +import type { + PlainEvaluator, PrivateEnvironmentRecord, PrivateName, PropertyKeyValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-object-initializer-runtime-semantics-evaluation */ +// PropertyName : +// LiteralPropertyName +// ComputedPropertyName +// LiteralPropertyName : +// IdentifierName +// StringLiteral +// NumericLiteral +// ComputedPropertyName : +// `[` AssignmentExpression `]` +export function* Evaluate_PropertyName(PropertyName: ParseNode.PropertyNameLike | ParseNode.PrivateIdentifier): PlainEvaluator { + switch (PropertyName.type) { + case 'IdentifierName': + return StringValue(PropertyName); + case 'StringLiteral': + return 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)); + } + case 'PrivateIdentifier': { + // 1. Let privateIdentifier be StringValue of PrivateIdentifier. + const privateIdentifier = StringValue(PropertyName); + // 2. Let privateEnvRec be the running execution context's PrivateEnvironment. + const privateEnvRec = surroundingAgent.runningExecutionContext.PrivateEnvironment; + // 3. Let names be privateEnvRec.[[Names]]. + const names = (privateEnvRec as PrivateEnvironmentRecord).Names; + // 4. Assert: Exactly one element of names is a Private Name whose [[Description]] is privateIdentifier. + // 5. Let privateName be the Private Name in names whose [[Description]] is privateIdentifier. + const privateName = names.find((n) => n.Description.stringValue() === privateIdentifier.stringValue()); + Assert(!!privateName); + // 6. Return privateName. + return privateName; + } + default: { + // 1. Let exprValue be the result of evaluating AssignmentExpression. + const exprValue = Q(yield* Evaluate(PropertyName.ComputedPropertyName)); + // 2. Let propName be ? GetValue(exprValue). + const propName = Q(yield* GetValue(exprValue)); + // 3. Return ? ToPropertyKey(propName). + return Q(yield* ToPropertyKey(propName)); + } + } +} diff --git a/src/runtime-semantics/RegExp.mts b/src/runtime-semantics/RegExp.mts new file mode 100644 index 0000000..774e029 --- /dev/null +++ b/src/runtime-semantics/RegExp.mts @@ -0,0 +1,1343 @@ +/* eslint-disable prefer-arrow-callback */ +// use function name for better debug + +/* https://tc39.es/ecma262/#sec-pattern */ +import { CharacterValue, CodePointsToString } from '../static-semantics/all.mts'; +import { isLineTerminator, isWhitespace } from '../parser/Lexer.mts'; +import { + __ts_cast__, isArray, unreachable, type Mutable, +} from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +// @ts-ignore +import PropertyValueAliases from '../unicode/PropertyValueAliases.json' with { type: 'json' }; +import { + Table70_BinaryUnicodeProperties, + Table69_NonbinaryUnicodeProperties, + Table71_BinaryPropertyOfStrings, + Unicode, + type Character, + type ListOfCharacter, + type CodePoint, + type Table69_NonbinaryUnicodePropertiesCanonicalized, +} from './all.mts'; +import { Assert } from '#self'; + +enum Direction { + Forward = 1, + Backward = -1, +} + +export type RegExpMatchingSource = (readonly string[]) & { readonly raw: string }; +/** https://tc39.es/ecma262/#pattern-matchstate */ +class MatchState { + readonly input: RegExpMatchingSource; + + readonly endIndex: number; + + readonly captures; + + constructor(input: RegExpMatchingSource, endIndex: number, captures: readonly (undefined | Range)[]) { + this.input = input; + this.endIndex = endIndex; + this.captures = captures; + } + + static createRegExpMatchingSource(input: readonly string[], raw: string) { + (input as Mutable).raw = raw; + return input as RegExpMatchingSource; + } +} +export { MatchState as RegExpState }; + +type MatcherResult = MatchState | 'failure'; +export type RegExpMatcher = (input: RegExpMatchingSource, index: number) => MatcherResult; + +// Note: A strict spec implementation cannot pass test262 because of stack overflow. We use generator to lift all calls to the top level. +type NonSpecFlattenedRegExpMatchingProcess = Generator<() => NonSpecFlattenedRegExpMatchingProcess, MatcherResult, MatcherResult>; +function runMatcher(matcher: NonSpecFlattenedRegExpMatchingProcess): MatcherResult { + // when debug, uncomment to use this version might be easier. + + // if (1 + 1 === 2) { + // let next: MatcherResult; + // while (true) { + // const iter = iterator.next(next!); + // if (iter.done) { + // const ret = iter.value; + // return ret; + // } + // const nextCall = iter.value(); + // const callResult = runMatcher(nextCall); + // next = callResult; + // } + // } + + const stack: NonSpecFlattenedRegExpMatchingProcess[] = []; + let next: MatcherResult | undefined; + while (true) { + const iter = matcher.next(next!); + if (iter.done) { + const ret = iter.value; + // return ret; + matcher = stack.pop()!; + if (matcher) { + // next = callResult (of upper call) + next = ret; + continue; + } else { + // outmost call + return ret; + } + } + const nextCall = iter.value(); + // const callResult = runMatcher(nextCall); + stack.push(matcher); + matcher = nextCall; + next = undefined; + } +} +/** https://tc39.es/ecma262/#pattern-matcher */ +type Matcher = (x: MatchState, c: MatcherContinuation) => NonSpecFlattenedRegExpMatchingProcess; +/** https://tc39.es/ecma262/#pattern-matchercontinuation */ +type MatcherContinuation = (y: MatchState) => NonSpecFlattenedRegExpMatchingProcess; + +type CharTester = (char: Character, canonicalize: RegExpRecord | undefined) => boolean; +/** https://tc39.es/ecma262/#pattern-charset */ +abstract class CharSet { + abstract has(c: Character, rer: RegExpRecord | undefined): boolean; + + abstract hasList(c: ListOfCharacter): boolean; + + getStrings() { + return [...this.strings || []]; + } + + /** + * Return false if the Pattern is compiled in UnicodeSetMode and contains the empty sequence or sequences of more than one character. + */ + abstract characterModeOnly: boolean; + + declare protected chars: Set | undefined; + + declare protected strings: Set | undefined; + + declare protected charTester: CharTester[] | undefined; + + static union(...sets: CharSet[]) { + const unionChars = new Set(); + const unionStrings = new Set(); + let unionCharTesters: CharTester[] = []; + sets.forEach((set) => { + if (set.chars) { + set.chars.forEach((c) => unionChars.add(c)); + } + if (set.strings) { + set.strings.forEach((s) => unionStrings.add(s)); + } + if (set.charTester) { + unionCharTesters = unionCharTesters.concat(set.charTester); + } + }); + + if (!unionCharTesters.length) { + if (!unionStrings.size) { + return new ConcreteCharSet(unionChars); + } + if (!unionChars.size) { + return ConcreteStringSet.of(unionStrings); + } + } + if (!unionChars.size && !unionStrings.size && unionCharTesters.length === 1) { + return new VirtualCharSet(unionCharTesters[0]); + } + return new UnionCharSet(unionChars, unionStrings, unionCharTesters); + } + + static intersection(...sets: CharSet[]): CharSet { + let intersectionChars: Set; + const setChars = sets.filter((x) => x.chars); + if (setChars.length === 0) { + intersectionChars = new Set(); + } else if (setChars.length === 1) { + intersectionChars = setChars[0].chars!; + } else { + const smallestSet = setChars.reduce((a, b) => (a.chars!.size < b.chars!.size ? a : b)); + intersectionChars = new Set(); + smallestSet.chars!.forEach((c) => { + if (setChars.every((s) => s.chars!.has(c))) { + intersectionChars.add(c); + } + }); + } + + let intersectionStrings: Set; + const setStrings = sets.filter((x) => x.strings); + if (setStrings.length === 0) { + intersectionStrings = new Set(); + } else if (setStrings.length === 1) { + intersectionStrings = setStrings[0].strings!; + } else { + const smallestSet = setStrings.reduce((a, b) => (a.strings!.size < b.strings!.size ? a : b)); + intersectionStrings = new Set(); + smallestSet.strings!.forEach((s) => { + if (setStrings.every((c) => c.strings!.has(s))) { + intersectionStrings.add(s); + } + }); + } + + let allCharTesters: CharTester[] = []; + sets.forEach((set) => { + if (set.charTester) { + allCharTesters = allCharTesters.concat(set.charTester); + } + }); + + if (!allCharTesters.length) { + if (!intersectionStrings.size) { + return new ConcreteCharSet(intersectionChars); + } + if (!intersectionChars.size) { + return ConcreteStringSet.of(intersectionStrings); + } + return new UnionCharSet(intersectionChars, intersectionStrings, undefined); + } + return new UnionCharSet(intersectionChars, intersectionStrings, allCharTesters.length ? [(char, canonicalize) => allCharTesters.every((f) => f(char, canonicalize))] : undefined); + } + + static subtract(maxSet: CharSet, subtractAllStrings: boolean, ...subtracts: readonly CharSet[]): CharSet { + const maxChars = maxSet.chars; + const maxStrings = subtractAllStrings ? undefined : maxSet.strings; + let allSubtractCharTesters: CharTester[] = []; + subtracts.forEach((subtract) => { + if (maxChars) { + subtract.chars?.forEach((c) => maxChars.delete(c)); + } + if (maxStrings) { + subtract.strings?.forEach((s) => maxStrings.delete(s)); + } + if (subtract.charTester) { + allSubtractCharTesters = allSubtractCharTesters.concat(subtract.charTester); + } + }); + if (!maxSet.charTester?.length && !allSubtractCharTesters.length) { + if (!maxStrings?.size) { + return new ConcreteCharSet(maxChars || []); + } + if (!maxChars?.size) { + return ConcreteStringSet.of(maxStrings); + } + return new UnionCharSet(maxChars, maxStrings, undefined); + } + return new UnionCharSet( + undefined, + maxStrings, + [(char, canonicalize) => { + if (!(maxChars?.has(char) || maxSet.charTester?.some((f) => f(char, canonicalize)))) { + return false; + } + if (allSubtractCharTesters.some((f) => f(char, canonicalize))) { + return false; + } + return true; + }], + ); + } +} + +class VirtualCharSet extends CharSet { + #f: CharTester; + + protected override charTester; + + constructor(f: CharTester) { + super(); + this.#f = f; + this.charTester = [f]; + } + + override has(c: Character, rer: RegExpRecord | undefined): boolean { + return this.#f(c, rer); + } + + override hasList(_c: ListOfCharacter): boolean { + return false; + } + + override characterModeOnly = true; +} + +class ConcreteCharSet extends CharSet { + protected override chars; + + #canonicalize: Record> | undefined; + + protected get debuggerGetCodePoints() { + return [...this.chars].map((char) => Unicode.toCodePoint(char)); + } + + constructor(chars: Iterable) { + super(); + this.chars = new Set(chars); + } + + override has(c: Character, rer: RegExpRecord): boolean { + const canonicalizeKey = JSON.stringify(rer); + this.#canonicalize ??= {}; + if (!this.#canonicalize[canonicalizeKey]) { + this.#canonicalize[canonicalizeKey] = new Set(); + const set = this.#canonicalize[canonicalizeKey]; + for (const c of this.chars) { + const ch = Canonicalize(rer, c); + set.add(ch); + } + } + return this.#canonicalize[canonicalizeKey].has(c); + } + + override hasList(_c: ListOfCharacter): boolean { + return false; + } + + override characterModeOnly = true; + + soleChar() { + Assert(this.chars.size === 1); + return this.chars.values().next().value!; + } +} + +class ConcreteStringSet extends CharSet { + protected override strings; + + private constructor(strings: Iterable) { + super(); + this.strings = new Set(strings); + } + + static of(charOrStrings: Iterable): CharSet { + const chars = new Set(); + const strings = new Set(); + for (const charOrString of charOrStrings) { + if (charOrString.length <= 1 || (charOrString.length === 2 && Array.from(charOrString).length === 1)) { + chars.add(charOrString as unknown as Character); + } else { + strings.add(charOrString); + } + } + if (chars.size && !strings.size) { + return new ConcreteCharSet(chars); + } else if (strings.size && !chars.size) { + return new ConcreteStringSet(strings); + } + return new UnionCharSet(chars, strings, undefined); + } + + override has(_c: Character): boolean { + return false; + } + + override hasList(c: ListOfCharacter): boolean { + return this.strings.has(c); + } + + override characterModeOnly = false; +} + +class UnionCharSet extends CharSet { + constructor(chars: Set | undefined, strings: Set | undefined, charTesters: CharTester[] | undefined) { + super(); + this.chars = chars; + this.strings = strings; + this.charTester = charTesters; + } + + override has(c: Character, rer: RegExpRecord): boolean { + if (this.chars && new ConcreteCharSet(this.chars).has(c, rer)) { + return true; + } + if (this.charTester?.some((f) => f(c, rer))) { + return true; + } + return false; + } + + override hasList(c: ListOfCharacter): boolean { + return !!this.strings?.has(c); + } + + get characterModeOnly() { + return !this.strings?.size; + } +} + +/** https://tc39.es/ecma262/#sec-regexp-records */ +export interface RegExpRecord { + readonly IgnoreCase: boolean; + readonly Multiline: boolean; + readonly DotAll: boolean; + readonly Unicode: boolean; + readonly UnicodeSets: boolean; + readonly CapturingGroupsCount: number; +} + +interface Range { + readonly startIndex: number; + readonly endIndex: number; +} + +/** https://tc39.es/ecma262/#sec-compilepattern */ +export function CompilePattern(pattern: ParseNode.RegExp.Pattern, rer: RegExpRecord): RegExpMatcher { + const m = CompileSubPattern(pattern.Disjunction, rer, Direction.Forward); + annotateMatcher(m, pattern.Disjunction); + return (input, index) => { + Assert(index >= 0 && index <= input.length); + const c: MatcherContinuation = function* MatchSuccess(y: MatchState) { + return y; + }; + // Let cap be a List of rer.[[CapturingGroupsCount]] undefined values, indexed 1 through rer.[[CapturingGroupsCount]]. + const cap = []; + for (let index = 1; index <= rer.CapturingGroupsCount; index += 1) { + cap[index] = undefined; + } + const x = new MatchState(input, index, cap); + return runMatcher(m(x, c)); + }; +} + +/** https://tc39.es/ecma262/#sec-compilesubpattern */ +function CompileSubPattern( + node: + ParseNode.RegExp.Disjunction | ParseNode.RegExp.Alternative | ParseNode.RegExp.Term, + rer: RegExpRecord, + direction: Direction, +): Matcher { + switch (node.type) { + // Disjunction :: Alternative | Disjunction + case 'Disjunction': { + if (node.Alternative && node.Disjunction) { + const m1 = CompileSubPattern(node.Alternative, rer, direction); + const m2 = CompileSubPattern(node.Disjunction, rer, direction); + return MatchTwoAlternatives(m1, m2); + } + // Disjunction :: Alternative + return CompileSubPattern(node.Alternative, rer, direction); + } + // Alternative :: [empty] + // Alternative :: Alternative Term + case 'Alternative': { + if (!node.Term.length) { + return EmptyMatcher; + } + if (node.Term.length === 1) { + return CompileSubPattern(node.Term[0], rer, direction); + } + return node.Term.reduceRight((m2, term) => { + const m1 = CompileSubPattern(term, rer, direction); + if (!m2) { + return m1; + } + return MatchSequence(m1, m2, direction); + }, undefined!); + } + // Term :: Assertion + // Term :: Atom + // Term :: Atom Quantifier + case 'Term': { + switch (node.production) { + case 'Assertion': + return annotateMatcher(CompileAssertion(node.Assertion, rer), node.Assertion); + case 'Atom': + if (node.Quantifier) { + const m = CompileAtom(node.Atom, rer, direction); + const q = CompileQuantifier(node.Quantifier); + Assert(q.Min <= q.Max); + const parenIndex = CountLeftCapturingParensBefore(node); + const parenCount = CountLeftCapturingParensWithin(node); + return (x, c) => RepeatMatcher(m, q.Min, q.Max, q.Greedy, x, c, parenIndex, parenCount); + } else { + return CompileAtom(node.Atom, rer, direction); + } + default: + unreachable(node); + } + } + default: + } + unreachable(node); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-repeatmatcher-abstract-operation */ +function* RepeatMatcher(m: Matcher, min: number, max: number, greedy: boolean, x: MatchState, c: MatcherContinuation, parenIndex: number, parenCount: number): NonSpecFlattenedRegExpMatchingProcess { + if (max === 0) { + return yield () => c(x); + } + const d: MatcherContinuation = function* RepeatMatcher_d(y) { + if (min === 0 && y.endIndex === x.endIndex) { + return 'failure'; + } + const min2 = min === 0 ? 0 : min - 1; + const max2 = max === Infinity ? Infinity : max - 1; + return yield () => RepeatMatcher(m, min2, max2, greedy, y, c, parenIndex, parenCount); + }; + const cap = [...x.captures]; + for (let k = parenIndex + 1; k <= parenIndex + parenCount; k += 1) { + cap[k] = undefined; + } + const input = x.input; + const e = x.endIndex; + const xr = new MatchState(input, e, cap); + if (min !== 0) { + return yield () => m(xr, d); + } + if (!greedy) { + const z = yield () => c(x); + if (z !== 'failure') { + return z; + } + return yield () => m(xr, d); + } + const z = yield () => m(xr, d); + if (z !== 'failure') { + return z; + } + return yield () => c(x); +} + +/** https://tc39.es/ecma262/#sec-emptymatcher */ +const EmptyMatcher: Matcher = (x, c) => c(x); +annotateMatcher(EmptyMatcher, 'EmptyMatcher'); + +/** https://tc39.es/ecma262/#sec-matchtwoalternatives */ +function MatchTwoAlternatives(m1: Matcher, m2: Matcher): Matcher { + return annotateMatcher(function* TwoAlternatives(x, c) { + const r = yield () => m1(x, c); + if (r !== 'failure') { + return r; + } + return yield () => m2(x, c); + }, [(m1 as MatcherWithComment).comment || m1, '|', (m2 as MatcherWithComment).comment || m2]); +} + +/** https://tc39.es/ecma262/#sec-matchsequence */ +function MatchSequence(m1: Matcher, m2: Matcher, direction: Direction): Matcher { + if (direction === Direction.Forward) { + return annotateMatcher(function Seq(x, c) { + const d: MatcherContinuation = (y) => m2(y, c); + return m1(x, d); + }, [(m1 as MatcherWithComment).comment || m1, '|', (m2 as MatcherWithComment).comment || m2]); + } else { + return annotateMatcher(function Seq_Backword(x, c) { + const d: MatcherContinuation = (y) => m1(y, c); + return m2(x, d); + }, [(m2 as MatcherWithComment).comment || m2, '|', (m1 as MatcherWithComment).comment || m1]); + } +} + +/** https://tc39.es/ecma262/#sec-compileassertion */ +function CompileAssertion(node: ParseNode.RegExp.Assertion, rer: RegExpRecord): Matcher { + if (node.production === '^') { + return function* Assertion_Start(x, c) { + const Input = x.input; + const e = x.endIndex; + if (e === 0 || (rer.Multiline && isLineTerminator(Input[e - 1]))) { + return yield () => c(x); + } + return 'failure'; + }; + } else if (node.production === '$') { + return function* Assertion_End(x, c) { + const Input = x.input; + const e = x.endIndex; + if (e === Input.length || (rer.Multiline && isLineTerminator(Input[e]))) { + return yield () => c(x); + } + return 'failure'; + }; + } else if (node.production === 'b') { + return function* Assertion_WordBoundary(x, c) { + const Input = x.input; + const e = x.endIndex; + const a = IsWordChar(rer, Input.raw, e - 1); + const b = IsWordChar(rer, Input.raw, e); + if ((a && !b) || (!a && b)) { + return yield () => c(x); + } + return 'failure'; + }; + } else if (node.production === 'B') { + return function* Assertion_NotWordBoundary(x, c) { + const Input = x.input; + const e = x.endIndex; + const a = IsWordChar(rer, Input.raw, e - 1); + const b = IsWordChar(rer, Input.raw, e); + if ((a && b) || (!a && !b)) { + return yield () => c(x); + } + return 'failure'; + }; + } else if (node.production === 'A') { + return function* Assertion_BufferStart(x, c) { + const e = x.endIndex; + if (e === 0) { + return yield () => c(x); + } + return 'failure'; + }; + } else if (node.production === 'z') { + return function* Assertion_BufferEnd(x, c) { + const Input = x.input; + const e = x.endIndex; + if (e === Input.length) { + return yield () => c(x); + } + return 'failure'; + }; + } else if (node.production === '?=') { + const m = CompileSubPattern(node.Disjunction, rer, Direction.Forward); + return function* Assertion_PositiveLookahead(x, c) { + const d: MatcherContinuation = function* Assertion_PositiveLookahead_Success(y) { + return y; + }; + const r = yield () => m(x, d); + if (r === 'failure') { + return 'failure'; + } + const cap = r.captures; + const input = x.input; + const xe = x.endIndex; + const z = new MatchState(input, xe, cap); + return yield () => c(z); + }; + } else if (node.production === '?!') { + const m = CompileSubPattern(node.Disjunction, rer, Direction.Forward); + return function* Assertion_NegativeLookahead(x, c) { + const d: MatcherContinuation = function* Assertion_NegativeLookahead_Success(y) { + return y; + }; + const r = yield () => m(x, d); + if (r !== 'failure') { + return 'failure'; + } + return yield () => c(x); + }; + } else if (node.production === '?<=') { + const m = CompileSubPattern(node.Disjunction, rer, Direction.Backward); + return function* Assertion_PositiveLookBehind(x, c) { + const d: MatcherContinuation = function* Assertion_PositiveLookBehind_Success(y) { + return y; + }; + const r = yield () => m(x, d); + if (r === 'failure') { + return 'failure'; + } + const cap = r.captures; + const input = x.input; + const xe = x.endIndex; + const z = new MatchState(input, xe, cap); + return yield () => c(z); + }; + } else if (node.production === '? m(x, d); + if (r !== 'failure') { + return 'failure'; + } + return yield () => c(x); + }; + } + unreachable(node.production); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-iswordchar-abstract-operation */ +function IsWordChar(rer: RegExpRecord, Input: string, e: number): boolean { + const inputLength = Input.length; + if (e === -1 || e === inputLength) { + return false; + } + const c = Input[e]; + return WordCharacters(rer).has(c as Character, rer); +} + +/** https://tc39.es/ecma262/#sec-compilequantifier */ +function CompileQuantifier(node: ParseNode.RegExp.Quantifier): { Min: number, Max: number, Greedy: boolean } { + const [Min, Max] = CompileQuantifierPrefix(node.QuantifierPrefix); + return { Min, Max, Greedy: !node.QuestionMark }; +} + +/** https://tc39.es/ecma262/#sec-compilequantifierprefix */ +function CompileQuantifierPrefix(node: ParseNode.RegExp.Quantifier['QuantifierPrefix']): [Min: number, Max: number] { + switch (node.production) { + case '*': + return [0, Infinity]; + case '+': + return [1, Infinity]; + case '?': + return [0, 1]; + default: { + return [node.DecimalDigits_a, node.DecimalDigits_b || node.DecimalDigits_a]; + } + } +} + +/** https://tc39.es/ecma262/#sec-compileatom */ +function CompileAtom(node: ParseNode.RegExp.Atom | ParseNode.RegExp.AtomEscape, rer: RegExpRecord, direction: Direction): Matcher { + if (node.type === 'Atom') { + switch (node.production) { + // Atom :: PatternCharacter + case 'PatternCharacter': { + const ch = node.PatternCharacter; + const A = new ConcreteCharSet([ch]); + return CharacterSetMatcher(rer, A, false, direction); + } + // Atom :: . + case '.': { + let A: CharSet = AllCharacters(rer); + if (!rer.DotAll) { + // Remove from A all characters corresponding to a code point on the right-hand side of the LineTerminator production. + A = CharSet.subtract(A, false, new VirtualCharSet(isLineTerminator)); + } + return CharacterSetMatcher(rer, A, false, direction); + } + // Atom :: CharacterClass + case 'CharacterClass': { + const cc = CompileCharacterClass(node.CharacterClass, rer); + const cs = cc.CharSet; + // If rer.[[UnicodeSets]] is false, or if every CharSetElement of cs consists of a single character (including if cs is empty), return CharacterSetMatcher(rer, cs, cc.[[Invert]], direction). + if (!rer.UnicodeSets || cs.characterModeOnly) { + return CharacterSetMatcher(rer, cs, cc.Invert, direction); + } + Assert(!cc.Invert); + const lm: Matcher[] = []; + // For each CharSetElement s in cs containing more than 1 character, iterating in descending order of length, do + for (const s of cs.getStrings().sort((a, b) => b.length - a.length)) { + // Let cs2 be a one-element CharSet containing the last code point of s. + const cs2 = new ConcreteCharSet([s.at(-1)! as Character]); + let m2 = CharacterSetMatcher(rer, cs2, false, direction); + // For each code point c1 in s, iterating backwards from its second-to-last code point, do + for (const c1 of Unicode.iterateByCodePoint(s).reverse().slice(1)) { + const cs1 = new ConcreteCharSet([c1 as unknown as Character]); + const m1 = CharacterSetMatcher(rer, cs1, false, direction); + m2 = MatchSequence(m1, m2, direction); + } + lm.push(m2); + } + // Let singles be the CharSet containing every CharSetElement of cs that consists of a single character. + const singles = CharSet.subtract(cs, true); + lm.push(CharacterSetMatcher(rer, singles, false, direction)); + // If cs contains the empty sequence of characters, append EmptyMatcher() to lm. + if (cs.hasList('' as ListOfCharacter)) { + lm.push(EmptyMatcher); + } + let m2 = lm.at(-1)!; + // For each Matcher m1 of lm, iterating backwards from its second-to-last element, do + for (const m1 of lm.toReversed().slice(1)) { + m2 = MatchTwoAlternatives(m1, m2); + } + return m2; + } + case 'Group': { + const m = CompileSubPattern(node.Disjunction, rer, direction); + const parenIndex = CountLeftCapturingParensBefore(node); + return annotateMatcher(function GroupMatcher(x, c) { + const d: MatcherContinuation = (y) => { + const cap = [...y.captures]; + const Input = x.input; + const xe = x.endIndex; + const ye = y.endIndex; + let r: Range; + if (direction === Direction.Forward) { + Assert(xe <= ye); + r = { startIndex: xe, endIndex: ye }; + } else { + Assert(direction === Direction.Backward); + Assert(ye <= xe); + r = { startIndex: ye, endIndex: xe }; + } + cap[parenIndex + 1] = r; + const z = new MatchState(Input, ye, cap); + return c(z); + }; + return m(x, d); + }, node); + } + case 'Modifier': { + const addModifiers = node.AddModifiers; + const removeModifiers = node.RemoveModifiers; + const modifiedRer = UpdateModifiers(rer, addModifiers?.join('') || '', removeModifiers?.join('') || ''); + return CompileSubPattern(node.Disjunction, modifiedRer, direction); + } + case 'AtomEscape': + return CompileAtom(node.AtomEscape, rer, direction); + default: + unreachable(node); + } + // Atom :: ( GroupSpecifieropt Disjunction ) + } else if (node.type === 'AtomEscape') { + switch (node.production) { + case 'DecimalEscape': { + const n = CapturingGroupNumber(node.DecimalEscape); + Assert(n <= rer.CapturingGroupsCount); + return BackreferenceMatcher(rer, [n], direction); + } + case 'CharacterEscape': { + const cv = CharacterValue(node.CharacterEscape); + const ch = Unicode.toCharacter(cv); + const A = new ConcreteCharSet([ch]); + return CharacterSetMatcher(rer, A, false, direction); + } + case 'CharacterClassEscape': { + const cs = CompileToCharSet(node.CharacterClassEscape, rer); + // If rer.[[UnicodeSets]] is false, or if every CharSetElement of cs consists of a single character (including if cs is empty), return CharacterSetMatcher(rer, cs, cc.[[Invert]], direction). + if (!rer.UnicodeSets || cs.characterModeOnly) { + return CharacterSetMatcher(rer, cs, false, direction); + } + const lm: Matcher[] = []; + // For each CharSetElement s in cs containing more than 1 character, iterating in descending order of length, do + for (const s of cs.getStrings().sort((a, b) => b.length - a.length)) { + const codePointOfS = Unicode.iterateByCodePoint(s); + // Let cs2 be a one-element CharSet containing the last code point of s. + const cs2 = new ConcreteCharSet([codePointOfS.at(-1)!]); + let m2 = CharacterSetMatcher(rer, cs2, false, direction); + // For each code point c1 in s, iterating backwards from its second-to-last code point, do + for (const c1 of codePointOfS.reverse().slice(1)) { + const cs1 = new ConcreteCharSet([c1]); + const m1 = CharacterSetMatcher(rer, cs1, false, direction); + m2 = MatchSequence(m1, m2, direction); + } + lm.push(m2); + } + // Let singles be the CharSet containing every CharSetElement of cs that consists of a single character. + const singles = CharSet.subtract(cs, true); + lm.push(CharacterSetMatcher(rer, singles, false, direction)); + // If cs contains the empty sequence of characters, append EmptyMatcher() to lm. + if (cs.hasList('' as ListOfCharacter)) { + lm.push(EmptyMatcher); + } + let m2 = lm.at(-1)!; + // For each Matcher m1 of lm, iterating backwards from its second-to-last element, do + for (const m1 of lm.toReversed().slice(1)) { + m2 = MatchTwoAlternatives(m1, m2); + } + return m2; + } + case 'CaptureGroupName': { + const matchingGroupSpecifiers = GroupSpecifiersThatMatch(node); + const parenIndices = []; + for (const atom_Group of matchingGroupSpecifiers) { + // Let parenIndex be CountLeftCapturingParensBefore(groupSpecifier). + // groupSpecifier is in a Atom_Group, the CountLeftCapturingParensBefore does not count for itself so add 1 + const parenIndex = CountLeftCapturingParensBefore(atom_Group) + 1; + parenIndices.push(parenIndex); + } + return BackreferenceMatcher(rer, parenIndices, direction); + } + default: + unreachable(node); + } + } + unreachable(node); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-charactersetmatcher-abstract-operation */ +function CharacterSetMatcher(rer: RegExpRecord, A: CharSet, invert: boolean, direction: Direction): Matcher { + if (rer.UnicodeSets) { + Assert(!invert); + // Assert: Every CharSetElement of A consists of a single character. + Assert(A.characterModeOnly); + } + return annotateMatcher(function* CharacterSetMatcher(x, c) { + const Input = x.input; + const e = x.endIndex; + const f = direction === Direction.Forward ? e + 1 : e - 1; + const InputLength = Input.length; + if (f < 0 || f > InputLength) { + return 'failure'; + } + const index = Math.min(e, f); + const ch = Input[index] as Character; + const cc = Canonicalize(rer, ch); + // If there exists a CharSetElement in A containing exactly one character a such that Canonicalize(rer, a) is cc, let found be true. Otherwise, let found be false. + const found = A.has(cc, rer); + + if ((!invert && !found) || (invert && found)) { + return 'failure'; + } + const cap = x.captures; + const y = new MatchState(Input, f, cap); + return yield () => c(y); + }, [A, invert]); +} + +/** https://tc39.es/ecma262/#sec-backreference-matcher */ +function BackreferenceMatcher(rer: RegExpRecord, ns: readonly number[], direction: Direction): Matcher { + return annotateMatcher(function* BackreferenceMatcher(x, c) { + const Input = x.input; + const cap = x.captures; + let r; + for (const n of ns) { + if (cap[n] !== undefined) { + Assert(r === undefined); + r = cap[n]; + } + } + if (r === undefined) { + return yield () => c(x); + } + const e = x.endIndex; + const rs = r.startIndex; + const re = r.endIndex; + const len = re - rs; + const f = direction === Direction.Forward ? e + len : e - len; + const InputLength = Input.length; + if (f < 0 || f > InputLength) { + return 'failure'; + } + const g = Math.min(e, f); + for (let i = 0; i < len; i += 1) { + if (Canonicalize(rer, Input[rs + i] as Character) !== Canonicalize(rer, Input[g + i] as Character)) { + return 'failure'; + } + } + const y = new MatchState(Input, f, cap); + return yield () => c(y); + }, ['BackreferenceMatcher', ns, rer]); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch */ +export function Canonicalize(rer: RegExpRecord, ch: Character): Character { + if (HasEitherUnicodeFlag(rer) && rer.IgnoreCase) { + // 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. + const mapped = Unicode.SimpleOrCommonCaseFoldingMapping(ch); + if (mapped) { + return mapped; + } else { + return ch; + } + } + if (!rer.IgnoreCase) { + return ch; + } + Assert(ch.length === 1, 'ch is a UTF-16 code unit'); + const cp = Unicode.toCodePoint(ch); + const u = Unicode.toUppercase(cp); + const uStr = CodePointsToString(Unicode.toCharacter(u)); + if (uStr.length !== 1) { + return ch; + } + // Let cu be uStr's single code unit element. + const cu = uStr[0] as Character; + if (Unicode.toCodePoint(ch) >= 128 && Unicode.toCodePoint(cu) < 128) { + return ch; + } + return cu; +} + +/** https://tc39.es/ecma262/#sec-updatemodifiers */ +function UpdateModifiers(rer: RegExpRecord, add: string, remove: string): RegExpRecord { + Assert(new Set([...add, ...remove]).size === (add + remove).length); + const next = { ...rer }; + if (remove.includes('i')) { + next.IgnoreCase = false; + } else if (add.includes('i')) { + next.IgnoreCase = true; + } + if (remove.includes('m')) { + next.Multiline = false; + } else if (add.includes('m')) { + next.Multiline = true; + } + if (remove.includes('s')) { + next.DotAll = false; + } else if (add.includes('s')) { + next.DotAll = true; + } + return next; +} + +/** https://tc39.es/ecma262/#sec-compilecharacterclass */ +function CompileCharacterClass(node: ParseNode.RegExp.CharacterClass, rer: RegExpRecord): { CharSet: CharSet, Invert: boolean } { + const A = CompileToCharSet(node.ClassContents, rer); + return { + CharSet: rer.UnicodeSets && node.invert ? CharacterComplement(rer, A) : A, + Invert: rer.UnicodeSets ? false : node.invert, + }; +} + +/** https://tc39.es/ecma262/#sec-compiletocharset */ +function CompileToCharSet( + node: + | ParseNode.RegExp.ClassContents + | ParseNode.RegExp.ClassAtom + | ParseNode.RegExp.ClassEscape + | ParseNode.RegExp.CharacterClassEscape + | ParseNode.RegExp.UnicodePropertyValueExpression + | ParseNode.RegExp.ClassUnion + | ParseNode.RegExp.ClassIntersection + | ParseNode.RegExp.ClassSubtraction + | ParseNode.RegExp.ClassSetRange + | ParseNode.RegExp.ClassSetOperand + | ParseNode.RegExp.NestedClass + | ParseNode.RegExp.ClassSetCharacter + | ParseNode.RegExp.ClassStringDisjunction + // eslint-disable-next-line comma-style + , rer: RegExpRecord, +): CharSet { + switch (node.type) { + // ClassContents :: [empty] + // NonemptyClassRanges :: ClassAtom NonemptyClassRangesNoDash + // NonemptyClassRanges :: ClassAtom - ClassAtom ClassContents + // NonemptyClassRangesNoDash :: ClassAtomNoDash NonemptyClassRangesNoDash + // NonemptyClassRangesNoDash :: ClassAtomNoDash - ClassAtom ClassContents + case 'ClassContents': { + if (node.production === 'Empty') { + return new ConcreteCharSet([]); + } else if (node.production === 'NonEmptyClassRanges') { + let allSet: CharSet = new ConcreteCharSet([]); + for (const range of node.NonemptyClassRanges) { + if (isArray(range)) { + const [A, B] = range; + const a = CompileToCharSet(A, rer); + const b = CompileToCharSet(B, rer); + Assert(a instanceof ConcreteCharSet && b instanceof ConcreteCharSet); + const set = CharacterRange(a, b); + allSet = CharSet.union(allSet, set); + } else { + const set = CompileToCharSet(range, rer); + allSet = CharSet.union(allSet, set); + } + } + return allSet!; + } else if (node.production === 'ClassSetExpression') { + return CompileToCharSet(node.ClassSetExpression, rer); + } + unreachable(node); + } + // ClassAtom :: - + // ClassAtomNoDash :: SourceCharacter but not one of \ or ] or - + case 'ClassAtom': { + if (node.production === '-') { + return new ConcreteCharSet(['-' as Character]); + } else if (node.production === 'SourceCharacter') { + return new ConcreteCharSet([node.SourceCharacter as Character]); + } else if (node.production === 'ClassEscape') { + return CompileToCharSet(node.ClassEscape, rer); + } + unreachable(node); + } + // ClassEscape :: - + // ClassEscape :: CharacterEscape + case 'ClassEscape': { + if (node.production === 'CharacterClassEscape') { + return CompileToCharSet(node.CharacterClassEscape, rer); + } + const cv = CharacterValue(node); + return new ConcreteCharSet([Unicode.toCharacter(cv)]); + } + // CharacterClassEscape :: d d s S w W + // CharacterClassEscape :: p{ UnicodePropertyValueExpression } + // CharacterClassEscape :: P{ UnicodePropertyValueExpression } + case 'CharacterClassEscape': { + switch (node.production) { + case 'd': + return new ConcreteCharSet('0123456789' as Iterable); + case 'D': + return CharacterComplement(rer, new ConcreteCharSet('0123456789' as Iterable)); + case 's': + return new VirtualCharSet((char) => isWhitespace(char) || isLineTerminator(char)); + case 'S': + return new VirtualCharSet(((char) => !isWhitespace(char) && !isLineTerminator(char))); + case 'w': + return MaybeSimpleCaseFolding(rer, WordCharacters(rer)); + case 'W': + return CharacterComplement(rer, MaybeSimpleCaseFolding(rer, WordCharacters(rer))); + case 'p': + return CompileToCharSet(node.UnicodePropertyValueExpression!, rer); + case 'P': { + const S = CompileToCharSet(node.UnicodePropertyValueExpression!, rer); + // Cannot implement: Assert: S contains only single code points. + return CharacterComplement(rer, S); + } + default: + unreachable(node); + } + } + // UnicodePropertyValueExpression :: UnicodePropertyName = UnicodePropertyValue + // UnicodePropertyValueExpression :: LoneUnicodePropertyNameOrValue + case 'UnicodePropertyValueExpression': { + if (node.production === '=') { + const ps = node.UnicodePropertyName; + const p = UnicodeMatchProperty(rer, ps); + Assert(p in Table69_NonbinaryUnicodeProperties); + __ts_cast__(p); + const vs = node.UnicodePropertyValue; + let v: string; + let A: CharSet; + if (p === 'Script_Extensions') { + Assert(vs in PropertyValueAliases.Script); + // Let v be the Set containing the “short name”, “long name”, and any other aliases corresponding with value vs for property “Script” in PropertyValueAliases.txt. + v = UnicodeMatchPropertyValue('Script', vs); + // Return the CharSet containing all Unicode code points whose character database definition includes the property “Script_Extensions” with value having a non-empty intersection with v. + A = new VirtualCharSet((ch, rer) => Unicode.characterMatchPropertyValue(ch, p, v, rer)); + } else { + v = UnicodeMatchPropertyValue(p, vs); + // Let A be the CharSet containing all Unicode code points whose character database definition includes the property p with value v. + A = new VirtualCharSet((ch, rer) => Unicode.characterMatchPropertyValue(ch, p, v, rer)); + } + return MaybeSimpleCaseFolding(rer, A); + } else { + const s = node.LoneUnicodePropertyNameOrValue; + if (s in PropertyValueAliases.General_Category) { + const v = UnicodeMatchPropertyValue('General_Category', s); + // Return the CharSet containing all Unicode code points whose character database definition includes the property “General_Category” with value v. + return new VirtualCharSet((ch, rer) => Unicode.characterMatchPropertyValue(ch, 'General_Category', v, rer)); + } + const p = UnicodeMatchProperty(rer, s); + Assert(p in Table70_BinaryUnicodeProperties || p in Table71_BinaryPropertyOfStrings); + // Let A be the CharSet containing all CharSetElements whose character database definition includes the property p with value “True”. + if (p in Table71_BinaryPropertyOfStrings) { + const A = ConcreteStringSet.of(Unicode.getStringPropertySet(p as keyof typeof Table71_BinaryPropertyOfStrings)); + return MaybeSimpleCaseFolding(rer, A); + } + const A = new VirtualCharSet((ch, rer) => Unicode.characterMatchPropertyValue(ch, p as Table69_NonbinaryUnicodePropertiesCanonicalized, undefined, rer)); + return MaybeSimpleCaseFolding(rer, A); + } + } + // ClassUnion :: ClassSetRange ClassUnion + // ClassUnion :: ClassSetOperand ClassUnion + case 'ClassUnion': { + return CharSet.union(...node.union.map((part): CharSet => CompileToCharSet(part, rer))); + } + // ClassIntersection :: ClassSetOperand && ClassSetOperand + // ClassIntersection :: ClassIntersection && ClassSetOperand + case 'ClassIntersection': { + return CharSet.intersection(...node.operands.map((part): CharSet => CompileToCharSet(part, rer))); + } + // ClassSubtraction :: ClassSetOperand -- ClassSetOperand + // ClassSubtraction :: ClassSubtraction -- ClassSetOperand + case 'ClassSubtraction': { + const mainSet = CompileToCharSet(node.operands[0], rer); + return CharSet.subtract(mainSet, false, ...node.operands.slice(1).map((part) => CompileToCharSet(part, rer))); + } + // ClassSetRange :: ClassSetCharacter - ClassSetCharacter + case 'ClassSetRange': { + const A = CompileToCharSet(node.left, rer); + const B = CompileToCharSet(node.right, rer); + Assert(A instanceof ConcreteCharSet && B instanceof ConcreteCharSet); + return MaybeSimpleCaseFolding(rer, CharacterRange(A, B)); + } + // ClassSetOperand :: ClassSetCharacter + // ClassSetOperand :: ClassStringDisjunction + // ClassSetOperand :: NestedClass + case 'ClassSetOperand': { + if (node.production === 'NestedClass') { + return CompileToCharSet(node.NestedClass, rer); + } else if (node.production === 'ClassSetCharacter') { + const A = CompileToCharSet(node.ClassSetCharacter, rer); + return MaybeSimpleCaseFolding(rer, A); + } else if (node.production === 'ClassStringDisjunction') { + const A = CompileToCharSet(node.ClassStringDisjunction, rer); + return MaybeSimpleCaseFolding(rer, A); + } + unreachable(node); + } + // NestedClass :: [ ClassContents ] + // NestedClass :: [^ ClassContents ] + // NestedClass :: \ CharacterClassEscape + case 'NestedClass': { + if (node.production === 'ClassContents') { + const A = CompileToCharSet(node.ClassContents, rer); + if (node.invert) { + return CharacterComplement(rer, A); + } + return A; + } + if (node.CharacterClassEscape) { + return CompileToCharSet(node.CharacterClassEscape, rer); + } + throw new Assert.Error('Invalid AST'); + } + // ClassStringDisjunction :: \q{ ClassStringDisjunctionContents } + // ClassStringDisjunctionContents :: ClassString + // ClassStringDisjunctionContents :: ClassString | ClassStringDisjunctionContents + case 'ClassStringDisjunction': { + const s = node.ClassString.map((node) => CompileClassSetString(node, rer)); + const A = ConcreteStringSet.of(s); + return A; + } + // ClassSetCharacter :: + // SourceCharacter but not ClassSetSyntaxCharacter + // \ CharacterEscape + // \ ClassSetReservedPunctuator + case 'ClassSetCharacter': { + const cv = CharacterValue(node); + const A = new ConcreteCharSet([Unicode.toCharacter(cv)]); + return A; + } + default: + unreachable(node); + } +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-characterrange-abstract-operation */ +function CharacterRange(A: ConcreteCharSet, B: ConcreteCharSet): CharSet { + const a = A.soleChar(); + const b = B.soleChar(); + const i = Unicode.toCodePoint(a); + const j = Unicode.toCodePoint(b); + Assert(i <= j); + + const canonicalized: Record> = {}; + // Return the CharSet containing all characters with a character value in the inclusive interval from i to j. + return new VirtualCharSet((ch, rer) => { + const cp = Unicode.toCodePoint(ch); + if (rer) { + const canonicalizedKey = JSON.stringify(rer); + if (canonicalized[canonicalizedKey] === undefined) { + canonicalized[canonicalizedKey] = new Set(); + const set = canonicalized[canonicalizedKey]; + for (let index = i; index <= j; index = index + 1 as CodePoint) { + const ch = Unicode.toCharacter(index); + set.add(Canonicalize(rer, ch)); + } + } + return canonicalized[canonicalizedKey].has(ch); + } + return cp >= i && cp <= j; + }); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-haseitherunicodeflag-abstract-operation */ +function HasEitherUnicodeFlag(rer: RegExpRecord) { + return rer.Unicode || rer.UnicodeSets; +} + +/** https://tc39.es/ecma262/#sec-wordcharacters */ +function WordCharacters(rer: RegExpRecord): CharSet { + const basicWordChars = new ConcreteCharSet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_' as Iterable); + const extraWordChars = new VirtualCharSet((c) => Unicode.isCharacter(c) && !basicWordChars.has(c, rer) && basicWordChars.has(Canonicalize(rer, c), rer)); + return CharSet.union(basicWordChars, extraWordChars); +} + +/** https://tc39.es/ecma262/#sec-allcharacters */ +function AllCharacters(rer: RegExpRecord): VirtualCharSet { + if (rer.UnicodeSets && rer.IgnoreCase) { + // Return the CharSet containing all Unicode code points c that do not have a Simple Case Folding mapping (that is, scf(c)=c). + return new VirtualCharSet((char) => Unicode.isCharacter(char) && Unicode.SimpleOrCommonCaseFoldingMapping(char) !== char); + } else if (HasEitherUnicodeFlag(rer)) { + // Return the CharSet containing all code point values. + return new VirtualCharSet((char) => Unicode.isCharacter(char)); + } else { + // Return the CharSet containing all code unit values. + return new VirtualCharSet((ch) => ch.length === 1); + } +} + +/** https://tc39.es/ecma262/#sec-maybesimplecasefolding */ +function MaybeSimpleCaseFolding(rer: RegExpRecord, A: CharSet): CharSet { + if (!rer.UnicodeSets || !rer.IgnoreCase) { + return A; + } + const strings = A.getStrings(); + const scfString = strings.map((s) => Array.from(Unicode.iterateCharacterByCodePoint(s)).map(Unicode.SimpleOrCommonCaseFoldingMapping).join('') as ListOfCharacter); + + const scfChar: CharTester = (ch, rer) => { + // before optimized: + // a. Let t be an empty sequence of characters. + // b. For each single code point cp in s, do + // i. Append scf(cp) to t. + // c. Add t to B. + + // it means B only contains scf(A) + // we optimized it as: + // if scf(ch) !== ch, it means ch is impossible to appear in scf(A). + let scf = ''; + for (const cp of Unicode.iterateCharacterByCodePoint(ch)) { + scf += Unicode.SimpleOrCommonCaseFoldingMapping(cp); + } + if (scf !== ch) { + return false; + } + return A.has(ch, rer); + }; + return CharSet.union(ConcreteStringSet.of(scfString), new VirtualCharSet(scfChar)); +} + +/** https://tc39.es/ecma262/#sec-charactercomplement */ +function CharacterComplement(rer: RegExpRecord, S: CharSet): VirtualCharSet { + const A = AllCharacters(rer); + // Return the CharSet containing the CharSetElements of A which are not also CharSetElements of S. + return new VirtualCharSet((ch, rer) => A.has(ch, rer) && !S.has(ch, rer)); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-unicodematchproperty-p */ +function UnicodeMatchProperty(rer: RegExpRecord, p: string): string { + // If rer.[[UnicodeSets]] is true and _p_ is listed in the “Property name” column of Table 71, then, then + if (rer.UnicodeSets && p in Table71_BinaryPropertyOfStrings) { + return p; + } + // Assert: p is listed in the “Property name and aliases” column of Table 69 or Table 70. + // Return the “canonical property name” corresponding to the property name or property alias p in Table 69 or Table 70. + if (p in Table69_NonbinaryUnicodeProperties) { + return Table69_NonbinaryUnicodeProperties[p as keyof typeof Table69_NonbinaryUnicodeProperties]; + } + if (p in Table70_BinaryUnicodeProperties) { + return Table70_BinaryUnicodeProperties[p as keyof typeof Table70_BinaryUnicodeProperties]; + } + Assert(false, 'p in Table69_NonbinaryUnicodeProperties || p in Table70_BinaryUnicodeProperties'); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-unicodematchpropertyvalue-p-v */ +function UnicodeMatchPropertyValue(p: string, v: string): string { + // Assert: p is a canonical, unaliased Unicode property name listed in the “Canonical property name” column of Table 69. + const CanonicalizedP = Table69_NonbinaryUnicodeProperties[p as keyof typeof Table69_NonbinaryUnicodeProperties]; + Assert(p in Table69_NonbinaryUnicodeProperties && CanonicalizedP === p); + + const table = PropertyValueAliases[CanonicalizedP]; + // Assert: v is a property value or property value alias for the Unicode property p listed in PropertyValueAliases.txt. + Assert(v in table); + // If v is a “short name” or other alias associated with some “long name” l for property name p in PropertyValueAliases.txt, return l; otherwise, return v. + return table[v as keyof typeof table] as string; +} + +/** https://tc39.es/ecma262/#sec-compileclasssetstring */ +function CompileClassSetString(node: ParseNode.RegExp.ClassSetCharacter[], rer: RegExpRecord): ListOfCharacter { + let str = ''; + for (const char of node) { + const cs = CompileToCharSet(char, rer); + Assert(cs instanceof ConcreteCharSet); + const s1 = cs.soleChar(); + str += s1; + } + return str as ListOfCharacter; +} + +// SS: +export function CountLeftCapturingParensWithin(node: ParseNode.RegExp.Term_Atom | ParseNode.RegExp.Pattern): number { + if (node.type === 'Pattern') { + return node.capturingGroups.length; + } + return node.capturingParenthesesWithin; +} +function CountLeftCapturingParensBefore(node: ParseNode.RegExp.Term_Atom | ParseNode.RegExp.Atom_Group): number { + return node.leftCapturingParenthesesBefore; +} +export function IsCharacterClass(node: ParseNode.RegExp.ClassAtom) { + return node.production === 'ClassEscape' && node.ClassEscape.production === 'CharacterClassEscape'; +} +function CapturingGroupNumber(node: ParseNode.RegExp.DecimalEscape): number { + return node.value; +} +function GroupSpecifiersThatMatch(node: ParseNode.RegExp.AtomEscape_CaptureGroupName) { + return node.groupSpecifiersThatMatchSelf; +} + +// for debugging purpose +type MatcherWithComment = Matcher & { comment: unknown }; +function annotateMatcher(matcher: Matcher, comment: unknown): Matcher { + Object.assign(matcher, { comment }); + return matcher; +} diff --git a/src/runtime-semantics/RegularExpressionLiteral.mts b/src/runtime-semantics/RegularExpressionLiteral.mts new file mode 100644 index 0000000..7a92b87 --- /dev/null +++ b/src/runtime-semantics/RegularExpressionLiteral.mts @@ -0,0 +1,16 @@ +import { Value } from '../value.mts'; +import { BodyText, FlagText } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { RegExpCreate } from '#self'; + +/** https://tc39.es/ecma262/#sec-regular-expression-literals-runtime-semantics-evaluation */ +// RegularExpressionLiteral : +// `/` RegularExpressionBody `/` RegularExpressionFlags +export function* Evaluate_RegularExpressionLiteral(RegularExpressionLiteral: ParseNode.RegularExpressionLiteral) { + // 1. Let pattern be ! UTF16Encode(BodyText of RegularExpressionLiteral). + const pattern = Value(BodyText(RegularExpressionLiteral)); + // 2. Let flags be ! UTF16Encode(FlagText of RegularExpressionLiteral). + const flags = Value(FlagText(RegularExpressionLiteral)); + // 3. Return RegExpCreate(pattern, flags). + return yield* RegExpCreate(pattern, flags); +} diff --git a/src/runtime-semantics/RelationalExpression.mts b/src/runtime-semantics/RelationalExpression.mts new file mode 100644 index 0000000..d55179c --- /dev/null +++ b/src/runtime-semantics/RelationalExpression.mts @@ -0,0 +1,151 @@ +import { + surroundingAgent, +} from '../host-defined/engine.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { + ObjectValue, + Value, + wellKnownSymbols, +} from '../value.mts'; +import { Q, X } from '../completion.mts'; +import { Evaluate } from '../evaluator.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + AbstractRelationalComparison, + Call, + GetMethod, + GetValue, + HasProperty, + IsCallable, + OrdinaryHasInstance, + ToBoolean, + ToPropertyKey, + PrivateElementFind, +} from '#self'; +import { ResolvePrivateIdentifier, type PrivateEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-instanceofoperator */ +export function* InstanceofOperator(V: Value, target: Value) { + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!(target instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', target); + } + // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). + const instOfHandler = Q(yield* 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(yield* Call(instOfHandler, target, [V])))); + } + // 4. If IsCallable(target) is false, throw a TypeError exception. + if (!IsCallable(target)) { + return surroundingAgent.Throw('TypeError', 'NotAFunction', target); + } + // 5. Return ? OrdinaryHasInstance(target, V). + return Q(yield* OrdinaryHasInstance(target, V)); +} + +// RelationalExpression : PrivateIdentifier `in` ShiftExpression +export function* Evaluate_RelationalExpression_PrivateIdentifier({ PrivateIdentifier, ShiftExpression }: ParseNode.RelationalExpression) { + // 1. Let privateIdentifier be the StringValue of PrivateIdentifier. + const privateIdentifier = StringValue(PrivateIdentifier!); + // 2. Let rref be the result of evaluating ShiftExpression. + const rref = Q(yield* Evaluate(ShiftExpression)); + // 3. Let rval be ? GetValue(rref). + const rval = Q(yield* GetValue(rref)); + // 4. If Type(rval) is not Object, throw a TypeError exception. + if (!(rval instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', rval); + } + // 5. Let privateEnv be the running execution context's PrivateEnvironment. + const privateEnv = surroundingAgent.runningExecutionContext.PrivateEnvironment as PrivateEnvironmentRecord; + // 6. Let privateName be ! ResolvePrivateIdentifier(privateEnv, privateIdentifier). + const privateName = X(ResolvePrivateIdentifier(privateEnv, privateIdentifier)); + // 7. If ! PrivateElementFind(privateName, rval) is not empty, return true. + if (X(PrivateElementFind(privateName, rval)) !== undefined) { + return Value.true; + } + // 8. Return false. + return Value.false; +} + +/** https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation */ +// RelationalExpression : +// RelationalExpression `<` ShiftExpression +// RelationalExpression `>` ShiftExpression +// RelationalExpression `<=` ShiftExpression +// RelationalExpression `>=` ShiftExpression +// RelationalExpression `instanceof` ShiftExpression +// RelationalExpression `in` ShiftExpression +// PrivateIdentifier `in` ShiftExpression +export function* Evaluate_RelationalExpression(expr: ParseNode.RelationalExpression) { + if (expr.PrivateIdentifier) { + return yield* Evaluate_RelationalExpression_PrivateIdentifier(expr); + } + + const { RelationalExpression, operator, ShiftExpression } = expr; + + // 1. Let lref be the result of evaluating RelationalExpression. + const lref = Q(yield* Evaluate(RelationalExpression!)); + // 2. Let lval be ? GetValue(lref). + const lval = Q(yield* GetValue(lref)); + // 3. Let rref be the result of evaluating ShiftExpression. + const rref = Q(yield* Evaluate(ShiftExpression)); + // 4. Let rval be ? GetValue(rref). + const rval = Q(yield* GetValue(rref)); + switch (operator) { + case '<': { + // 5. Let r be the result of performing Abstract Relational Comparison lval < rval. + const r = yield* AbstractRelationalComparison(lval, rval); + Q(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 = yield* AbstractRelationalComparison(rval, lval, false); + Q(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 = yield* AbstractRelationalComparison(rval, lval, false); + Q(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 = yield* AbstractRelationalComparison(lval, rval); + Q(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(yield* InstanceofOperator(lval, rval)); + case 'in': + // 5. Return ? InstanceofOperator(lval, rval). + if (!(rval instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', rval); + } + // 6. Return ? HasProperty(rval, ? ToPropertyKey(lval)). + return Q(yield* HasProperty(rval, Q(yield* ToPropertyKey(lval)))); + default: + throw new OutOfRange('Evaluate_RelationalExpression', operator); + } +} diff --git a/src/runtime-semantics/RestBindingInitialization.mts b/src/runtime-semantics/RestBindingInitialization.mts new file mode 100644 index 0000000..5355fa0 --- /dev/null +++ b/src/runtime-semantics/RestBindingInitialization.mts @@ -0,0 +1,29 @@ +import { Value } from '../value.mts'; +import { surroundingAgent } from '../host-defined/engine.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + CopyDataProperties, + InitializeReferencedBinding, + OrdinaryObjectCreate, + PutValue, + ResolveBinding, +} from '#self'; +import type { EnvironmentRecord, PropertyKeyValue, UndefinedValue } from '#self'; + +// BindingRestProperty : `...` BindingIdentifier +export function* RestBindingInitialization({ BindingIdentifier }: ParseNode.BindingRestProperty, value: Value, environment: EnvironmentRecord | UndefinedValue, excludedNames: readonly PropertyKeyValue[]) { + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + const lhs = Q(yield* 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(yield* CopyDataProperties(restObj, value, excludedNames)); + // 4. If environment is undefined, return PutValue(lhs, restObj). + if (environment === Value.undefined) { + return yield* PutValue(lhs, restObj); + } + // 5. Return InitializeReferencedBinding(lhs, restObj). + return yield* InitializeReferencedBinding(lhs, restObj); +} diff --git a/src/runtime-semantics/ReturnStatement.mts b/src/runtime-semantics/ReturnStatement.mts new file mode 100644 index 0000000..edcb12d --- /dev/null +++ b/src/runtime-semantics/ReturnStatement.mts @@ -0,0 +1,32 @@ +import { Value } from '../value.mts'; +import { Evaluate, type Evaluator } from '../evaluator.mts'; +import { + Completion, + Await, + Q, X, + ReturnCompletion, + ThrowCompletion, +} from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue, GetGeneratorKind } from '#self'; + +/** https://tc39.es/ecma262/#sec-return-statement-runtime-semantics-evaluation */ +// ReturnStatement : +// `return` `;` +// `return` Expression `;` +export function* Evaluate_ReturnStatement({ Expression }: ParseNode.ReturnStatement): Evaluator { + 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 = Q(yield* Evaluate(Expression)); + // 1. Let exprValue be ? GetValue(exprRef). + let exprValue = Q(yield* 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/src/runtime-semantics/Script.mts b/src/runtime-semantics/Script.mts new file mode 100644 index 0000000..41dd93c --- /dev/null +++ b/src/runtime-semantics/Script.mts @@ -0,0 +1,15 @@ +import { Value } from '../value.mts'; +import { NormalCompletion } from '../completion.mts'; +import { Evaluate } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-script-semantics-runtime-semantics-evaluation */ +// Script : +// [empty] +// ScriptBody +export function* Evaluate_Script({ ScriptBody }: ParseNode.Script) { + if (!ScriptBody) { + return NormalCompletion(Value.undefined); + } + return yield* Evaluate(ScriptBody); +} diff --git a/src/runtime-semantics/ScriptBody.mts b/src/runtime-semantics/ScriptBody.mts new file mode 100644 index 0000000..746b271 --- /dev/null +++ b/src/runtime-semantics/ScriptBody.mts @@ -0,0 +1,7 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Evaluate_StatementList } from './all.mts'; + +// ScriptBody : StatementList +export function Evaluate_ScriptBody(ScriptBody: ParseNode.ScriptBody) { + return Evaluate_StatementList(ScriptBody.StatementList); +} diff --git a/src/runtime-semantics/ShiftExpression.mts b/src/runtime-semantics/ShiftExpression.mts new file mode 100644 index 0000000..b69d63d --- /dev/null +++ b/src/runtime-semantics/ShiftExpression.mts @@ -0,0 +1,17 @@ +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateStringOrNumericBinaryExpression } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-left-shift-operator-runtime-semantics-evaluation */ +// ShiftExpression : +// ShiftExpression `<<` AdditiveExpression +/** https://tc39.es/ecma262/#sec-signed-right-shift-operator-runtime-semantics-evaluation */ +// ShiftExpression : +// ShiftExpression `>>` AdditiveExpression +/** https://tc39.es/ecma262/#sec-unsigned-right-shift-operator-runtime-semantics-evaluation */ +// ShiftExpression : +// ShiftExpression `>>>` AdditiveExpression +export function* Evaluate_ShiftExpression({ ShiftExpression, operator, AdditiveExpression }: ParseNode.ShiftExpression): ValueEvaluator { + return Q(yield* EvaluateStringOrNumericBinaryExpression(ShiftExpression, operator, AdditiveExpression)); +} diff --git a/src/runtime-semantics/StatementList.mts b/src/runtime-semantics/StatementList.mts new file mode 100644 index 0000000..84c489a --- /dev/null +++ b/src/runtime-semantics/StatementList.mts @@ -0,0 +1,33 @@ +import { Evaluate } from '../evaluator.mts'; +import { + EnsureCompletion, + Q, + UpdateEmpty, + NormalCompletion, +} from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { surroundingAgent, type Completion, type Value } from '#self'; + +/** https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation */ +export function* Evaluate_StatementList(StatementList: ParseNode.StatementList) { + if (StatementList.length === 0) { + return NormalCompletion(undefined); + } + + let blockCompletion: Completion = NormalCompletion(undefined); + + for (let index = 0; index < StatementList.length; index += 1) { + const StatementListItem = StatementList[index]; + + if (surroundingAgent.hostDefinedOptions.onDebugger) { + const NextStatementListItem = StatementList[index + 1]; + surroundingAgent.runningExecutionContext.callSite.setNextLocation(NextStatementListItem); + } + + Q(blockCompletion); + const itemCompletion = EnsureCompletion(yield* Evaluate(StatementListItem)); + blockCompletion = UpdateEmpty(itemCompletion, blockCompletion); + } + + return blockCompletion; +} diff --git a/src/runtime-semantics/StringIndexOf.mts b/src/runtime-semantics/StringIndexOf.mts new file mode 100644 index 0000000..e241a6d --- /dev/null +++ b/src/runtime-semantics/StringIndexOf.mts @@ -0,0 +1,43 @@ +import { JSStringValue } from '../value.mts'; +import { Assert, F, isNonNegativeInteger } from '#self'; + +// https://tc39.es/proposal-string-replaceall/#sec-stringindexof +export function StringIndexOf(string: JSStringValue, searchValue: JSStringValue, fromIndex: number) { + // 1. Assert: Type(string) is String. + Assert(string instanceof JSStringValue); + // 2. Assert: Type(searchValue) is String. + Assert(searchValue instanceof JSStringValue); + // 3. Assert: fromIndex is a non-negative integer. + Assert(isNonNegativeInteger(fromIndex)); + 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 F(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 F(pos); +} diff --git a/src/runtime-semantics/StringPad.mts b/src/runtime-semantics/StringPad.mts new file mode 100644 index 0000000..b0a656e --- /dev/null +++ b/src/runtime-semantics/StringPad.mts @@ -0,0 +1,34 @@ +import { JSStringValue, Value } from '../value.mts'; +import { Q } from '../completion.mts'; +import type { ValueEvaluator } from '../evaluator.mts'; +import { + Assert, ToString, ToLength, R, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-stringpad */ +export function* StringPad(O: Value, maxLength: Value, fillString: Value, placement: 'start' | 'end'): ValueEvaluator { + Assert(placement === 'start' || placement === 'end'); + const S = Q(yield* ToString(O)); + const intMaxLength = R(Q(yield* ToLength(maxLength))); + const stringLength = S.stringValue().length; + if (intMaxLength <= stringLength) { + return S; + } + let filler; + if (fillString === Value.undefined) { + filler = ' '; + } else { + filler = Q(yield* 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 Value(truncatedStringFiller + S.stringValue()); + } else { + return Value(S.stringValue() + truncatedStringFiller); + } +} diff --git a/src/runtime-semantics/SuperCall.mts b/src/runtime-semantics/SuperCall.mts new file mode 100644 index 0000000..21a46bf --- /dev/null +++ b/src/runtime-semantics/SuperCall.mts @@ -0,0 +1,65 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { ObjectValue } from '../value.mts'; +import { Q, X } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ArgumentListEvaluation } from './all.mts'; +import { + Assert, + Construct, + GetNewTarget, + GetThisEnvironment, + IsConstructor, + InitializeInstanceElements, + isECMAScriptFunctionObject, + type FunctionObject, +} from '#self'; +import { FunctionEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation */ +// SuperCall : `super` Arguments +export function* Evaluate_SuperCall({ Arguments }: ParseNode.SuperCall) { + // 1. Let newTarget be GetNewTarget(). + const newTarget = GetNewTarget(); + // 2. Assert: Type(newTarget) is Object. + Assert(newTarget instanceof ObjectValue); + // 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)) { + return surroundingAgent.Throw('TypeError', 'NotAConstructor', func); + } + // 6. Let result be ? Construct(func, argList, newTarget). + const result = Q(yield* Construct(func, argList, newTarget as FunctionObject)); + // 7. Let thisER be GetThisEnvironment(). + const thisER = GetThisEnvironment(); + // 8. Assert: thisER is a Function Environment Record. + Assert(thisER instanceof FunctionEnvironmentRecord); + // 8. Perform ? thisER.BindThisValue(result). + Q(thisER.BindThisValue(result)); + // 9. Let F be thisER.[[FunctionObject]]. + const F = thisER.FunctionObject; + // 10. Assert: F is an ECMAScript function object. + Assert(isECMAScriptFunctionObject(F)); + // 11. Perform ? InitializeInstanceElements(result, F). + Q(yield* InitializeInstanceElements(result, F)); + // 12. Return result. + return result; +} + +/** https://tc39.es/ecma262/#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/src/runtime-semantics/SuperProperty.mts b/src/runtime-semantics/SuperProperty.mts new file mode 100644 index 0000000..8beb018 --- /dev/null +++ b/src/runtime-semantics/SuperProperty.mts @@ -0,0 +1,58 @@ +import { Evaluate, type ExpressionEvaluator } from '../evaluator.mts'; +import { + ReferenceRecord, Value, +} from '../value.mts'; +import { StringValue } from '../static-semantics/all.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + GetThisEnvironment, + GetValue, + FunctionEnvironmentRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-makesuperpropertyreference */ +function MakeSuperPropertyReference(actualThis: Value, propertyKey: Value, strict: boolean) { + // 1. Let env be GetThisEnvironment(). + const env = GetThisEnvironment(); + // 2. Assert: env.HasSuperBinding() is true. + Assert(env.HasSuperBinding() === Value.true); + // 3. Assert: env is a Function Environment Record. + Assert(env instanceof FunctionEnvironmentRecord); + // 4. Let baseValue be ? env.GetSuperBase(). + const baseValue = Q(env.GetSuperBase()); + // 5. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. + return new ReferenceRecord({ + Base: baseValue, + ReferencedName: propertyKey, + Strict: strict ? Value.true : Value.false, + ThisValue: actualThis, + }); +} + +/** https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation */ +// SuperProperty : +// `super` `[` Expression `]` +// `super` `.` IdentifierName +export function* Evaluate_SuperProperty({ Expression, IdentifierName, strict }: ParseNode.SuperProperty): ExpressionEvaluator { + // 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 = Q(yield* Evaluate(Expression)); + // 4. Let propertyNameReference be the result of evaluating Expression. + const propertyNameValue = Q(yield* GetValue(propertyNameReference)); + // 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, propertyNameValue, 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/src/runtime-semantics/SwitchStatement.mts b/src/runtime-semantics/SwitchStatement.mts new file mode 100644 index 0000000..e634a1d --- /dev/null +++ b/src/runtime-semantics/SwitchStatement.mts @@ -0,0 +1,222 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Evaluate, type StatementEvaluator, type ValueEvaluator } from '../evaluator.mts'; +import { + BooleanValue, ReferenceRecord, Value, +} from '../value.mts'; +import { + Completion, + AbruptCompletion, + NormalCompletion, + EnsureCompletion, + UpdateEmpty, + Q, +} from '../completion.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + BlockDeclarationInstantiation, + Evaluate_StatementList, +} from './all.mts'; +import { + Assert, GetValue, IsStrictlyEqual, DeclarativeEnvironmentRecord, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-runtime-semantics-caseclauseisselected */ +function* CaseClauseIsSelected(C: ParseNode.CaseClause, input: Value): ValueEvaluator { + // 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 = Q(yield* Evaluate(C.Expression)); + // 3. Let clauseSelector be ? GetValue(exprRef). + const clauseSelector = Q(yield* GetValue(exprRef)); + // 4. Return the result of performing Strict Equality Comparison input === clauseSelector. + return IsStrictlyEqual(input, clauseSelector); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-caseblockevaluation */ +// CaseBlock : +// `{` `}` +// `{` CaseClauses `}` +// `{` CaseClauses? DefaultClause CaseClauses? `}` +function* CaseBlockEvaluation({ CaseClauses_a, DefaultClause, CaseClauses_b }: ParseNode.CaseBlock, input: Value): StatementEvaluator { + 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 = 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: BooleanValue = 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 | ReferenceRecord = 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: BooleanValue = 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: BooleanValue = 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 as Value); + } + // 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 as Value); + } + default: + throw new OutOfRange('CaseBlockEvaluation', ''); + } +} + +/** https://tc39.es/ecma262/#sec-switch-statement-runtime-semantics-evaluation */ +// SwitchStatement : +// `switch` `(` Expression `)` CaseBlock +export function* Evaluate_SwitchStatement({ Expression, CaseBlock }: ParseNode.SwitchStatement): StatementEvaluator { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = Q(yield* Evaluate(Expression)); + // 2. Let switchValue be ? GetValue(exprRef). + const switchValue = Q(yield* 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 = new DeclarativeEnvironmentRecord(oldEnv); + // 5. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv). + yield* 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; +} + +/** https://tc39.es/ecma262/#sec-switch-statement-runtime-semantics-evaluation */ +// CaseClause : +// `case` Expression `:` +// `case` Expression `:` StatementList +// DefaultClause : +// `case` `default` `:` +// `case` `default` `:` StatementList +export function* Evaluate_CaseClause({ StatementList }: ParseNode.CaseClause | ParseNode.DefaultClause) { + if (!StatementList) { + // 1. Return NormalCompletion(empty). + return NormalCompletion(undefined); + } + // 1. Return the result of evaluating StatementList. + return yield* Evaluate_StatementList(StatementList); +} diff --git a/src/runtime-semantics/TaggedTemplateExpression.mts b/src/runtime-semantics/TaggedTemplateExpression.mts new file mode 100644 index 0000000..2dc8760 --- /dev/null +++ b/src/runtime-semantics/TaggedTemplateExpression.mts @@ -0,0 +1,23 @@ +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { IsInTailPosition } from '../static-semantics/all.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { EvaluateCall } from './all.mts'; +import { GetValue } from '#self'; + +/** https://tc39.es/ecma262/#sec-tagged-templates-runtime-semantics-evaluation */ +// MemberExpression : +// MemberExpression TemplateLiteral +export function* Evaluate_TaggedTemplateExpression(node: ParseNode.TaggedTemplateExpression): ValueEvaluator { + const { MemberExpression, TemplateLiteral } = node; + // 1. Let tagRef be ? Evaluation of MemberExpression. + const tagRef = Q(yield* Evaluate(MemberExpression)); + // 1. Let tagFunc be ? GetValue(tagRef). + const tagFunc = Q(yield* 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/src/runtime-semantics/TemplateLiteral.mts b/src/runtime-semantics/TemplateLiteral.mts new file mode 100644 index 0000000..ecf17b9 --- /dev/null +++ b/src/runtime-semantics/TemplateLiteral.mts @@ -0,0 +1,34 @@ +import { Value } from '../value.mts'; +import { Q } from '../completion.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { TV } from '../static-semantics/all.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { GetValue, ToString } from '#self'; + +/** https://tc39.es/ecma262/#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 }: ParseNode.TemplateLiteral): ValueEvaluator { + 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 = Q(yield* Evaluate(Expression)); + // 3. Let sub be ? GetValue(subRef). + const sub = Q(yield* GetValue(subRef)); + // 4. Let middle be ? ToString(sub). + const middle = Q(yield* ToString(sub)); + str += head; + str += middle.stringValue(); + } + const tail = TV(TemplateSpanList[TemplateSpanList.length - 1]); + return Value(str + tail); +} diff --git a/src/runtime-semantics/This.mts b/src/runtime-semantics/This.mts new file mode 100644 index 0000000..486cf82 --- /dev/null +++ b/src/runtime-semantics/This.mts @@ -0,0 +1,9 @@ +import { Q, type ValueCompletion } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ResolveThisBinding } from '#self'; + +/** https://tc39.es/ecma262/#sec-this-keyword-runtime-semantics-evaluation */ +// PrimaryExpression : `this` +export function Evaluate_This(_PrimaryExpression: ParseNode.ThisExpression): ValueCompletion { + return Q(ResolveThisBinding()); +} diff --git a/src/runtime-semantics/ThrowStatement.mts b/src/runtime-semantics/ThrowStatement.mts new file mode 100644 index 0000000..beb7ece --- /dev/null +++ b/src/runtime-semantics/ThrowStatement.mts @@ -0,0 +1,22 @@ +import { + Evaluate, +} from '../evaluator.mts'; +import { + Q, + ThrowCompletion, +} from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + GetValue, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-throw-statement-runtime-semantics-evaluation */ +// ThrowStatement : `throw` Expression `;` +export function* Evaluate_ThrowStatement({ Expression }: ParseNode.ThrowStatement) { + // 1. Let exprRef be the result of evaluating Expression. + const exprRef = Q(yield* Evaluate(Expression)); + // 2. Let exprValue be ? GetValue(exprRef). + const exprValue = Q(yield* GetValue(exprRef)); + // 3. Return ThrowCompletion(exprValue). + return ThrowCompletion(exprValue); +} diff --git a/src/runtime-semantics/TrimString.mts b/src/runtime-semantics/TrimString.mts new file mode 100644 index 0000000..49a3076 --- /dev/null +++ b/src/runtime-semantics/TrimString.mts @@ -0,0 +1,19 @@ +import { JSStringValue, Value } from '../value.mts'; +import { Q, type ValueEvaluator } from '../completion.mts'; +import { Assert, RequireObjectCoercible, ToString } from '#self'; + +/** https://tc39.es/ecma262/#sec-trimstring */ +export function* TrimString(string: Value, where: 'start' | 'end' | 'start+end'): ValueEvaluator { + Q(RequireObjectCoercible(string)); + const S = Q(yield* ToString(string)).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 Value(T); +} diff --git a/src/runtime-semantics/TryStatement.mts b/src/runtime-semantics/TryStatement.mts new file mode 100644 index 0000000..3b34c5d --- /dev/null +++ b/src/runtime-semantics/TryStatement.mts @@ -0,0 +1,120 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { Evaluate, type StatementEvaluator } from '../evaluator.mts'; +import { + Completion, + AbruptCompletion, + UpdateEmpty, + EnsureCompletion, + X, +} from '../completion.mts'; +import { BoundNames } from '../static-semantics/all.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { BindingInitialization } from './all.mts'; +import { DeclarativeEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-try-statement-runtime-semantics-evaluation */ +// TryStatement : +// `try` Block Catch +// `try` Block Finally +// `try` Block Catch Finally +export function Evaluate_TryStatement(TryStatement: ParseNode.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 }: ParseNode.TryStatement) { + // 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 }: ParseNode.TryStatement) { + // 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 }: ParseNode.TryStatement) { + // 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: Completion; + 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)); +} + +/** https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation */ +// Catch : +// `catch` Block +// `catch` `(` CatchParameter `)` Block +function* CatchClauseEvaluation({ CatchParameter, Block }: ParseNode.Catch, thrownValue: Value): StatementEvaluator { + 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 = new DeclarativeEnvironmentRecord(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/src/runtime-semantics/UnaryExpression.mts b/src/runtime-semantics/UnaryExpression.mts new file mode 100644 index 0000000..15f7ea0 --- /dev/null +++ b/src/runtime-semantics/UnaryExpression.mts @@ -0,0 +1,218 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { Q } from '../completion.mts'; +import { + Value, ReferenceRecord, UndefinedValue, BigIntValue, BooleanValue, JSStringValue, NullValue, NumberValue, ObjectValue, SymbolValue, +} from '../value.mts'; +import { __ts_cast__, OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + GetValue, + IsCallable, + IsPropertyReference, + IsSuperReference, + IsUnresolvableReference, + ToBoolean, + ToNumber, + ToObject, + ToNumeric, + type PropertyReference, + IsPropertyKey, + IsPrivateReference, + ToPropertyKey, +} from '#self'; +import { EnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation */ +// UnaryExpression : `delete` UnaryExpression +function* Evaluate_UnaryExpression_Delete({ UnaryExpression }: ParseNode.UnaryExpression) { + // 1. Let ref be the result of evaluating UnaryExpression. + const ref = Q(yield* Evaluate(UnaryExpression)); + Q(ref); + // 3. If ref is not a Reference Record, return true. + if (!(ref instanceof ReferenceRecord)) { + return Value.true; + } + // 4. If IsUnresolvableReference(ref) is true, then + if (IsUnresolvableReference(ref) === Value.true) { + // a. Assert: ref.[[Strict]] is false. + Assert(ref.Strict === Value.false); + // b. Return true. + return Value.true; + } + // 5. If IsPropertyReference(ref) is true, then + if (IsPropertyReference(ref) === Value.true) { + __ts_cast__(ref); + // a. Assert: IsPrivateReference(ref) is false. + Assert(!IsPrivateReference(ref)); + // b. If IsSuperReference(ref) is true, throw a ReferenceError exception. + if (IsSuperReference(ref) === Value.true) { + return surroundingAgent.Throw('ReferenceError', 'CannotDeleteSuper'); + } + // c. Let baseObj be ? ToObject(ref.[[Base]]). + const baseObj = Q(ToObject(ref.Base as Value)); + // d. If ref.[[ReferencedName]] is not a property key, then + if (!IsPropertyKey(ref.ReferencedName)) { + // Set ref.[[ReferencedName]] to ? ToPropertyKey(ref.[[ReferencedName]]). + ref.ReferencedName = Q(yield* ToPropertyKey(ref.ReferencedName as Value)); + } + // e. Let deleteStatus be ? baseObj.[[Delete]](ref.[[ReferencedName]]). + const deleteStatus = Q(yield* baseObj.Delete(ref.ReferencedName as JSStringValue)); + // f. If deleteStatus is false and ref.[[Strict]] is true, throw a TypeError exception. + if (deleteStatus === Value.false && ref.Strict === Value.true) { + return surroundingAgent.Throw('TypeError', 'StrictModeDelete', ref.ReferencedName); + } + // g. Return deleteStatus. + return deleteStatus; + } else { // 6. Else, + // a. Let base be ref.[[Base]]. + const base = ref.Base; + // b. Assert: base is an Environment Record. + Assert(base instanceof EnvironmentRecord); + // c. Return ? bindings.DeleteBinding(GetReferencedName(ref)). + return Q(yield* base.DeleteBinding(ref.ReferencedName as JSStringValue)); + } +} + +/** https://tc39.es/ecma262/#sec-void-operator-runtime-semantics-evaluation */ +// UnaryExpression : `void` UnaryExpression +function* Evaluate_UnaryExpression_Void({ UnaryExpression }: ParseNode.UnaryExpression): ValueEvaluator { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Perform ? GetValue(expr). + Q(yield* GetValue(expr)); + // 3. Return undefined. + return Value.undefined; +} + +/** https://tc39.es/ecma262/#sec-typeof-operator-runtime-semantics-evaluation */ +// UnaryExpression : `typeof` UnaryExpression +function* Evaluate_UnaryExpression_Typeof({ UnaryExpression }: ParseNode.UnaryExpression): ValueEvaluator { + // 1. Let val be the result of evaluating UnaryExpression. + const _val = Q(yield* Evaluate(UnaryExpression)); + // 2. If Type(val) is Reference, then + if (_val instanceof ReferenceRecord) { + // a. If IsUnresolvableReference(val) is true, return "undefined". + if (IsUnresolvableReference(_val) === Value.true) { + return Value('undefined'); + } + } + // 3. Set val to ? GetValue(val). + const val = Q(yield* GetValue(_val)); + // 4. Return a String according to Table 37. + if (val instanceof UndefinedValue) { + return Value('undefined'); + } else if (val instanceof NullValue) { + return Value('object'); + } else if (val instanceof BooleanValue) { + return Value('boolean'); + } else if (val instanceof NumberValue) { + return Value('number'); + } else if (val instanceof JSStringValue) { + return Value('string'); + } else if (val instanceof BigIntValue) { + return Value('bigint'); + } else if (val instanceof SymbolValue) { + return Value('symbol'); + } else if (val instanceof ObjectValue) { + if (IsCallable(val)) { + return Value('function'); + } + return Value('object'); + } + throw new OutOfRange('Evaluate_UnaryExpression_Typeof', val); +} + +/** https://tc39.es/ecma262/#sec-unary-plus-operator-runtime-semantics-evaluation */ +// UnaryExpression : `+` UnaryExpression +function* Evaluate_UnaryExpression_Plus({ UnaryExpression }: ParseNode.UnaryExpression): ValueEvaluator { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Return ? ToNumber(? GetValue(expr)). + return Q(yield* ToNumber(Q(yield* GetValue(expr)))); +} + +/** https://tc39.es/ecma262/#sec-unary-minus-operator-runtime-semantics-evaluation */ +// UnaryExpression : `-` UnaryExpression +function* Evaluate_UnaryExpression_Minus({ UnaryExpression }: ParseNode.UnaryExpression): ValueEvaluator { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(yield* ToNumeric(Q(yield* GetValue(expr)))); + // 3. If oldValue is a Number, then + if (oldValue instanceof NumberValue) { + // a. Return Number::unaryMinus(oldValue). + return NumberValue.unaryMinus(oldValue); + } else { + // a. Assert: oldValue is a BigInt. + // b. Return BigInt::unaryMinus(oldValue). + Assert(oldValue instanceof BigIntValue); + return BigIntValue.unaryMinus(oldValue); + } +} + +/** https://tc39.es/ecma262/#sec-bitwise-not-operator-runtime-semantics-evaluation */ +// UnaryExpression : `~` UnaryExpression +function* Evaluate_UnaryExpression_Tilde({ UnaryExpression }: ParseNode.UnaryExpression): ValueEvaluator { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(yield* ToNumeric(Q(yield* GetValue(expr)))); + // 3. If oldValue is a Number, then + if (oldValue instanceof NumberValue) { + // a. Return Number::bitwiseNOT(oldValue). + return NumberValue.bitwiseNOT(oldValue); + } else { + // a. Assert: oldValue is a BigInt. + // b. Return BigInt::bitwiseNOT(oldValue). + Assert(oldValue instanceof BigIntValue); + return BigIntValue.bitwiseNOT(oldValue); + } +} + +/** https://tc39.es/ecma262/#sec-logical-not-operator-runtime-semantics-evaluation */ +// UnaryExpression : `!` UnaryExpression +function* Evaluate_UnaryExpression_Bang({ UnaryExpression }: ParseNode.UnaryExpression): ValueEvaluator { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Let oldValue be ! ToBoolean(? GetValue(expr)). + const oldValue = ToBoolean(Q(yield* 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: ParseNode.UnaryExpression) { + switch (UnaryExpression.operator) { + case 'delete': + Q(surroundingAgent.debugger_cannotPreview); + 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/src/runtime-semantics/Unicode.mts b/src/runtime-semantics/Unicode.mts new file mode 100644 index 0000000..9ba0c69 --- /dev/null +++ b/src/runtime-semantics/Unicode.mts @@ -0,0 +1,252 @@ +import unicodeCaseFoldingCommon from '@unicode/unicode-16.0.0/Case_Folding/C/symbols.js'; +import unicodeCaseFoldingSimple from '@unicode/unicode-16.0.0/Case_Folding/S/symbols.js'; +// @ts-ignore +import UnicodeSets from '../unicode/CodePointProperties.json' with { type: 'json' }; +// @ts-ignore +import SequenceProperties from '../unicode/SequenceProperties.json' with { type: 'json' }; +import { Assert, Canonicalize, type RegExpRecord } from '#self'; + +export const isLeadingSurrogate = (cp: number) => cp >= 0xD800 && cp <= 0xDBFF; +export const isTrailingSurrogate = (cp: number) => cp >= 0xDC00 && cp <= 0xDFFF; +/** https://tc39.es/ecma262/#table-nonbinary-unicode-properties */ +export const Table69_NonbinaryUnicodeProperties = { + General_Category: 'General_Category', + gc: 'General_Category', + Script: 'Script', + sc: 'Script', + Script_Extensions: 'Script_Extensions', + scx: 'Script_Extensions', +} as const; +Object.setPrototypeOf(Table69_NonbinaryUnicodeProperties, null); +export type Table69_NonbinaryUnicodePropertiesCanonicalized = typeof Table69_NonbinaryUnicodeProperties[keyof typeof Table69_NonbinaryUnicodeProperties]; + +/** https://tc39.es/ecma262/#table-binary-unicode-properties */ +export const Table70_BinaryUnicodeProperties = { + 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', +} as const; +Object.setPrototypeOf(Table70_BinaryUnicodeProperties, null); + +/** https://tc39.es/ecma262/#table-binary-unicode-properties-of-strings */ +export const Table71_BinaryPropertyOfStrings = { + Basic_Emoji: 'Basic_Emoji', + Emoji_Keycap_Sequence: 'Emoji_Keycap_Sequence', + RGI_Emoji_Modifier_Sequence: 'RGI_Emoji_Modifier_Sequence', + RGI_Emoji_Flag_Sequence: 'RGI_Emoji_Flag_Sequence', + RGI_Emoji_Tag_Sequence: 'RGI_Emoji_Tag_Sequence', + RGI_Emoji_ZWJ_Sequence: 'RGI_Emoji_ZWJ_Sequence', + RGI_Emoji: 'RGI_Emoji', +} as const; +Object.setPrototypeOf(Table71_BinaryPropertyOfStrings, null); + +const canonicalizeUnicodePropertyCache: Record, includeSet: ReadonlySet]> = { __proto__: null! }; +const stringPropertySetCache: Record = {}; +export const Unicode = { + toUppercase(ch: CodePoint): CodePoint { + return String.fromCodePoint(ch).toUpperCase().codePointAt(0)! as CodePoint; + }, + toCodePoint(ch: Character): CodePoint { + return ch.codePointAt(0)! as CodePoint; + }, + toCharacter(ch: CodePoint): UnicodeCharacter { + return String.fromCodePoint(ch) as UnicodeCharacter; + }, + isCharacter(ch: Character | ListOfCharacter): ch is Character { + return ch.length === 1 || [...ch].length === 1; + }, + toCodeUnit(ch: Character): [CodeUnit, CodeUnit?] { + const codePoint = ch.charCodeAt(0)!; + const codePoint2 = ch.charCodeAt(1); + return [codePoint as CodeUnit, Number.isNaN(codePoint2) ? codePoint2 as CodeUnit : undefined]; + }, + iterateByCodePoint(x: string): UnicodeCharacter[] { + return Array.from(x) as UnicodeCharacter[]; + }, + characterMatchPropertyValue(ch: Character | ListOfCharacter, property: Table69_NonbinaryUnicodePropertiesCanonicalized, value: string | undefined, rer: RegExpRecord | undefined) { + if (!Unicode.isCharacter(ch)) { + return false; + } + let path = value ? `${property}/${value}` : `Binary_Property/${property}`; + const cp = ch.codePointAt(0)!; + // https://www.unicode.org/reports/tr24/#Script_Values + // Unknown is: Unused, private use or surrogate code points. + if ((property === 'Script' || property === 'Script_Extensions') && (value === 'Unknown' || value === 'Zzzz')) { + // https://www.unicode.org/faq/private_use.html + if ((cp >= 0xE000 && cp <= 0xF8FF) || (cp >= 0xF0000 && cp <= 0xFFFFD) || (cp >= 0x100000 && cp <= 0x10FFFD)) { + return true; + } + // Non characters + if ((cp >= 0xFDD0 && cp <= 0xFDEF) || cp === 0xFFFE || cp === 0xFFFF || cp.toString(16).match(/^(?:[0-9a-f]|10)fff[fe]$/i)) { + return true; + } + if (isLeadingSurrogate(cp) || isTrailingSurrogate(cp)) { + return true; + } + path = 'General_Category/Unassigned'; + } + if (!(path in UnicodeSets)) { + throw new Assert.Error(`Unicode property "${path}" not found in UnicodeSets.`); + } + if (rer) { + const cacheKey = JSON.stringify([rer, path]); + if (!canonicalizeUnicodePropertyCache[cacheKey]) { + const excludeSet = new Set(); + const includeSet = new Set(); + for (const [from, to] of (UnicodeSets as Record)[path]) { + for (let index = from; index <= to; index += 1) { + const char = String.fromCodePoint(index) as UnicodeCharacter; + const ch2 = Canonicalize(rer, char) as UnicodeCharacter; + if (char !== ch2) { + excludeSet.add(char); + includeSet.add(ch2); + } + } + } + canonicalizeUnicodePropertyCache[cacheKey] = [excludeSet, includeSet]; + } + const [excludeSet, includeSet] = canonicalizeUnicodePropertyCache[cacheKey]; + if (excludeSet.has(ch)) { + return false; + } + if (includeSet.has(ch)) { + return true; + } + } + return !!(UnicodeSets as Record)[path].find(([from, to]) => from <= cp && cp <= to); + }, + getStringPropertySet(property: keyof typeof Table71_BinaryPropertyOfStrings) { + stringPropertySetCache[property] ??= SequenceProperties[property].split(',') as ListOfCharacter[]; + return stringPropertySetCache[property]; + }, + + /** https://www.unicode.org/reports/tr44/#Simple_Case_Folding */ + // TODO: scf() in spec means Simple Case Folding or Simple + Common Case Folding? + // https://github.com/tc39/ecma262/issues/3594 + // SimpleCaseFoldingMapping(ch: Character): Character { + // // Note: The case foldings are omitted in the data file if they are the same as the code point itself. + // return (unicodeCaseFoldingSimple.get(ch) || ch) as Character; + // }, + SimpleOrCommonCaseFoldingMapping(ch: Character): Character | undefined { + if (unicodeCaseFoldingCommon.has(ch)) { + return unicodeCaseFoldingCommon.get(ch)! as Character; + } + if (unicodeCaseFoldingSimple.has(ch)) { + return unicodeCaseFoldingSimple.get(ch)! as Character; + } + return ch; + }, + iterateCharacterByCodePoint(string: Character | ListOfCharacter) { + return string[Symbol.iterator]() as IterableIterator; + }, +}; + +/** https://tc39.es/ecma262/#sec-pattern-semantics */ +export type BMPCharacter = string & { description: 'A code unit', length: 1 }; +/** https://tc39.es/ecma262/#sec-pattern-semantics */ +export type UnicodeCharacter = string & { description: 'A code point', length: 1 | 2 }; +/** https://tc39.es/ecma262/#sec-pattern-semantics */ +export type Character = BMPCharacter | UnicodeCharacter; +/* List of BMPCharacter (non Unicode mode) or list of CodePoint. */ +export type ListOfCharacter = string & { __brand__: 'ListOfCharacter' }; + +/** https://developer.mozilla.org/en-US/docs/Glossary/Code_point */ +export type CodePoint = number & { __brand__: 'CodePoint' }; + +/** https://developer.mozilla.org/en-US/docs/Glossary/Code_unit */ +export type CodeUnit = number & { __brand__: 'CodeUnit' }; diff --git a/src/runtime-semantics/UpdateExpression.mts b/src/runtime-semantics/UpdateExpression.mts new file mode 100644 index 0000000..5ee3971 --- /dev/null +++ b/src/runtime-semantics/UpdateExpression.mts @@ -0,0 +1,126 @@ +import { Evaluate, type ValueEvaluator } from '../evaluator.mts'; +import { OutOfRange } from '../helpers.mts'; +import { BigIntValue, NumberValue } from '../value.mts'; +import { Q } from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + F, + GetValue, + PutValue, + ToNumeric, + Z, +} from '#self'; + +type AnyNumericValue = BigIntValue | NumberValue; +// UpdateExpression : +// LeftHandSideExpression `++` +// LeftHandSideExpression `--` +// `++` UnaryExpression +// `--` UnaryExpression +export function* Evaluate_UpdateExpression({ LeftHandSideExpression, operator, UnaryExpression }: ParseNode.UpdateExpression): ValueEvaluator { + switch (true) { + // UpdateExpression : LeftHandSideExpression `++` + // https://tc39.es/ecma262/#sec-postfix-increment-operator-runtime-semantics-evaluation + case operator === '++' && !!LeftHandSideExpression: { + // 1. Let lhs be the result of evaluating LeftHandSideExpression. + const lhs = Q(yield* Evaluate(LeftHandSideExpression)); + // 2. Let oldValue be ? ToNumeric(? GetValue(lhs)). + const oldValue = Q(yield* ToNumeric(Q(yield* GetValue(lhs)))); + // 3. If oldValue is a Number, then + // a. Let newValue be Number::add(oldValue, 1𝔽). + // 4. Else, + // a. Assert: oldValue is a BigInt. + // b. Let newValue be BigInt::add(oldValue, 1ℤ). + let newValue: AnyNumericValue; + if (oldValue instanceof NumberValue) { + newValue = NumberValue.add(oldValue, F(1)); + } else { + Assert(oldValue instanceof BigIntValue); + newValue = BigIntValue.add(oldValue, Z(1n)); + } + // 4. Perform ? PutValue(lhs, newValue). + Q(yield* PutValue(lhs, newValue)); + // 5. Return oldValue. + return oldValue; + } + + // UpdateExpression : LeftHandSideExpression `--` + // https://tc39.es/ecma262/#sec-postfix-decrement-operator-runtime-semantics-evaluation + case operator === '--' && !!LeftHandSideExpression: { + // 1. Let lhs be the result of evaluating LeftHandSideExpression. + const lhs = Q(yield* Evaluate(LeftHandSideExpression)); + // 2. Let oldValue be ? ToNumeric(? GetValue(lhs)). + const oldValue = Q(yield* ToNumeric(Q(yield* GetValue(lhs)))); + // 3. If oldValue is a Number, then + // a. Let newValue be Number::subtract(oldValue, 1𝔽). + // 4. Else, + // a. Assert: oldValue is a BigInt. + // b. Let newValue be BigInt::subtract(oldValue, 1ℤ). + let newValue: AnyNumericValue; + if (oldValue instanceof NumberValue) { + newValue = NumberValue.subtract(oldValue, F(1)); + } else { + Assert(oldValue instanceof BigIntValue); + newValue = BigIntValue.subtract(oldValue, Z(1n)); + } + // 4. Perform ? PutValue(lhs, newValue). + Q(yield* PutValue(lhs, newValue)); + // 5. Return oldValue. + return oldValue; + } + + // UpdateExpression : `++` UnaryExpression + // https://tc39.es/ecma262/#sec-prefix-increment-operator-runtime-semantics-evaluation + case operator === '++' && !!UnaryExpression: { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(yield* ToNumeric(Q(yield* GetValue(expr)))); + // 3. If oldValue is a Number, then + // a. Let newValue be Number::add(oldValue, 1𝔽). + // 4. Else, + // a. Assert: oldValue is a BigInt. + // b. Let newValue be BigInt::add(oldValue, 1ℤ). + let newValue: AnyNumericValue; + if (oldValue instanceof NumberValue) { + newValue = NumberValue.add(oldValue, F(1)); + } else { + Assert(oldValue instanceof BigIntValue); + newValue = BigIntValue.add(oldValue, Z(1n)); + } + // 4. Perform ? PutValue(expr, newValue). + Q(yield* PutValue(expr, newValue)); + // 5. Return newValue. + return newValue; + } + + // UpdateExpression : `--` UnaryExpression + // https://tc39.es/ecma262/#sec-prefix-decrement-operator-runtime-semantics-evaluation + case operator === '--' && !!UnaryExpression: { + // 1. Let expr be the result of evaluating UnaryExpression. + const expr = Q(yield* Evaluate(UnaryExpression)); + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + const oldValue = Q(yield* ToNumeric(Q(yield* GetValue(expr)))); + // 3. If oldValue is a Number, then + // a. Let newValue be Number::subtract(oldValue, 1𝔽). + // 4. Else, + // a. Assert: oldValue is a BigInt. + // b. Let newValue be BigInt::subtract(oldValue, 1ℤ). + let newValue: AnyNumericValue; + if (oldValue instanceof NumberValue) { + newValue = NumberValue.subtract(oldValue, F(1)); + } else { + Assert(oldValue instanceof BigIntValue); + newValue = BigIntValue.subtract(oldValue, Z(1n)); + } + // 4. Perform ? PutValue(expr, newValue). + Q(yield* PutValue(expr, newValue)); + // 5. Return newValue. + return newValue; + } + + default: + throw new OutOfRange('Evaluate_UpdateExpression', operator); + } +} diff --git a/src/runtime-semantics/VariableStatement.mts b/src/runtime-semantics/VariableStatement.mts new file mode 100644 index 0000000..4cfe3f0 --- /dev/null +++ b/src/runtime-semantics/VariableStatement.mts @@ -0,0 +1,72 @@ +import { + NormalCompletion, Q, +} from '../completion.mts'; +import { Evaluate, type PlainEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { StringValue, IsAnonymousFunctionDefinition, type FunctionDeclaration } from '../static-semantics/all.mts'; +import { Value } from '../value.mts'; +import { NamedEvaluation, BindingInitialization } from './all.mts'; +import { + GetValue, + PutValue, + ResolveBinding, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation */ +// VariableDeclaration : +// BindingIdentifier +// BindingIdentifier Initializer +// BindingPattern Initializer +function* Evaluate_VariableDeclaration({ BindingIdentifier, Initializer, BindingPattern }: ParseNode.VariableDeclaration): PlainEvaluator { + 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(yield* 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 = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, bindingId)); + } else { // 4. Else, + // a. Let rhs be the result of evaluating Initializer. + const rhs = Q(yield* Evaluate(Initializer)); + // b. Let value be ? GetValue(rhs). + value = Q(yield* GetValue(rhs)); + } + // 5. Return ? PutValue(lhs, value). + return Q(yield* PutValue(lhs, value)); + } + // 1. Let rhs be the result of evaluating Initializer. + const rhs = Q(yield* Evaluate(Initializer!)); + // 2. Let rval be ? GetValue(rhs). + const rval = Q(yield* GetValue(rhs)); + // 3. Return the result of performing BindingInitialization for BindingPattern passing rval and undefined as arguments. + return yield* BindingInitialization(BindingPattern!, rval, Value.undefined); +} + +/** https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation */ +// VariableDeclarationList : VariableDeclarationList `,` VariableDeclaration +// +// (implicit) +// VariableDeclarationList : VariableDeclaration +export function* Evaluate_VariableDeclarationList(VariableDeclarationList: ParseNode.VariableDeclarationList) { + let next; + for (const VariableDeclaration of VariableDeclarationList) { + next = yield* Evaluate_VariableDeclaration(VariableDeclaration); + Q(next); + } + return next; +} + +/** https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation */ +// VariableStatement : `var` VariableDeclarationList `;` +export function* Evaluate_VariableStatement({ VariableDeclarationList }: ParseNode.VariableStatement): PlainEvaluator { + const next = yield* Evaluate_VariableDeclarationList(VariableDeclarationList); + Q(next); + return NormalCompletion(undefined); +} diff --git a/src/runtime-semantics/WithStatement.mts b/src/runtime-semantics/WithStatement.mts new file mode 100644 index 0000000..24ee84b --- /dev/null +++ b/src/runtime-semantics/WithStatement.mts @@ -0,0 +1,32 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { Value } from '../value.mts'; +import { Evaluate } from '../evaluator.mts'; +import { + UpdateEmpty, + Completion, + EnsureCompletion, + Q, +} from '../completion.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { ToObject, GetValue, ObjectEnvironmentRecord } from '#self'; + +/** https://tc39.es/ecma262/#sec-with-statement-runtime-semantics-evaluation */ +// WithStatement : `with` `(` Expression `)` Statement +export function* Evaluate_WithStatement({ Expression, Statement }: ParseNode.WithStatement) { + // 1. Let val be the result of evaluating Expression. + const val = Q(yield* Evaluate(Expression)); + // 2. Let obj be ? ToObject(? GetValue(val)). + const obj = Q(ToObject(Q(yield* GetValue(val)))); + // 3. Let oldEnv be the running execution context's LexicalEnvironment. + const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment; + // 4. Let newEnv be NewObjectEnvironment(obj, true, oldEnv). + const newEnv = new ObjectEnvironmentRecord(obj, Value.true, oldEnv); + // 5. Set the running execution context's LexicalEnvironment to newEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = newEnv; + // 6. Let C be the result of evaluating Statement. + const C = EnsureCompletion(yield* Evaluate(Statement)); + // 7. Set the running execution context's LexicalEnvironment to oldEnv. + surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv; + // 8. Return Completion(UpdateEmpty(C, undefined)). + return Completion(UpdateEmpty(C, Value.undefined)); +} diff --git a/src/runtime-semantics/YieldExpression.mts b/src/runtime-semantics/YieldExpression.mts new file mode 100644 index 0000000..15f6e18 --- /dev/null +++ b/src/runtime-semantics/YieldExpression.mts @@ -0,0 +1,174 @@ +import { surroundingAgent } from '../host-defined/engine.mts'; +import { ObjectValue, Value } from '../value.mts'; +import { + Await, + NormalCompletion, + Q, + ReturnCompletion, + ThrowCompletion, + type ValueCompletion, +} from '../completion.mts'; +import { Evaluate, type YieldEvaluator } from '../evaluator.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + Assert, + Call, + GeneratorYield, + GetGeneratorKind, + GetIterator, + GetMethod, + GetValue, + IteratorClose, + IteratorComplete, + IteratorValue, + AsyncGeneratorYield, + AsyncIteratorClose, + Yield, +} from '#self'; + +/** https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation */ +// YieldExpression : +// `yield` +// `yield` AssignmentExpression +// `yield` `*` AssignmentExpression +export function* Evaluate_YieldExpression({ hasStar, AssignmentExpression }: ParseNode.YieldExpression): YieldEvaluator { + if (hasStar) { + // 1. Let generatorKind be GetGeneratorKind(). + const generatorKind = GetGeneratorKind(); + // 2. Assert: generatorKind is either sync or async. + Assert(generatorKind === 'async' || generatorKind === 'sync'); + // 2. Let exprRef be ? Evaluation of AssignmentExpression. + const exprRef = Q(yield* Evaluate(AssignmentExpression!)); + // 3. Let value be ? GetValue(exprRef). + const value = Q(yield* GetValue(exprRef)); + // 4. Let iteratorRecord be ? GetIterator(value, generatorKind). + const iteratorRecord = Q(yield* GetIterator(value, generatorKind)); + // 5. Let iterator be iteratorRecord.[[Iterator]]. + const iterator = iteratorRecord.Iterator; + // 6. Let received be NormalCompletion(undefined). + let received: ValueCompletion | ReturnCompletion = NormalCompletion(Value.undefined); + // 7. Repeat, + while (true) { + // a. If received is a normal completion, then + if (received instanceof NormalCompletion) { + // i. Let innerResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « received.[[Value]] »). + let innerResult: Value = Q(yield* 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 (!(innerResult instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult); + } + // iv. Let done be ? IteratorComplete(innerResult). + const done = Q(yield* IteratorComplete(innerResult)); + // v. If done is true, then + if (done === Value.true) { + // 1. Return ? IteratorValue(innerResult). + return Q(yield* IteratorValue(innerResult)); + } + // vi. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + if (generatorKind === 'async') { + received = yield* AsyncGeneratorYield(Q(yield* IteratorValue(innerResult))); + } else { // vii. Else, set received to GeneratorYield(innerResult). + received = yield* GeneratorYield(innerResult); + } + } else if (received instanceof ThrowCompletion) { // b. Else if received is a throw completion, then + // i. Let throw be ? GetMethod(iterator, "throw"). + const thr = Q(yield* GetMethod(iterator, Value('throw'))); + // ii. If throw is not undefined, then + if (thr !== Value.undefined) { + // 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »). + let innerResult: Value = Q(yield* 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 (!(innerResult instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerResult); + } + // 5. Let done be ? IteratorComplete(innerResult). + const done = Q(yield* IteratorComplete(innerResult)); + // 6. If done is true, then + if (done === Value.true) { + // a. Return ? IteratorValue(innerResult). + return Q(yield* IteratorValue(innerResult)); + } + // 7. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + if (generatorKind === 'async') { + received = yield* AsyncGeneratorYield(Q(yield* 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 NormalCompletion(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(yield* 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 is a return completion. + Assert(received instanceof ReturnCompletion); + // ii. Let return be ? GetMethod(iterator, "return"). + const ret = Q(yield* GetMethod(iterator, Value('return'))); + // iii. If return is undefined, then + if (ret === Value.undefined) { + let receivedValue = received.Value; + // 1. If generatorKind is async, then set receivedValue to ? Await(received.[[Value]]). + if (generatorKind === 'async') { + receivedValue = Q(yield* Await(receivedValue)); + } + // 2. Return ReturnCompletion(receivedValue). + return ReturnCompletion(receivedValue); + } + // iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »). + let innerReturnResult: Value = Q(yield* 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 (!(innerReturnResult instanceof ObjectValue)) { + return surroundingAgent.Throw('TypeError', 'NotAnObject', innerReturnResult); + } + // vii. Let done be ? IteratorComplete(innerReturnResult). + const done = Q(yield* IteratorComplete(innerReturnResult)); + // viii. If done is true, then + if (done === Value.true) { + // 1. Set returnedValue to ? IteratorValue(innerReturnResult). + const returnedValue = Q(yield* IteratorValue(innerReturnResult)); + // 2. Return ReturnCompletion(value). + return ReturnCompletion(returnedValue); + } + // ix. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + if (generatorKind === 'async') { + received = yield* AsyncGeneratorYield(Q(yield* IteratorValue(innerReturnResult))); + } else { // ixx. Else, set received to GeneratorYield(innerResult). + received = yield* GeneratorYield(innerReturnResult); + } + } + } + } + if (AssignmentExpression) { + // 1. Let exprRef be the result of evaluating AssignmentExpression. + const exprRef = Q(yield* Evaluate(AssignmentExpression)); + // 2. Let value be ? GetValue(exprRef). + const value = Q(yield* GetValue(exprRef)); + // 3. Return ? Yield(value). + return Q(yield* Yield(value)); + } + // 1. Return ? Yield(undefined). + return Q(yield* Yield(Value.undefined)); +} diff --git a/src/runtime-semantics/all.mts b/src/runtime-semantics/all.mts new file mode 100644 index 0000000..2cd2a06 --- /dev/null +++ b/src/runtime-semantics/all.mts @@ -0,0 +1,108 @@ +export * from './IdentifierReference.mts'; +export * from './This.mts'; +export * from './Literal.mts'; +export * from './ClassExpression.mts'; +export * from './ClassDefinitionEvaluation.mts'; +export * from './DefineMethod.mts'; +export * from './PropertyName.mts'; +export * from './AdditiveExpression.mts'; +export * from './AssignmentExpression.mts'; +export * from './BitwiseOperators.mts'; +export * from './CoalesceExpression.mts'; +export * from './EmptyStatement.mts'; +export * from './ExponentiationExpression.mts'; +export * from './IfStatement.mts'; +export * from './ImportCall.mts'; +export * from './MultiplicativeExpression.mts'; +export * from './ThrowStatement.mts'; +export * from './UpdateExpression.mts'; +export * from './GlobalDeclarationInstantiation.mts'; +export * from './InstantiateFunctionObject.mts'; +export * from './Script.mts'; +export * from './ScriptBody.mts'; +export * from './StatementList.mts'; +export * from './ExpressionStatement.mts'; +export * from './VariableStatement.mts'; +export * from './FunctionDeclaration.mts'; +export * from './CallExpression.mts'; +export * from './EvaluateCall.mts'; +export * from './ArgumentListEvaluation.mts'; +export * from './EvaluateBody.mts'; +export * from './FunctionDeclarationInstantiation.mts'; +export * from './FunctionStatementList.mts'; +export * from './IteratorBindingInitialization.mts'; +export * from './ReturnStatement.mts'; +export * from './ParenthesizedExpression.mts'; +export * from './MemberExpression.mts'; +export * from './EvaluatePropertyAccess.mts'; +export * from './LexicalDeclaration.mts'; +export * from './ObjectLiteral.mts'; +export * from './PropertyDefinitionEvaluation.mts'; +export * from './FunctionExpression.mts'; +export * from './NamedEvaluation.mts'; +export * from './TryStatement.mts'; +export * from './Block.mts'; +export * from './ArrayLiteral.mts'; +export * from './UnaryExpression.mts'; +export * from './EqualityExpression.mts'; +export * from './LogicalANDExpression.mts'; +export * from './LogicalORExpression.mts'; +export * from './NewExpression.mts'; +export * from './ShiftExpression.mts'; +export * from './SuperCall.mts'; +export * from './SuperProperty.mts'; +export * from './BindingInitialization.mts'; +export * from './AsyncFunctionExpression.mts'; +export * from './RelationalExpression.mts'; +export * from './BreakableStatement.mts'; +export * from './LabelledEvaluation.mts'; +export * from './TemplateLiteral.mts'; +export * from './SwitchStatement.mts'; +export * from './CreateDynamicFunction.mts'; +export * from './GeneratorExpression.mts'; +export * from './ArrowFunction.mts'; +export * from './AsyncArrowFunction.mts'; +export * from './BreakStatement.mts'; +export * from './AsyncGeneratorExpression.mts'; +export * from './HoistableDeclaration.mts'; +export * from './CommaOperator.mts'; +export * from './YieldExpression.mts'; +export * from './StringIndexOf.mts'; +export * from './NumberToBigInt.mts'; +export * from './ConditionalExpression.mts'; +export * from './RegularExpressionLiteral.mts'; +export * from './RegExp.mts'; +export * from './StringPad.mts'; +export * from './TrimString.mts'; +export * from './NewTarget.mts'; +export * from './AwaitExpression.mts'; +export * from './ClassDeclaration.mts'; +export * from './WithStatement.mts'; +export * from './Module.mts'; +export * from './ModuleBody.mts'; +export * from './ImportDeclaration.mts'; +export * from './ExportDeclaration.mts'; +export * from './OptionalExpression.mts'; +export * from './TaggedTemplateExpression.mts'; +export * from './GetSubstitution.mts'; +export * from './ContinueStatement.mts'; +export * from './LabelledStatement.mts'; +export * from './MV.mts'; +export * from './ApplyStringOrNumericBinaryOperator.mts'; +export * from './EvaluateStringOrNumericBinaryExpression.mts'; +export * from './ImportMeta.mts'; +export * from './DebuggerStatement.mts'; +export * from './PropertyBindingInitialization.mts'; +export * from './KeyedBindingInitialization.mts'; +export * from './DestructuringAssignmentEvaluation.mts'; +export * from './RestBindingInitialization.mts'; +export * from './Unicode.mts'; +export * from './MethodDefinitionEvaluation.mts'; +export * from './ClassFieldDefinitionEvaluation.mts'; +export * from './InstantiateOrdinaryFunctionExpression.mts'; +export * from './InstantiateGeneratorFunctionExpression.mts'; +export * from './InstantiateArrowFunctionExpression.mts'; +export * from './InstantiateAsyncArrowFunctionExpression.mts'; +export * from './InstantiateAsyncFunctionExpression.mts'; +export * from './InstantiateAsyncGeneratorFunctionExpression.mts'; +export * from './ClassStaticBlockDefinitionEvaluation.mts'; diff --git a/src/static-semantics/BodyText.mts b/src/static-semantics/BodyText.mts new file mode 100644 index 0000000..477d9d2 --- /dev/null +++ b/src/static-semantics/BodyText.mts @@ -0,0 +1,7 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-bodytext */ +// RegularExpressionLiteral :: `/` RegularExpressionBody `/` RegularExpressionFlags +export function BodyText(RegularExpressionLiteral: ParseNode.RegularExpressionLiteral) { + return RegularExpressionLiteral.RegularExpressionBody; +} diff --git a/src/static-semantics/BoundNames.mts b/src/static-semantics/BoundNames.mts new file mode 100644 index 0000000..973367f --- /dev/null +++ b/src/static-semantics/BoundNames.mts @@ -0,0 +1,101 @@ +import { OutOfRange, isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { JSStringValue, Value } from '../value.mts'; +import { StringValue } from './all.mts'; + +export function BoundNames(node: ParseNode | readonly ParseNode[]): JSStringValue[] { + if (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 [Value('*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 [Value('*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/src/static-semantics/CharacterValue.mts b/src/static-semantics/CharacterValue.mts new file mode 100644 index 0000000..c8ffade --- /dev/null +++ b/src/static-semantics/CharacterValue.mts @@ -0,0 +1,112 @@ +import { OutOfRange, unreachable } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { UTF16SurrogatePairToCodePoint } from './all.mts'; +import { Unicode, type CodePoint } from '#self'; + +export type CharacterValueAcceptNode = + | ParseNode.RegExp.CharacterEscape + | ParseNode.RegExp.RegExpUnicodeEscapeSequence + | ParseNode.RegExp.ClassAtom + | ParseNode.RegExp.ClassEscape + | ParseNode.RegExp.ClassSetCharacter; + +/** https://tc39.es/ecma262/#sec-patterns-static-semantics-character-value */ +export function CharacterValue(node: CharacterValueAcceptNode): CodePoint { + switch (node.type) { + case 'CharacterEscape': + switch (node.production) { + case 'ControlEscape': + switch (node.ControlEscape) { + case 't': + return 0x0009 as CodePoint; + case 'n': + return 0x000A as CodePoint; + case 'v': + return 0x000B as CodePoint; + case 'f': + return 0x000C as CodePoint; + case 'r': + return 0x000D as CodePoint; + default: + unreachable(node.ControlEscape); + } + case 'AsciiLetter': { + // 1. Let ch be the code point matched by ControlLetter. + const ch = node.AsciiLetter; + // 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 as CodePoint; + } + case '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) as CodePoint; + case 'RegExpUnicodeEscapeSequence': + return CharacterValue(node.RegExpUnicodeEscapeSequence); + case '0': + // 1. Return the code point value of U+0000 (NULL). + return 0x0000 as CodePoint; + case '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 as CodePoint; + } + default: + unreachable(node); + } + case 'RegExpUnicodeEscapeSequence': + switch (true) { + case 'Hex4Digits' in node: + return node.Hex4Digits as CodePoint; + case 'CodePoint' in node: + return node.CodePoint as CodePoint; + case 'HexTrailSurrogate' in node: + return UTF16SurrogatePairToCodePoint(node.HexLeadSurrogate!, node.HexTrailSurrogate!); + case 'HexLeadSurrogate' in node: + return node.HexLeadSurrogate as CodePoint; + default: + throw new OutOfRange('CharacterValue', node); + } + case 'ClassAtom': + switch (node.production) { + case '-': + // 1. Return the code point value of U+002D (HYPHEN-MINUS). + return 0x002D as CodePoint; + case 'SourceCharacter': { + // 1. Let ch be the code point matched by SourceCharacter. + const ch = node.SourceCharacter.codePointAt(0)!; + // 2. Return ch. + return ch as CodePoint; + } + case 'ClassEscape': + return CharacterValue(node.ClassEscape); + default: + unreachable(node); + } + case 'ClassEscape': + switch (node.production) { + case 'b': + // 1. Return the code point value of U+0008 (BACKSPACE). + return 0x0008 as CodePoint; + case '-': + // 1. Return the code point value of U+002D (HYPHEN-MINUS). + return 0x002D as CodePoint; + case 'CharacterEscape': + return CharacterValue(node.CharacterEscape); + case 'CharacterClassEscape': + throw new OutOfRange('CharacterValue', node); + default: + unreachable(node); + } + case 'ClassSetCharacter': { + if (node.production === 'CharacterEscape') { + return CharacterValue(node.CharacterEscape); + } else { + return Unicode.toCodePoint(node.UnicodeCharacter); + } + } + default: + unreachable(node); + } +} diff --git a/src/static-semantics/CodePointAt.mts b/src/static-semantics/CodePointAt.mts new file mode 100644 index 0000000..0cc4151 --- /dev/null +++ b/src/static-semantics/CodePointAt.mts @@ -0,0 +1,53 @@ +import { X } from '../completion.mts'; +import { UTF16SurrogatePairToCodePoint } from './all.mts'; +import { Assert } from '#self'; +import { isLeadingSurrogate, isTrailingSurrogate, type CodePoint } from '#self'; + +/** https://tc39.es/ecma262/#sec-codepointat */ +export function CodePointAt(string: string, position: number) { + // 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 as CodePoint, + 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 as CodePoint, + 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 as CodePoint, + 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 as CodePoint, + CodeUnitCount: 2, + IsUnpairedSurrogate: false, + }; +} diff --git a/src/static-semantics/CodePointsToString.mts b/src/static-semantics/CodePointsToString.mts new file mode 100644 index 0000000..f8240e0 --- /dev/null +++ b/src/static-semantics/CodePointsToString.mts @@ -0,0 +1,15 @@ +import { UTF16EncodeCodePoint } from './all.mts'; +import type { CodePoint } from '#self'; + +/** https://tc39.es/ecma262/#sec-codepointstostring */ +export function CodePointsToString(text: string) { + // 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 UTF16EncodeCodePoint(cp). + result += UTF16EncodeCodePoint(cp.codePointAt(0)! as CodePoint); + } + // 3. Return result. + return result; +} diff --git a/src/static-semantics/ConstructorMethod.mts b/src/static-semantics/ConstructorMethod.mts new file mode 100644 index 0000000..933f8e4 --- /dev/null +++ b/src/static-semantics/ConstructorMethod.mts @@ -0,0 +1,10 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { PropName } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-constructormethod */ +// ClassElementList : +// ClassElement +// ClassElementList ClassElement +export function ConstructorMethod(ClassElementList: ParseNode.ClassElementList): ParseNode.MethodDefinition | undefined { + return ClassElementList.find((ClassElement) => ClassElement.static === false && PropName(ClassElement) === 'constructor') as ParseNode.MethodDefinition; +} diff --git a/src/static-semantics/ContainsArguments.mts b/src/static-semantics/ContainsArguments.mts new file mode 100644 index 0000000..6e209d8 --- /dev/null +++ b/src/static-semantics/ContainsArguments.mts @@ -0,0 +1,33 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-containsarguments */ +export function ContainsArguments(node: ParseNode): ParseNode.IdentifierReference | null { + switch (node.type) { + case 'IdentifierReference': + if (node.name === 'arguments') { + return node; + } + return null; + case 'FunctionDeclaration': + case 'FunctionExpression': + case 'MethodDefinition': + case 'GeneratorMethod': + case 'GeneratorDeclaration': + case 'GeneratorExpression': + case 'AsyncMethod': + case 'AsyncFunctionDeclaration': + case 'AsyncFunctionExpression': + return null; + default: + for (const value of Object.values(node)) { + // TODO(ts): This function does not accept a ParseNode[], when isArray(value), ContainsArguments should never return a result? + if ((value?.type || Array.isArray(value))) { + const maybe = ContainsArguments(value); + if (maybe) { + return maybe; + } + } + } + return null; + } +} diff --git a/src/static-semantics/ContainsExpression.mts b/src/static-semantics/ContainsExpression.mts new file mode 100644 index 0000000..af0a6e3 --- /dev/null +++ b/src/static-semantics/ContainsExpression.mts @@ -0,0 +1,59 @@ +import { OutOfRange, isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function ContainsExpression(node: ParseNode | readonly ParseNode[]): boolean { + if (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 && 'ComputedPropertyName' in node.PropertyName && node.PropertyName.ComputedPropertyName) { + return true; + } + return ContainsExpression(node.BindingElement); + case 'BindingRestProperty': + if (node.BindingIdentifier) { + return false; + } + // TODO(ts): BindingRestProperty and BindingElement is different. Is there missing a case? + // @ts-expect-error + return ContainsExpression((node as ParseNode.BindingElement).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/src/static-semantics/DeclarationPart.mts b/src/static-semantics/DeclarationPart.mts new file mode 100644 index 0000000..1b714db --- /dev/null +++ b/src/static-semantics/DeclarationPart.mts @@ -0,0 +1,5 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function DeclarationPart(node: T): T { + return node; +} diff --git a/src/static-semantics/ExpectedArgumentCount.mts b/src/static-semantics/ExpectedArgumentCount.mts new file mode 100644 index 0000000..15a19d4 --- /dev/null +++ b/src/static-semantics/ExpectedArgumentCount.mts @@ -0,0 +1,26 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { HasInitializer } from './all.mts'; + +export function ExpectedArgumentCount(FormalParameterList: ParseNode.FormalParameters) { + 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/src/static-semantics/ExportEntries.mts b/src/static-semantics/ExportEntries.mts new file mode 100644 index 0000000..fcb1b63 --- /dev/null +++ b/src/static-semantics/ExportEntries.mts @@ -0,0 +1,129 @@ +import { JSStringValue, NullValue, Value } from '../value.mts'; +import { OutOfRange, isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + BoundNames, ModuleRequests, ExportEntriesForModule, type ModuleRequestRecord, +} from './all.mts'; + +export function ExportEntries(node: ParseNode | readonly ParseNode[]): ExportEntry[] { + if (isArray(node)) { + const entries: ExportEntry[] = []; + 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 WithClause? `;` + // 1. Let module be the sole element of ModuleRequests of FromClause. + const module = ModuleRequests(node)[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: ExportEntry[] = []; + // 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: 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: 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: Value('*default*'), + ExportName: Value('default'), + }; + // 2. Return a new List containing entry. + return [entry]; + } + default: + throw new OutOfRange('ExportEntries', node); + } + default: + return []; + } +} + +export interface ExportEntry { + readonly ModuleRequest: ModuleRequestRecord | NullValue; + readonly ImportName: JSStringValue | NullValue | 'all' | 'all-but-default'; + readonly LocalName: JSStringValue | NullValue; + readonly ExportName: JSStringValue | NullValue; +} diff --git a/src/static-semantics/ExportEntriesForModule.mts b/src/static-semantics/ExportEntriesForModule.mts new file mode 100644 index 0000000..4d20eb2 --- /dev/null +++ b/src/static-semantics/ExportEntriesForModule.mts @@ -0,0 +1,63 @@ +import { NullValue, Value } from '../value.mts'; +import { OutOfRange, isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { StringValue, type ExportEntry, type ModuleRequestRecord } from './all.mts'; + +export function ExportEntriesForModule(node: ParseNode | readonly ParseNode[], module: ModuleRequestRecord | NullValue): ExportEntry[] { + if (isArray(node)) { + const specs: ExportEntry[] = []; + node.forEach((n) => { + specs.push(...ExportEntriesForModule(n, module)); + }); + return specs; + } + switch (node.type) { + case 'ExportFromClause': + 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]]: ~all~, [[LocalName]]: null, [[ExportName]]: exportName }. + const entry: ExportEntry = { + ModuleRequest: module, + ImportName: 'all', + 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]]: ~all-but-default~, [[LocalName]]: null, [[ExportName]]: null }. + const entry: ExportEntry = { + ModuleRequest: module, + ImportName: 'all-but-default', + LocalName: Value.null, + ExportName: Value.null, + }; + // 2. Return a new List containing entry. + return [entry]; + } + case 'ExportSpecifier': { + const sourceName = StringValue(node.localName); + const exportName = StringValue(node.exportName); + let localName; + let importName; + if (module === Value.null) { + localName = sourceName; + importName = Value.null; + } else { // 4. Else, + localName = Value.null; + importName = sourceName; + } + return [{ + ModuleRequest: module, + ImportName: importName, + LocalName: localName, + ExportName: exportName, + }]; + } + case 'NamedExports': + return ExportEntriesForModule(node.ExportsList, module); + default: + throw new OutOfRange('ExportEntriesForModule', node); + } +} diff --git a/src/static-semantics/FlagText.mts b/src/static-semantics/FlagText.mts new file mode 100644 index 0000000..4ccee24 --- /dev/null +++ b/src/static-semantics/FlagText.mts @@ -0,0 +1,7 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-flagtext */ +// RegularExpressionLiteral :: `/` RegularExpressionBody `/` RegularExpressionFlags +export function FlagText(RegularExpressionLiteral: ParseNode.RegularExpressionLiteral) { + return RegularExpressionLiteral.RegularExpressionFlags; +} diff --git a/src/static-semantics/HasInitializer.mts b/src/static-semantics/HasInitializer.mts new file mode 100644 index 0000000..09d1946 --- /dev/null +++ b/src/static-semantics/HasInitializer.mts @@ -0,0 +1,5 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function HasInitializer(node: ParseNode): node is ParseNode & { readonly Initializer: ParseNode.Initializer; } { + return 'Initializer' in node && !!node.Initializer; +} diff --git a/src/static-semantics/HasName.mts b/src/static-semantics/HasName.mts new file mode 100644 index 0000000..692915f --- /dev/null +++ b/src/static-semantics/HasName.mts @@ -0,0 +1,8 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function HasName(node: ParseNode): boolean { + if (node.type === 'ParenthesizedExpression') { + return HasName(node.Expression); + } + return 'BindingIdentifier' in node && !!node.BindingIdentifier; +} diff --git a/src/static-semantics/ImportEntries.mts b/src/static-semantics/ImportEntries.mts new file mode 100644 index 0000000..06e5ce3 --- /dev/null +++ b/src/static-semantics/ImportEntries.mts @@ -0,0 +1,36 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { ImportEntriesForModule, ModuleRequests, type ModuleRequestRecord } from './all.mts'; + +export function ImportEntries(node: ParseNode): ImportEntry[] { + switch (node.type) { + case 'Module': + if (node.ModuleBody) { + return ImportEntries(node.ModuleBody); + } + return []; + case 'ModuleBody': { + const entries: ImportEntry[] = []; + 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)[0]; + // 2. Return ImportEntriesForModule of ImportClause with argument module. + return ImportEntriesForModule(node.ImportClause!, module); + } + return []; + default: + return []; + } +} + +export interface ImportEntry { + readonly ModuleRequest: ModuleRequestRecord; + readonly ImportName: JSStringValue | 'namespace-object'; + readonly LocalName: JSStringValue; +} diff --git a/src/static-semantics/ImportEntriesForModule.mts b/src/static-semantics/ImportEntriesForModule.mts new file mode 100644 index 0000000..ad23410 --- /dev/null +++ b/src/static-semantics/ImportEntriesForModule.mts @@ -0,0 +1,97 @@ +import { Value } from '../value.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { + BoundNames, StringValue, type ImportEntry, type ModuleRequestRecord, +} from './all.mts'; + +export function ImportEntriesForModule(node: ParseNode, module: ModuleRequestRecord): ImportEntry[] { + 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: ImportEntry = { + ModuleRequest: module, + ImportName: 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]]: ~namespace-object~, [[LocalName]]: localName }. + const entry: ImportEntry = { + ModuleRequest: module, + ImportName: 'namespace-object', + LocalName: localName, + }; + // 3. Return a new List containing entry. + return [entry]; + } + case 'NamedImports': { + const specs: ImportEntry[] = []; + node.ImportsList.forEach((n) => { + specs.push(...ImportEntriesForModule(n, module)); + }); + return specs; + } + case 'ImportSpecifier': + 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: ImportEntry = { + 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: ImportEntry = { + ModuleRequest: module, + ImportName: localName, + LocalName: localName, + }; + // 3. Return a new List containing entry. + return [entry]; + } + default: + throw new OutOfRange('ImportEntriesForModule', node); + } +} diff --git a/src/static-semantics/ImportedLocalNames.mts b/src/static-semantics/ImportedLocalNames.mts new file mode 100644 index 0000000..8301815 --- /dev/null +++ b/src/static-semantics/ImportedLocalNames.mts @@ -0,0 +1,14 @@ +import type { ImportEntry } from './ImportEntries.mts'; + +/** https://tc39.es/ecma262/#sec-importedlocalnames */ +export function ImportedLocalNames(importEntries: readonly ImportEntry[]) { + // 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/src/static-semantics/IsAnonymousFunctionDefinition.mts b/src/static-semantics/IsAnonymousFunctionDefinition.mts new file mode 100644 index 0000000..de19761 --- /dev/null +++ b/src/static-semantics/IsAnonymousFunctionDefinition.mts @@ -0,0 +1,18 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { IsFunctionDefinition, HasName } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-isanonymousfunctiondefinition */ +export function IsAnonymousFunctionDefinition(expr: ParseNode) { + // 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/src/static-semantics/IsComputedPropertyKey.mts b/src/static-semantics/IsComputedPropertyKey.mts new file mode 100644 index 0000000..3442139 --- /dev/null +++ b/src/static-semantics/IsComputedPropertyKey.mts @@ -0,0 +1,7 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function IsComputedPropertyKey(node: ParseNode.PropertyNameLike): node is ParseNode.PropertyName { + return node.type !== 'IdentifierName' + && node.type !== 'StringLiteral' + && node.type !== 'NumericLiteral'; +} diff --git a/src/static-semantics/IsConstantDeclaration.mts b/src/static-semantics/IsConstantDeclaration.mts new file mode 100644 index 0000000..412be15 --- /dev/null +++ b/src/static-semantics/IsConstantDeclaration.mts @@ -0,0 +1,5 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function IsConstantDeclaration(node: ParseNode | ParseNode.LetOrConst) { + return node === 'const' || (typeof node === 'object' && 'LetOrConst' in node && node.LetOrConst === 'const'); +} diff --git a/src/static-semantics/IsDestructuring.mts b/src/static-semantics/IsDestructuring.mts new file mode 100644 index 0000000..f5ab14c --- /dev/null +++ b/src/static-semantics/IsDestructuring.mts @@ -0,0 +1,21 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export type DestructuringParseNode = ParseNode.ObjectBindingPattern | ParseNode.ArrayBindingPattern | ParseNode.ObjectLiteral | ParseNode.ArrayLiteral | ParseNode.ForDeclaration | ParseNode.ForBinding; +export function IsDestructuring(node: ParseNode): boolean { + 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/src/static-semantics/IsFunctionDefinition.mts b/src/static-semantics/IsFunctionDefinition.mts new file mode 100644 index 0000000..30e0517 --- /dev/null +++ b/src/static-semantics/IsFunctionDefinition.mts @@ -0,0 +1,15 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export type FunctionDeclaration = ParseNode.FunctionExpression | ParseNode.GeneratorExpression | ParseNode.AsyncFunctionExpression | ParseNode.AsyncGeneratorExpression | ParseNode.ClassExpression | ParseNode.ArrowFunction | ParseNode.AsyncArrowFunction | ParseNode.ParenthesizedExpression & { readonly Expression: FunctionDeclaration }; +export function IsFunctionDefinition(node: ParseNode): node is FunctionDeclaration { + 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/src/static-semantics/IsIdentifierRef.mts b/src/static-semantics/IsIdentifierRef.mts new file mode 100644 index 0000000..d329bf1 --- /dev/null +++ b/src/static-semantics/IsIdentifierRef.mts @@ -0,0 +1,5 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function IsIdentifierRef(node: ParseNode): node is ParseNode.IdentifierReference { + return node.type === 'IdentifierReference'; +} diff --git a/src/static-semantics/IsInTailPosition.mts b/src/static-semantics/IsInTailPosition.mts new file mode 100644 index 0000000..67247e5 --- /dev/null +++ b/src/static-semantics/IsInTailPosition.mts @@ -0,0 +1,5 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function IsInTailPosition(_node: ParseNode): boolean { + return false; +} diff --git a/src/static-semantics/IsSimpleParameterList.mts b/src/static-semantics/IsSimpleParameterList.mts new file mode 100644 index 0000000..cf65b54 --- /dev/null +++ b/src/static-semantics/IsSimpleParameterList.mts @@ -0,0 +1,23 @@ +import { OutOfRange, isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function IsSimpleParameterList(node: ParseNode | readonly ParseNode[]) { + if (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/src/static-semantics/IsStatic.mts b/src/static-semantics/IsStatic.mts new file mode 100644 index 0000000..f10a0c0 --- /dev/null +++ b/src/static-semantics/IsStatic.mts @@ -0,0 +1,10 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-isstatic */ +// ClassElement : +// MethodDefinition +// `static` MethodDefinition +// `;` +export function IsStatic(ClassElement: ParseNode.ClassElement) { + return ClassElement.static; +} diff --git a/src/static-semantics/IsStrict.mts b/src/static-semantics/IsStrict.mts new file mode 100644 index 0000000..f1eca9f --- /dev/null +++ b/src/static-semantics/IsStrict.mts @@ -0,0 +1,7 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-isstrict */ +export function IsStrict({ ScriptBody }: ParseNode.Script) { + // 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/src/static-semantics/IsStringWellFormedUnicode.mts b/src/static-semantics/IsStringWellFormedUnicode.mts new file mode 100644 index 0000000..8f47e61 --- /dev/null +++ b/src/static-semantics/IsStringWellFormedUnicode.mts @@ -0,0 +1,24 @@ +import { X } from '../completion.mts'; +import type { JSStringValue } from '../value.mts'; +import { CodePointAt } from './all.mts'; + +export function IsStringWellFormedUnicode(string_: JSStringValue) { + const 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 ≠ 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; + } + // 4. Return true. + return true; +} diff --git a/src/static-semantics/LexicallyDeclaredNames.mts b/src/static-semantics/LexicallyDeclaredNames.mts new file mode 100644 index 0000000..54f0312 --- /dev/null +++ b/src/static-semantics/LexicallyDeclaredNames.mts @@ -0,0 +1,26 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { + TopLevelLexicallyDeclaredNames, +} from './all.mts'; + +export function LexicallyDeclaredNames(node: ParseNode): JSStringValue[] { + switch (node.type) { + case 'Script': + if (node.ScriptBody) { + return LexicallyDeclaredNames(node.ScriptBody); + } + return []; + case 'ScriptBody': + return TopLevelLexicallyDeclaredNames(node.StatementList); + case 'FunctionBody': + case 'GeneratorBody': + case 'AsyncBody': + case 'AsyncGeneratorBody': + return TopLevelLexicallyDeclaredNames(node.FunctionStatementList); + case 'ClassStaticBlockBody': + return TopLevelLexicallyDeclaredNames(node.ClassStaticBlockStatementList); + default: + return []; + } +} diff --git a/src/static-semantics/LexicallyScopedDeclarations.mts b/src/static-semantics/LexicallyScopedDeclarations.mts new file mode 100644 index 0000000..901e24a --- /dev/null +++ b/src/static-semantics/LexicallyScopedDeclarations.mts @@ -0,0 +1,82 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { TopLevelLexicallyScopedDeclarations, DeclarationPart } from './all.mts'; + +export function LexicallyScopedDeclarations(node: ParseNode | readonly ParseNode[]): (ParseNode.Declaration | ParseNode.ExportDeclaration)[] { + if (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 'AsyncBody': + case 'AsyncGeneratorBody': + return TopLevelLexicallyScopedDeclarations(node.FunctionStatementList); + case 'ClassStaticBlockBody': + return TopLevelLexicallyScopedDeclarations(node.ClassStaticBlockStatementList); + 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/src/static-semantics/ModuleRequests.mts b/src/static-semantics/ModuleRequests.mts new file mode 100644 index 0000000..9c291b2 --- /dev/null +++ b/src/static-semantics/ModuleRequests.mts @@ -0,0 +1,98 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { StringValue } from './all.mts'; +import { type LoadedModuleRequestRecord } from '#self'; + +// https://tc39.es/ecma262/#modulerequest-record +export interface ModuleRequestRecord { + readonly Specifier: JSStringValue; + readonly Attributes: ImportAttributeRecord[]; + readonly Phase: 'defer' | 'evaluation'; +} + +// https://tc39.es/ecma262/#importattribute-record +export interface ImportAttributeRecord { + readonly Key: JSStringValue; + readonly Value: JSStringValue; +} + +function stringsEqual(left: JSStringValue, right: JSStringValue) { + return left === right || left.stringValue() === right.stringValue(); +} + +// https://tc39.es/ecma262/#sec-ModuleRequestsEqual +export function ModuleRequestsEqual(left: ModuleRequestRecord | LoadedModuleRequestRecord, right: ModuleRequestRecord | LoadedModuleRequestRecord) { + if (!stringsEqual(left.Specifier, right.Specifier)) { + return false; + } + const leftAttrs = left.Attributes; + const rightAttrs = right.Attributes; + const leftAttrsCount = leftAttrs.length; + const rightAttrsCount = rightAttrs.length; + if (leftAttrsCount !== rightAttrsCount) { + return false; + } + for (const l of leftAttrs) { + if (!rightAttrs.some((r) => stringsEqual(l.Key, r.Key) && stringsEqual(l.Value, r.Value))) { + return false; + } + } + return true; +} + +// https://tc39.es/ecma262/#sec-withclausetoattributes +function WithClauseToAttributes(node: ParseNode.WithClause): ImportAttributeRecord[] { + const attributes: ImportAttributeRecord[] = []; + for (const attribute of node.WithEntries) { + attributes.push({ + Key: StringValue(attribute.AttributeKey), + Value: StringValue(attribute.AttributeValue), + }); + } + attributes.sort((a, b) => (a.Key.value < b.Key.value ? -1 : 1)); + return attributes; +} + +export function ModuleRequests(node: ParseNode): ModuleRequestRecord[] { + switch (node.type) { + case 'Module': + if (node.ModuleBody) { + return ModuleRequests(node.ModuleBody); + } + return []; + case 'ModuleBody': { + const requests: ModuleRequestRecord[] = []; + for (const item of node.ModuleItemList) { + const additionalRequests = ModuleRequests(item); + for (const mr of additionalRequests) { + if (!requests.some((r) => ModuleRequestsEqual(r, mr) && r.Phase === mr.Phase) + ) { + requests.push(mr); + } + } + } + return requests; + } + case 'ImportDeclaration': + if (node.FromClause) { + const specifier = StringValue(node.FromClause); + const attributes = node.WithClause ? WithClauseToAttributes(node.WithClause) : []; + return [{ Specifier: specifier, Attributes: attributes, Phase: node.Phase }]; + } + if (node.ModuleSpecifier) { + const specifier = StringValue(node.ModuleSpecifier); + const attributes = node.WithClause ? WithClauseToAttributes(node.WithClause) : []; + return [{ Specifier: specifier, Attributes: attributes, Phase: node.Phase }]; + } + throw new Error('Unreachable: all imports must have either an ImportClause or a ModuleSpecifier'); + case 'ExportDeclaration': + if (node.FromClause) { + const specifier = StringValue(node.FromClause); + const attributes = node.WithClause ? WithClauseToAttributes(node.WithClause) : []; + return [{ Specifier: specifier, Attributes: attributes, Phase: 'evaluation' }]; + } + return []; + default: + return []; + } +} diff --git a/src/static-semantics/NonConstructorElements.mts b/src/static-semantics/NonConstructorElements.mts new file mode 100644 index 0000000..6ec1fd8 --- /dev/null +++ b/src/static-semantics/NonConstructorElements.mts @@ -0,0 +1,15 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { PropName } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-nonconstructorelements */ +// ClassElementList : +// ClassElement +// ClassElementList ClassElement +export function NonConstructorElements(ClassElementList: ParseNode.ClassElementList) { + return ClassElementList.filter((ClassElement) => { + if (ClassElement.static === false && PropName(ClassElement) === 'constructor') { + return false; + } + return true; + }); +} diff --git a/src/static-semantics/NumericValue.mts b/src/static-semantics/NumericValue.mts new file mode 100644 index 0000000..420132b --- /dev/null +++ b/src/static-semantics/NumericValue.mts @@ -0,0 +1,7 @@ +/** https://tc39.es/ecma262/#sec-numericvalue */ +import type { ParseNode } from '../parser/ParseNode.mts'; +import { Value } from '../value.mts'; + +export function NumericValue(node: ParseNode.NumericLiteral) { + return Value(node.value); +} diff --git a/src/static-semantics/PrivateBoundIdentifiers.mts b/src/static-semantics/PrivateBoundIdentifiers.mts new file mode 100644 index 0000000..5216035 --- /dev/null +++ b/src/static-semantics/PrivateBoundIdentifiers.mts @@ -0,0 +1,23 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { StringValue } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-privateboundidentifiers */ +export function PrivateBoundIdentifiers(node: ParseNode | readonly ParseNode[]): JSStringValue[] { + if (isArray(node)) { + return node.flatMap((n) => PrivateBoundIdentifiers(n)); + } + switch (node.type) { + case 'PrivateIdentifier': + return [StringValue(node)]; + case 'MethodDefinition': + case 'GeneratorMethod': + case 'AsyncMethod': + case 'AsyncGeneratorMethod': + case 'FieldDefinition': + return PrivateBoundIdentifiers(node.ClassElementName); + default: + return []; + } +} diff --git a/src/static-semantics/PropName.mts b/src/static-semantics/PropName.mts new file mode 100644 index 0000000..953244f --- /dev/null +++ b/src/static-semantics/PropName.mts @@ -0,0 +1,23 @@ +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function PropName(node: ParseNode): string | undefined { + switch (node.type) { + case 'IdentifierName': + return node.name; + case 'StringLiteral': + return node.value; + case 'MethodDefinition': + case 'GeneratorMethod': + case 'AsyncGeneratorMethod': + case 'AsyncMethod': + case 'FieldDefinition': + return PropName(node.ClassElementName); + case 'PropertyDefinition': + if (node.PropertyName) { + return PropName(node.PropertyName); + } + break; + default: + } + return undefined; +} diff --git a/src/static-semantics/StringToCodePoints.mts b/src/static-semantics/StringToCodePoints.mts new file mode 100644 index 0000000..69a1703 --- /dev/null +++ b/src/static-semantics/StringToCodePoints.mts @@ -0,0 +1,22 @@ +import { CodePointAt } from './all.mts'; + +/** https://tc39.es/ecma262/#sec-stringtocodepoints */ +export function StringToCodePoints(string: 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 = 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/src/static-semantics/StringValue.mts b/src/static-semantics/StringValue.mts new file mode 100644 index 0000000..6bf6b12 --- /dev/null +++ b/src/static-semantics/StringValue.mts @@ -0,0 +1,19 @@ +import { Value } from '../value.mts'; +import { OutOfRange } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function StringValue(node: ParseNode) { + switch (node.type) { + case 'IdentifierName': + case 'BindingIdentifier': + case 'IdentifierReference': + case 'LabelIdentifier': + return Value(node.name); + case 'PrivateIdentifier': + return Value(`#${node.name}`); + case 'StringLiteral': + return Value(node.value); + default: + throw new OutOfRange('StringValue', node); + } +} diff --git a/src/static-semantics/TemplateStrings.mts b/src/static-semantics/TemplateStrings.mts new file mode 100644 index 0000000..660fffe --- /dev/null +++ b/src/static-semantics/TemplateStrings.mts @@ -0,0 +1,109 @@ +import { Value } from '../value.mts'; +import { isHexDigit, isDecimalDigit, isLineTerminator } from '../parser/Lexer.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +/** https://tc39.es/ecma262/#sec-static-semantics-tv */ +export function TV(s: string) { + 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 '"': + 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 += 1; + 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: ParseNode.TemplateLiteral, raw: boolean) { + if (raw) { + return node.TemplateSpanList.map((s) => Value(s)); + } + return node.TemplateSpanList.map((v) => { + const tv = TV(v); + if (tv === undefined) { + return Value.undefined; + } + return Value(tv); + }); +} diff --git a/src/static-semantics/TopLevelLexicallyDeclaredNames.mts b/src/static-semantics/TopLevelLexicallyDeclaredNames.mts new file mode 100644 index 0000000..5a9abd2 --- /dev/null +++ b/src/static-semantics/TopLevelLexicallyDeclaredNames.mts @@ -0,0 +1,21 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { BoundNames } from './all.mts'; + +export function TopLevelLexicallyDeclaredNames(node: ParseNode | readonly ParseNode[]): JSStringValue[] { + if (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/src/static-semantics/TopLevelLexicallyScopedDeclarations.mts b/src/static-semantics/TopLevelLexicallyScopedDeclarations.mts new file mode 100644 index 0000000..8ad9454 --- /dev/null +++ b/src/static-semantics/TopLevelLexicallyScopedDeclarations.mts @@ -0,0 +1,23 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; + +export function TopLevelLexicallyScopedDeclarations(node: ParseNode | readonly ParseNode[]): LexicallyScopedDeclaration[] { + if (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 []; + } +} + +export type LexicallyScopedDeclaration = + | ParseNode.ClassDeclaration + | ParseNode.LexicalDeclaration; diff --git a/src/static-semantics/TopLevelVarDeclaredNames.mts b/src/static-semantics/TopLevelVarDeclaredNames.mts new file mode 100644 index 0000000..9ba1240 --- /dev/null +++ b/src/static-semantics/TopLevelVarDeclaredNames.mts @@ -0,0 +1,26 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { BoundNames, VarDeclaredNames } from './all.mts'; + +export function TopLevelVarDeclaredNames(node: ParseNode | readonly ParseNode[]): JSStringValue[] { + if (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/src/static-semantics/TopLevelVarScopedDeclarations.mts b/src/static-semantics/TopLevelVarScopedDeclarations.mts new file mode 100644 index 0000000..df4c5d8 --- /dev/null +++ b/src/static-semantics/TopLevelVarScopedDeclarations.mts @@ -0,0 +1,34 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { DeclarationPart, VarScopedDeclarations } from './all.mts'; + +export function TopLevelVarScopedDeclarations(node: ParseNode | readonly ParseNode[]): VarScopedDeclaration[] { + if (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); + } +} + +export type VarScopedDeclaration = + | ParseNode.ForBinding + | ParseNode.VariableDeclaration + | ParseNode.FunctionDeclaration + | ParseNode.GeneratorDeclaration + | ParseNode.AsyncFunctionDeclaration + | ParseNode.AsyncGeneratorDeclaration + | ParseNode.BindingIdentifier; diff --git a/src/static-semantics/UTF16EncodeCodePoint.mts b/src/static-semantics/UTF16EncodeCodePoint.mts new file mode 100644 index 0000000..fc29021 --- /dev/null +++ b/src/static-semantics/UTF16EncodeCodePoint.mts @@ -0,0 +1,18 @@ +import { Assert } from '#self'; +import type { CodePoint } from '#self'; + +/** https://tc39.es/ecma262/#sec-utf16encodecodepoint */ +export function UTF16EncodeCodePoint(cp: CodePoint) { + // 1. Assert: 0 ≤ cp ≤ 0x10FFFF. + Assert(cp >= 0 && cp <= 0x10FFFF); + // 2. If cp ≤ 0xFFFF, return the String value consisting of the code unit whose value is cp. + if (cp <= 0xFFFF) { + return String.fromCodePoint(cp); + } + // 3. Let cu1 be the code unit whose value is floor((cp - 0x10000) / 0x400) + 0xD800. + const cu1 = Math.floor((cp - 0x10000) / 0x400) + 0xD800; + // 4. Let cu2 be the code unit whose value is ((cp - 0x10000) modulo 0x400) + 0xDC00. + const cu2 = ((cp - 0x10000) % 0x400) + 0xDC00; + // 5. Return the string-concatenation of cu1 and cu2. + return String.fromCodePoint(cu1, cu2); +} diff --git a/src/static-semantics/UTF16SurrogatePairToCodePoint.mts b/src/static-semantics/UTF16SurrogatePairToCodePoint.mts new file mode 100644 index 0000000..a6e9adb --- /dev/null +++ b/src/static-semantics/UTF16SurrogatePairToCodePoint.mts @@ -0,0 +1,12 @@ +import { Assert } from '#self'; +import { isLeadingSurrogate, isTrailingSurrogate, type CodePoint } from '#self'; + +/** https://tc39.es/ecma262/#sec-utf16decodesurrogatepair */ +export function UTF16SurrogatePairToCodePoint(lead: number, trail: number): CodePoint { + // 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 as CodePoint; +} diff --git a/src/static-semantics/VarDeclaredNames.mts b/src/static-semantics/VarDeclaredNames.mts new file mode 100644 index 0000000..fcda0eb --- /dev/null +++ b/src/static-semantics/VarDeclaredNames.mts @@ -0,0 +1,109 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import type { JSStringValue } from '../value.mts'; +import { BoundNames, TopLevelVarDeclaredNames } from './all.mts'; + +export function VarDeclaredNames(node: ParseNode | readonly ParseNode[]): JSStringValue[] { + if (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 'AsyncBody': + case 'AsyncGeneratorBody': + return TopLevelVarDeclaredNames(node.FunctionStatementList); + case 'ClassStaticBlockBody': + return TopLevelVarDeclaredNames(node.ClassStaticBlockStatementList); + case 'ExportDeclaration': + if (node.VariableStatement) { + return BoundNames(node); + } + return []; + default: + return []; + } +} diff --git a/src/static-semantics/VarScopedDeclarations.mts b/src/static-semantics/VarScopedDeclarations.mts new file mode 100644 index 0000000..4720608 --- /dev/null +++ b/src/static-semantics/VarScopedDeclarations.mts @@ -0,0 +1,115 @@ +import { isArray } from '../helpers.mts'; +import type { ParseNode } from '../parser/ParseNode.mts'; +import { TopLevelVarScopedDeclarations, type VarScopedDeclaration } from './all.mts'; + +export function VarScopedDeclarations(node: ParseNode | readonly ParseNode[]): VarScopedDeclaration[] { + if (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 'AsyncBody': + case 'AsyncGeneratorBody': + return TopLevelVarScopedDeclarations(node.FunctionStatementList); + case 'ClassStaticBlockBody': + return TopLevelVarScopedDeclarations(node.ClassStaticBlockStatementList); + default: + return []; + } +} diff --git a/src/static-semantics/all.mts b/src/static-semantics/all.mts new file mode 100644 index 0000000..491fdc5 --- /dev/null +++ b/src/static-semantics/all.mts @@ -0,0 +1,47 @@ +export * from './StringValue.mts'; +export * from './IsStatic.mts'; +export * from './NonConstructorElements.mts'; +export * from './ConstructorMethod.mts'; +export * from './PropName.mts'; +export * from './NumericValue.mts'; +export * from './IsAnonymousFunctionDefinition.mts'; +export * from './IsFunctionDefinition.mts'; +export * from './HasName.mts'; +export * from './IsIdentifierRef.mts'; +export * from './LexicallyDeclaredNames.mts'; +export * from './TopLevelLexicallyDeclaredNames.mts'; +export * from './BoundNames.mts'; +export * from './VarDeclaredNames.mts'; +export * from './TopLevelVarDeclaredNames.mts'; +export * from './VarScopedDeclarations.mts'; +export * from './TopLevelVarScopedDeclarations.mts'; +export * from './DeclarationPart.mts'; +export * from './LexicallyScopedDeclarations.mts'; +export * from './TopLevelLexicallyScopedDeclarations.mts'; +export * from './IsConstantDeclaration.mts'; +export * from './IsInTailPosition.mts'; +export * from './ExpectedArgumentCount.mts'; +export * from './HasInitializer.mts'; +export * from './IsSimpleParameterList.mts'; +export * from './ContainsExpression.mts'; +export * from './IsStrict.mts'; +export * from './BodyText.mts'; +export * from './FlagText.mts'; +export * from './ModuleRequests.mts'; +export * from './ImportEntries.mts'; +export * from './ExportEntries.mts'; +export * from './ImportedLocalNames.mts'; +export * from './IsDestructuring.mts'; +export * from './TemplateStrings.mts'; +export * from './ImportEntriesForModule.mts'; +export * from './ExportEntriesForModule.mts'; +export * from './CharacterValue.mts'; +export * from './UTF16SurrogatePairToCodePoint.mts'; +export * from './CodePointAt.mts'; +export * from './StringToCodePoints.mts'; +export * from './CodePointsToString.mts'; +export * from './IsStringWellFormedUnicode.mts'; +export * from './IsComputedPropertyKey.mts'; +export * from './PrivateBoundIdentifiers.mts'; +export * from './ContainsArguments.mts'; +export * from './UTF16EncodeCodePoint.mts'; diff --git a/src/syntax-error.d.ts b/src/syntax-error.d.ts new file mode 100644 index 0000000..3e9138d --- /dev/null +++ b/src/syntax-error.d.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line no-unused-vars +interface SyntaxError { + decoration?: string + position?: number +} diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 0000000..f9bd23b --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "./", + "emitDeclarationOnly": true, + "declarationDir": "../declaration/", + "tsBuildInfoFile": "../declaration/.tsbuildinfo" + }, + "include": ["./"] +} diff --git a/src/unicode/.gitkeep b/src/unicode/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/value.mts b/src/value.mts new file mode 100644 index 0000000..62c5dfb --- /dev/null +++ b/src/value.mts @@ -0,0 +1,1016 @@ +import { type GCMarker, surroundingAgent } from './host-defined/engine.mts'; +import { + Q, X, type ValueEvaluator, type PlainCompletion, +} from './completion.mts'; +import { + PropertyKeyMap, OutOfRange, callable, +} from './helpers.mts'; +import type { PrivateElementRecord } from './runtime-semantics/MethodDefinitionEvaluation.mts'; +import type { PlainEvaluator } from './evaluator.mts'; +import { + Assert, + OrdinaryDefineOwnProperty, + OrdinaryDelete, + OrdinaryGet, + OrdinaryGetOwnProperty, + OrdinaryGetPrototypeOf, + OrdinaryHasProperty, + OrdinaryIsExtensible, + OrdinaryOwnPropertyKeys, + OrdinaryPreventExtensions, + OrdinarySet, + OrdinarySetPrototypeOf, + ToInt32, + ToUint32, + Z, + F, R, type OrdinaryObject, type FunctionObject, + type BuiltinFunctionObject, + type ECMAScriptFunctionObject, + type DefaultConstructorBuiltinFunction, EnvironmentRecord, + Throw, +} from '#self'; + +let createStringValue: (value: string) => JSStringValue; // set by static block in StringValue for privileged access to constructor +let createNumberValue: (value: number) => NumberValue; // set by static block in NumberValue for privileged access to constructor +let createBigIntValue: (value: bigint) => BigIntValue; // set by static block in BigIntValue for privileged access to constructor + +abstract class BaseValue { + static declare readonly null: NullValue; // defined in static block of NullValue + + static declare readonly undefined: UndefinedValue; // defined in static block of UndefinedValue + + static declare readonly true: BooleanValue; // defined in static block of BooleanValue + + static declare readonly false: BooleanValue; // defined in static block of BooleanValue + + abstract type: Value['type']; // ensures new `Value` subtypes must be added to `Value` union + + declare static [Symbol.hasInstance]: (value: unknown) => value is Value; // no need to actually declare it. +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types */ +export type Value = + | UndefinedValue + | NullValue + | BooleanValue + | JSStringValue + | SymbolValue + | NumberValue + | BigIntValue + | ObjectValue; + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types */ +export const Value = (() => { + // NOTE: Using IIFE so that the class does not conflict with the type of the same name + @callable((_target, _thisArg, [value]) => { + if (value === null) { + return Value.null; + } else if (value === undefined) { + return Value.undefined; + } else if (value === true) { + return Value.true; + } else if (value === false) { + return Value.false; + } + switch (typeof value) { + case 'string': + return createStringValue(value); + case 'number': + return createNumberValue(value); + case 'bigint': + return createBigIntValue(value); + default: + throw new OutOfRange('new Value', value); + } + }) + abstract class Value extends BaseValue { + } + return Value; +})() as typeof BaseValue & { + (value: T): + T extends null ? NullValue : + T extends undefined ? UndefinedValue : + T extends boolean ? BooleanValue : + T extends string ? JSStringValue : + T extends number ? NumberValue : + T extends bigint ? BigIntValue : + never; +}; + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types */ +export type PropertyKeyValue = + | JSStringValue + | SymbolValue; + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types */ +export type PrimitiveValue = + | UndefinedValue + | NullValue + | BooleanValue + | JSStringValue + | SymbolValue + | NumberValue + | BigIntValue; + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types */ +export const PrimitiveValue = (() => { + type PrimValue = PrimitiveValue; + return (() => { + // NOTE: Using nested IIFE so that the class does not conflict with the type of the same name + // NOTE: Only using IIFE because TypeScript errors when `abstract` is used on class expressions + abstract class PrimitiveValue extends Value { + declare static [Symbol.hasInstance]: (value: unknown) => value is PrimValue; + } + return PrimitiveValue; + })(); +})(); + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type */ +export class UndefinedValue extends PrimitiveValue { + declare readonly type: 'Undefined'; // defined on prototype by static block + + declare readonly value: undefined; // defined on prototype by static block + + private constructor() { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(); + } + + static { + Object.defineProperty(this.prototype, 'type', { value: 'Undefined' }); + Object.defineProperty(this.prototype, 'value', { value: undefined }); + Object.defineProperty(Value, 'undefined', { value: new this() }); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is UndefinedValue; +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-null-type */ +export class NullValue extends PrimitiveValue { + declare readonly type: 'Null'; // defined on prototype by static block + + declare readonly value: null; // defined on prototype by static block + + private constructor() { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor + super(); + } + + static { + Object.defineProperty(this.prototype, 'type', { value: 'Null' }); + Object.defineProperty(this.prototype, 'value', { value: null }); + Object.defineProperty(Value, 'null', { value: new this() }); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is NullValue; +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-boolean-type */ +export class BooleanValue extends PrimitiveValue { + declare readonly type: 'Boolean'; // defined on prototype by static block + + readonly value: T; + + private constructor(value: T) { + super(); + this.value = value; + } + + booleanValue() { + return this.value; + } + + [Symbol.for('nodejs.util.inspect.custom')]() { + return `Boolean { ${this.value} }`; + } + + static { + Object.defineProperty(this.prototype, 'type', { value: 'Boolean' }); + Object.defineProperty(Value, 'true', { value: new this(true) }); + Object.defineProperty(Value, 'false', { value: new this(false) }); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is BooleanValue; +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type */ +export class JSStringValue extends PrimitiveValue { + declare readonly type: 'String'; // defined on prototype by static block + + readonly value: string; + + private constructor(value: string) { + super(); + this.value = value; + } + + stringValue() { + return this.value; + } + + static { + Object.defineProperty(this.prototype, 'type', { value: 'String' }); + createStringValue = (value) => new this(value); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is JSStringValue; +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type */ +export class SymbolValue extends PrimitiveValue { + declare readonly type: 'Symbol'; // defined on prototype by static block + + readonly Description: JSStringValue | UndefinedValue; + + constructor(Description: JSStringValue | UndefinedValue) { + super(); + this.Description = Description; + } + + static { + Object.defineProperty(this.prototype, 'type', { value: 'Symbol' }); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is SymbolValue; +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type */ +export const wellKnownSymbols = { + asyncIterator: new SymbolValue(Value('Symbol.asyncIterator')), + hasInstance: new SymbolValue(Value('Symbol.hasInstance')), + isConcatSpreadable: new SymbolValue(Value('Symbol.isConcatSpreadable')), + iterator: new SymbolValue(Value('Symbol.iterator')), + match: new SymbolValue(Value('Symbol.match')), + matchAll: new SymbolValue(Value('Symbol.matchAll')), + replace: new SymbolValue(Value('Symbol.replace')), + search: new SymbolValue(Value('Symbol.search')), + species: new SymbolValue(Value('Symbol.species')), + split: new SymbolValue(Value('Symbol.split')), + toPrimitive: new SymbolValue(Value('Symbol.toPrimitive')), + toStringTag: new SymbolValue(Value('Symbol.toStringTag')), + unscopables: new SymbolValue(Value('Symbol.unscopables')), +} as const; +Object.setPrototypeOf(wellKnownSymbols, null); +Object.freeze(wellKnownSymbols); + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type */ +export class NumberValue extends PrimitiveValue { + declare readonly type: 'Number'; // defined on prototype by static block + + readonly value: number; + + private constructor(value: number) { + super(); + this.value = value; + } + + numberValue() { + return this.value; + } + + isNaN() { + return Number.isNaN(this.value); + } + + isInfinity() { + return !Number.isFinite(this.value) && !this.isNaN(); + } + + isFinite() { + return Number.isFinite(this.value); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-unaryMinus */ + static unaryMinus(x: NumberValue) { + if (x.isNaN()) { + return F(NaN); + } + return F(-R(x)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseNOT */ + static bitwiseNOT(x: NumberValue) { + // 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 F(~R(oldValue)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate */ + static exponentiate(base: NumberValue, exponent: NumberValue) { + return F(R(base) ** R(exponent)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-multiply */ + static multiply(x: NumberValue, y: NumberValue) { + return F(R(x) * R(y)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-divide */ + static divide(x: NumberValue, y: NumberValue) { + return F(R(x) / R(y)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-remainder */ + static remainder(n: NumberValue, d: NumberValue) { + return F(R(n) % R(d)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-add */ + static add(x: NumberValue, y: NumberValue) { + return F(R(x) + R(y)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-subtract */ + static subtract(x: NumberValue, y: NumberValue) { + // The result of - operator is x + (-y). + return NumberValue.add(x, F(-R(y))); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-leftShift */ + static leftShift(x: NumberValue, y: NumberValue) { + // 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 = R(rnum) & 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 F(R(lnum) << shiftCount); // eslint-disable-line no-bitwise + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-signedRightShift */ + static signedRightShift(x: NumberValue, y: NumberValue) { + // 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 = R(rnum) & 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 F(R(lnum) >> shiftCount); // eslint-disable-line no-bitwise + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-unsignedRightShift */ + static unsignedRightShift(x: NumberValue, y: NumberValue) { + // 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 = R(rnum) & 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 F(R(lnum) >>> shiftCount); // eslint-disable-line no-bitwise + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-lessThan */ + static lessThan(x: NumberValue, y: NumberValue) { + 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 (R(x) === R(y)) { + return Value.false; + } + if (R(x) === +Infinity) { + return Value.false; + } + if (R(y) === +Infinity) { + return Value.true; + } + if (R(y) === -Infinity) { + return Value.false; + } + if (R(x) === -Infinity) { + return Value.true; + } + return R(x) < R(y) ? Value.true : Value.false; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-equal */ + static equal(x: NumberValue, y: NumberValue) { + if (x.isNaN()) { + return Value.false; + } + if (y.isNaN()) { + return Value.false; + } + const xVal = R(x); + const yVal = R(y); + 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; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-sameValue */ + static sameValue(x: NumberValue, y: NumberValue) { + if (x.isNaN() && y.isNaN()) { + return Value.true; + } + const xVal = R(x); + const yVal = R(y); + 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; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero */ + static sameValueZero(x: NumberValue, y: NumberValue) { + if (x.isNaN() && y.isNaN()) { + return Value.true; + } + const xVal = R(x); + const yVal = R(y); + 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; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseAND */ + static bitwiseAND(x: NumberValue, y: NumberValue) { + // 1. Return NumberBitwiseOp(&, x, y). + return NumberBitwiseOp('&', x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseXOR */ + static bitwiseXOR(x: NumberValue, y: NumberValue) { + // 1. Return NumberBitwiseOp(^, x, y). + return NumberBitwiseOp('^', x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseOR */ + static bitwiseOR(x: NumberValue, y: NumberValue) { + // 1. Return NumberBitwiseOp(|, x, y). + return NumberBitwiseOp('|', x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-number-tostring */ + static override toString(xV: NumberValue, radix: number): JSStringValue { + if (xV.isNaN()) { + return Value('NaN'); + } + const x = R(xV); + if (Object.is(x, -0) || Object.is(x, 0)) { + return Value('0'); + } + if (x < 0) { + return Value(`-${NumberValue.toString(F(-x), radix).stringValue()}`); + } + if (xV.isInfinity()) { + return Value('Infinity'); + } + // TODO: implement properly, currently depends on host. + return Value(`${x.toString(radix)}`); + } + + static readonly unit = new NumberValue(1); + + static { + Object.defineProperty(this.prototype, 'type', { value: 'Number' }); + createNumberValue = (value) => new NumberValue(value); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is NumberValue; +} + +/** https://tc39.es/ecma262/#sec-numberbitwiseop */ +function NumberBitwiseOp(op: '&' | '|' | '^', x: NumberValue, y: NumberValue) { + // 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 F(R(lnum) & R(rnum)); + case '|': + return F(R(lnum) | R(rnum)); + case '^': + return F(R(lnum) ^ R(rnum)); + default: + throw new OutOfRange('NumberBitwiseOp', op); + } +} + +/** https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type */ +export class BigIntValue extends PrimitiveValue { + declare readonly type: 'BigInt'; // defined on prototype by static block + + readonly value: bigint; + + private constructor(value: bigint) { + super(); + this.value = value; + } + + bigintValue() { + return this.value; + } + + isNaN() { + return false; + } + + isFinite() { + return true; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-unaryMinus */ + static unaryMinus(x: BigIntValue) { + if (R(x) === 0n) { + return Z(0n); + } + return Z(-R(x)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseNOT */ + static bitwiseNOT(x: BigIntValue) { + return Z(-R(x) - 1n); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-exponentiate */ + static exponentiate(base: BigIntValue, exponent: BigIntValue) { + // 1. If exponent < 0n, throw a RangeError exception. + if (R(exponent) < 0n) { + return Throw.RangeError('Exponent of bigint must be positive'); + } + // 2. If base is 0n and exponent is 0n, return 1n. + if (R(base) === 0n && R(exponent) === 0n) { + return Z(1n); + } + // 3. Return the BigInt value that represents the mathematical value of base raised to the power exponent. + return Z(R(base) ** R(exponent)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-multiply */ + static multiply(x: BigIntValue, y: BigIntValue) { + return Z(R(x) * R(y)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-divide */ + static divide(x: BigIntValue, y: BigIntValue) { + // 1. If y is 0n, throw a RangeError exception. + if (R(y) === 0n) { + return Throw.RangeError('Cannot divide by zero'); + } + // 2. Let quotient be the mathematical value of x divided by y. + const quotient = R(x) / R(y); + // 3. Return the BigInt value that represents quotient rounded towards 0 to the next integral value. + return Z(quotient); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-remainder */ + static remainder(n: BigIntValue, d: BigIntValue) { + // 1. If d is 0n, throw a RangeError exception. + if (R(d) === 0n) { + return Throw.RangeError('Cannot divide by zero'); + } + // 2. If n is 0n, return 0n. + if (R(n) === 0n) { + return Z(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 = Z(R(n) % R(d)); + // 4. Return r. + return r; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-add */ + static add(x: BigIntValue, y: BigIntValue) { + return Z(R(x) + R(y)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-subtract */ + static subtract(x: BigIntValue, y: BigIntValue) { + return Z(R(x) - R(y)); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-leftShift */ + static leftShift(x: BigIntValue, y: BigIntValue) { + return Z(R(x) << R(y)); // eslint-disable-line no-bitwise + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-signedRightShift */ + static signedRightShift(x: BigIntValue, y: BigIntValue) { + // 1. Return BigInt::leftShift(x, -y). + return BigIntValue.leftShift(x, Z(-R(y))); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-unsignedRightShift */ + static unsignedRightShift(_x: BigIntValue, _y: BigIntValue) { + return Throw.TypeError('BigInt has no unsigned right shift, use >> instead'); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-lessThan */ + static lessThan(x: BigIntValue, y: BigIntValue) { + return R(x) < R(y) ? Value.true : Value.false; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-equal */ + static equal(x: BigIntValue, y: BigIntValue) { + // Return true if x and y have the same mathematical integer value and false otherwise. + return R(x) === R(y) ? Value.true : Value.false; + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue */ + static sameValue(x: BigIntValue, y: BigIntValue) { + // 1. Return BigInt::equal(x, y). + return BigIntValue.equal(x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero */ + static sameValueZero(x: BigIntValue, y: BigIntValue) { + // 1. Return BigInt::equal(x, y). + return BigIntValue.equal(x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseAND */ + static bitwiseAND(x: BigIntValue, y: BigIntValue) { + // 1. Return BigIntBitwiseOp(&, x, y). + return BigIntBitwiseOp('&', x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseXOR */ + static bitwiseXOR(x: BigIntValue, y: BigIntValue) { + // 1. Return BigIntBitwiseOp(^, x, y). + return BigIntBitwiseOp('^', x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseOR */ + static bitwiseOR(x: BigIntValue, y: BigIntValue) { + // 1. Return BigIntBitwiseOp(|, x, y); + return BigIntBitwiseOp('|', x, y); + } + + /** https://tc39.es/ecma262/#sec-numeric-types-bigint-tostring */ + static override toString(x: BigIntValue, radix: number): JSStringValue { + // 1. If x is less than zero, return the string-concatenation of the String "-" and ! BigInt::toString(-x). + if (R(x) < 0n) { + const str = X(BigIntValue.toString(Z(-R(x)), radix)).stringValue(); + return Value(`-${str}`); + } + // 2. Return the String value consisting of the code units of the digits of the decimal representation of x. + return Value(`${R(x).toString(radix)}`); + } + + static readonly unit = new BigIntValue(1n); + + static { + Object.defineProperty(this.prototype, 'type', { value: 'BigInt' }); + createBigIntValue = (value) => new BigIntValue(value); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is BigIntValue; +} + +/** https://tc39.es/ecma262/#sec-bigintbitwiseop */ +function BigIntBitwiseOp(op: '&' | '|' | '^', x: BigIntValue, y: BigIntValue) { + // 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 Z(result); + */ + switch (op) { + case '&': + return Z(R(x) & R(y)); + case '|': + return Z(R(x) | R(y)); + case '^': + return Z(R(x) ^ R(y)); + default: + throw new OutOfRange('BigIntBitwiseOp', op); + } +} + +export interface ObjectInternalMethods { + GetPrototypeOf(this: Self): ValueEvaluator; + SetPrototypeOf(this: Self, V: ObjectValue | NullValue): ValueEvaluator; + IsExtensible(this: Self): ValueEvaluator; + PreventExtensions(this: Self): ValueEvaluator; + GetOwnProperty(this: Self, P: PropertyKeyValue): PlainEvaluator; + DefineOwnProperty(this: Self, P: PropertyKeyValue, Desc: Descriptor): ValueEvaluator; + HasProperty(this: Self, P: PropertyKeyValue): ValueEvaluator; + Get(this: Self, P: PropertyKeyValue, Receiver: Value): ValueEvaluator; + Set(this: Self, P: PropertyKeyValue, V: Value, Receiver: Value): ValueEvaluator; + Delete(this: Self, P: PropertyKeyValue): ValueEvaluator; + OwnPropertyKeys(this: Self): PlainEvaluator; + Call?(this: Self, thisArg: Value, args: Arguments): ValueEvaluator; + Construct?(this: Self, args: Arguments, newTarget: FunctionObject | UndefinedValue): ValueEvaluator; +} + +type ObjectSlotReturn = { + [key in keyof ObjectInternalMethods]: ReturnType[key]>> +}; +/** https://tc39.es/ecma262/#sec-object-type */ +export class ObjectValue extends Value implements ObjectInternalMethods { + declare readonly type: 'Object'; // defined on prototype by static block + + readonly properties: PropertyKeyMap; + + readonly internalSlotsList: readonly string[]; + + readonly PrivateElements: PrivateElementRecord[]; + + // https://tc39.es/proposal-pattern-matching/#sec-object-internal-methods-and-internal-slots + readonly ConstructedBy: (ECMAScriptFunctionObject | DefaultConstructorBuiltinFunction)[]; + + constructor(internalSlotsList: readonly string[]) { + super(); + + this.PrivateElements = []; + this.ConstructedBy = []; + this.properties = new PropertyKeyMap(); + this.internalSlotsList = internalSlotsList; + surroundingAgent.debugger_markObjectCreated(this); + } + + // UNSAFE casts below. Methods below are expected to be rewritten when the object is not an OrdinaryObject. (an example is ArgumentExoticObject) + // If those methods aren't rewritten, it is an error. + // eslint-disable-next-line require-yield + * GetPrototypeOf(): ObjectSlotReturn['GetPrototypeOf'] { + return OrdinaryGetPrototypeOf(this as unknown as OrdinaryObject); + } + + // eslint-disable-next-line require-yield + * SetPrototypeOf(V: ObjectValue | NullValue): ObjectSlotReturn['SetPrototypeOf'] { + Q(surroundingAgent.debugger_tryTouchDuringPreview(this)); + return OrdinarySetPrototypeOf(this as unknown as OrdinaryObject, V); + } + + // eslint-disable-next-line require-yield + * IsExtensible(): ObjectSlotReturn['IsExtensible'] { + return OrdinaryIsExtensible(this as unknown as OrdinaryObject); + } + + // eslint-disable-next-line require-yield + * PreventExtensions(): ObjectSlotReturn['PreventExtensions'] { + Q(surroundingAgent.debugger_tryTouchDuringPreview(this)); + return OrdinaryPreventExtensions(this as unknown as OrdinaryObject); + } + + // eslint-disable-next-line require-yield + * GetOwnProperty(P: PropertyKeyValue): ObjectSlotReturn['GetOwnProperty'] { + return OrdinaryGetOwnProperty(this as unknown as OrdinaryObject, P); + } + + * DefineOwnProperty(P: PropertyKeyValue, Desc: Descriptor): ObjectSlotReturn['DefineOwnProperty'] { + Q(surroundingAgent.debugger_tryTouchDuringPreview(this)); + return yield* OrdinaryDefineOwnProperty(this as unknown as OrdinaryObject, P, Desc); + } + + * HasProperty(P: PropertyKeyValue): ObjectSlotReturn['HasProperty'] { + return yield* OrdinaryHasProperty(this as unknown as OrdinaryObject, P); + } + + * Get(P: PropertyKeyValue, Receiver: Value): ObjectSlotReturn['Get'] { + return yield* OrdinaryGet(this as unknown as OrdinaryObject, P, Receiver); + } + + * Set(P: PropertyKeyValue, V: Value, Receiver: Value): ObjectSlotReturn['Set'] { + // TODO: + Q(surroundingAgent.debugger_tryTouchDuringPreview(Receiver as ObjectValue)); + return yield* OrdinarySet(this as unknown as OrdinaryObject, P, V, Receiver); + } + + * Delete(P: PropertyKeyValue): ObjectSlotReturn['Delete'] { + Q(surroundingAgent.debugger_tryTouchDuringPreview(this)); + return yield* OrdinaryDelete(this as unknown as OrdinaryObject, P); + } + + // eslint-disable-next-line require-yield + * OwnPropertyKeys(): ObjectSlotReturn['OwnPropertyKeys'] { + return OrdinaryOwnPropertyKeys(this as unknown as OrdinaryObject); + } + + // NON-SPEC + mark(m: GCMarker) { + m(this.properties); + this.internalSlotsList.forEach((s) => { + // @ts-ignore + m(this[s]); + if (s === 'HostCapturedValues' && s in this && Array.isArray(this[s])) { + this[s].forEach(m); + } + }); + } + + static { + Object.defineProperty(this.prototype, 'type', { value: 'Object' }); + } + + declare static [Symbol.hasInstance]: (value: unknown) => value is ObjectValue; +} + +/** https://tc39.es/ecma262/#sec-private-names */ +export class PrivateName { + // NOTE: The following declaration distinguishes `PrivateName` from `SymbolValue` so that type guards can properly + // remove it from unions with `SymbolValue` due to structural overlap. + declare private _: never; + + readonly Description: JSStringValue; + + constructor(description: JSStringValue) { + this.Description = description; + } +} + +export class ReferenceRecord { + readonly Base: 'unresolvable' | Value | EnvironmentRecord; + + ReferencedName: Value | PrivateName; + + readonly Strict: BooleanValue; + + readonly ThisValue: Value | undefined; + + constructor({ + Base, + ReferencedName, + Strict, + ThisValue, + }: Pick) { + this.Base = Base; + this.ReferencedName = ReferencedName; + this.Strict = Strict; + this.ThisValue = ThisValue; + } + + // NON-SPEC + mark(m: GCMarker) { + m(this.Base); + m(this.ReferencedName); + m(this.ThisValue); + } +} + +export type DescriptorInit = Pick; +// @ts-expect-error +export function Descriptor(O: DescriptorInit): Descriptor // @ts-expect-error +export @callable() class Descriptor { + readonly Value?: Value; + + readonly Get?: FunctionObject | UndefinedValue; + + readonly Set?: FunctionObject | UndefinedValue; + + readonly Writable?: BooleanValue; + + readonly Enumerable?: BooleanValue; + + readonly Configurable?: BooleanValue; + + constructor(O: Pick) { + this.Value = O.Value; + this.Get = O.Get; + this.Set = O.Set; + this.Writable = O.Writable; + this.Enumerable = O.Enumerable; + this.Configurable = O.Configurable; + } + + everyFieldIsAbsent() { + return this.Value === undefined + && this.Get === undefined + && this.Set === undefined + && this.Writable === undefined + && this.Enumerable === undefined + && this.Configurable === undefined; + } + + // NON-SPEC + mark(m: GCMarker) { + m(this.Value); + m(this.Get); + m(this.Set); + } +} + +export class DataBlock extends Uint8Array { + constructor(sizeOrBuffer: number | ArrayBuffer, byteOffset?: number, length?: number) { + if (sizeOrBuffer instanceof ArrayBuffer) { + super(sizeOrBuffer, byteOffset, length); + } else { + Assert(typeof sizeOrBuffer === 'number'); + super(sizeOrBuffer); + } + } +} + +/** https://tc39.es/ecma262/#sec-sametype */ +export function SameType(x: Value, y: Value) { + switch (true) { + case x === Value.undefined && y === Value.undefined: + case x === Value.null && y === Value.null: + case x instanceof BooleanValue && y instanceof BooleanValue: + case x instanceof NumberValue && y instanceof NumberValue: + case x instanceof BigIntValue && y instanceof BigIntValue: + case x instanceof SymbolValue && y instanceof SymbolValue: + case x instanceof JSStringValue && y instanceof JSStringValue: + case x instanceof ObjectValue && y instanceof ObjectValue: + return true; + default: + return false; + } +} + +type SafeAccessMethods = 'map' | 'values' | 'entries' | 'filter' | 'forEach' | 'find'; +// function* myFunction([callback]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator +// ^^^^^^^^ +// if user calls myFunction with no arguments, callback would be undefined, not Value.undefined +// the correct way is to type it as: +// function* myFunction([callback = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator +// +// this type is to prevent such mistakes +export type Arguments = + Omit & + Pick; +export interface FunctionCallContext { + readonly thisValue: Value; + readonly NewTarget: FunctionObject | UndefinedValue; +} +export interface NativeSteps { + (this: BuiltinFunctionObject, args: Arguments, context: FunctionCallContext): PlainEvaluator | PlainCompletion; + section?: string; + isConstructor?: boolean; +} +export interface CanBeNativeSteps { + (...args: (Value | undefined)[]): PlainEvaluator | PlainCompletion; +} diff --git a/test/base.mts b/test/base.mts new file mode 100644 index 0000000..70459b1 --- /dev/null +++ b/test/base.mts @@ -0,0 +1,177 @@ +import fs from 'node:fs'; +import { loadImportedModuleSync } from '../lib-src/node/module.mts'; +import { supportColor, type SkipReason } from './tui.mts'; +import { + Agent, ManagedRealm, type OrdinaryObject, SourceTextModuleRecord, OrdinaryObjectCreate, createTest262Intrinsics, + Value, +} from '#self'; + +export interface Attrs { + description: string; + features?: string[]; + includes: string[]; + flags: { + async?: boolean; + module?: boolean; + onlyStrict?: boolean; + noStrict?: boolean; + raw?: boolean; + }; + negative: { + type: string; + phase: string; + }; +} + +export class Test { + constructor(file: string, specifier: string, engineFeatures: readonly string[], attrs: Attrs, currentRunFlags: string, contents: string) { + this.file = file; + this.specifier = specifier; + this.engineFeatures = engineFeatures; + this.attrs = attrs; + this.content = contents; + this.currentTestFlag = currentRunFlags; + } + + startTime: number | null = null; + + endTime: number | null = null; + + getRuntimeSeconds(): number { + if (this.startTime === null) { + return 0; + } + return ~~((Date.now() - this.startTime) / 1000); + } + + id = Math.random(); + + file: string; + + specifier: string; + + attrs: Attrs; + + engineFeatures: readonly string[]; + + content: string; + + currentTestFlag: string; + + status: 'pending' | 'skipped' | 'running' | 'passed' | 'failed' = 'pending'; + + skipReason: SkipReason | null = null; + + skipFeature: string | null = null; + + withDifferentTestFlag(newFlag: string, newContent = this.content) { + return new Test(this.file, this.specifier, this.engineFeatures, this.attrs, newFlag, newContent); + } +} + +export type SupervisorToWorker = Exclude +export type WorkerToSupervisor_Running = { + status: 'RUNNING'; + testId: number; +}; + +export type WorkerToSupervisor_Pass = { + status: 'PASS'; + file: string; + flags: string; + testId: number; +}; + +export interface Stack { + specifier?: string | null | undefined; + source?: string; + line: number; + column: number; +} + +export type WorkerToSupervisor_Failed = { + status: 'FAIL'; + file: string; + flags: string; + testId: number; + description: string; + error: string; + stack: Stack[] +}; + +export type WorkerToSupervisor = + | WorkerToSupervisor_Running + | WorkerToSupervisor_Pass + | WorkerToSupervisor_Failed + +export function readList(path: string | URL) { + const source = fs.readFileSync(path, 'utf8'); + return source + .split('\n') + .filter((line) => line && !line.startsWith('#') && !line.startsWith(';')) + .map((line) => line.split('#')[0].split(';')[0].trim()); +} + +export interface CreateAgentOptions { + features?: readonly string[]; +} + +export function createAgent({ features = [] }: CreateAgentOptions) { + const agent = new Agent({ + features, + supportedImportAttributes: ['type'], + loadImportedModule: loadImportedModuleSync, + onDebugger() { + // attach an empty debugger to make sure our debugger infrastructure does not break the engine + agent.resumeEvaluate({ noBreakpoint: true }); + }, + }); + return agent; +} + +export interface Test262CreateRealm { + realm: ManagedRealm; + $262: OrdinaryObject; + resolverCache: Map; + setPrintHandle: (callback: ((str: string, value: Value) => void) | undefined) => void; +} +export interface CreateRealmOptions { + printCompatMode?: boolean; + specifier?: string; +} + +export function createRealm({ printCompatMode = false, specifier }: CreateRealmOptions = {}): Test262CreateRealm { + const resolverCache = new Map(); + + const realm = new ManagedRealm({ + resolverCache, + specifier, + }); + + return realm.scope(() => { + const $262 = OrdinaryObjectCreate(realm.Intrinsics['%Object.prototype%']); + const { setPrintHandle } = createTest262Intrinsics(realm, printCompatMode); + return { + realm, + $262, + resolverCache, + setPrintHandle, + }; + }); +} + +export function fatal_exit(message: string): never { + // eslint-disable-next-line no-console + console.error(message); + process.exit(1); +} + +export function link(text: string, url: string | URL) { + if (supportColor) { + const OSC = '\u001B]'; + const BEL = '\u0007'; + return `${OSC}8;;${url}${BEL}${text}${OSC}8;;${BEL}`; + } else { + return text; + } +} diff --git a/test/engine262/WeakRef.test.mts b/test/engine262/WeakRef.test.mts new file mode 100644 index 0000000..aa89cdd --- /dev/null +++ b/test/engine262/WeakRef.test.mts @@ -0,0 +1,77 @@ +import { expect, test } from 'vitest'; +import { + Agent, evalQ, Get, isPromiseObject, JSStringValue, ManagedRealm, NormalCompletion, setSurroundingAgent, + skipDebugger, + ThrowCompletion, + unwrapCompletion, + Value, + type PromiseObject, +} from '#self'; + +test('WeakRef (script)', () => { + const agent = new Agent(); + 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'); + `) as NormalCompletion; + expect(result).toBeInstanceOf(NormalCompletion); + expect(isPromiseObject(result.Value)).toBe(true); + expect(result.Value.PromiseState).toBe('fulfilled'); + if (!(result.Value.PromiseResult instanceof JSStringValue)) { + throw new Error('Expected JSStringValue'); + } + expect(result.Value.PromiseResult.stringValue()).toBe('pass'); +}); + +test('WeakRef (module)', () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + realm.scope(() => { + const module = realm.compileModule(` + 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'); + `, { specifier: 'test.mjs' }); + if (module instanceof ThrowCompletion) { + throw new Error('Module compilation failed'); + } + const completion = evalQ((_Q, X) => { + const m = X(module); + m.LoadRequestedModules(); + X(m.Link()); + skipDebugger(m.Evaluate()); + const result = X(skipDebugger(Get(realm.GlobalObject, Value('result')))) as PromiseObject; + expect(isPromiseObject(result)).toBe(true); + expect(result.PromiseState).toBe('fulfilled'); + if (!(result.PromiseResult instanceof JSStringValue)) { + throw new Error('Expected JSStringValue'); + } + expect(result.PromiseResult.stringValue()).toBe('pass'); + }); + unwrapCompletion(completion); + }); +}); diff --git a/test/engine262/debugger.test.mts b/test/engine262/debugger.test.mts new file mode 100644 index 0000000..0617ae6 --- /dev/null +++ b/test/engine262/debugger.test.mts @@ -0,0 +1,45 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable quotes */ +import { expect, test } from 'vitest'; +import { + Agent, evalQ, ManagedRealm, NormalCompletion, NumberValue, R, setSurroundingAgent, + surroundingAgent, + UndefinedValue, + Value, + type ValueCompletion, +} from '#self'; + +test('debugger statement should return undefined when no debugger is attached', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript('debugger;') as NormalCompletion; + expect(result).toBeInstanceOf(NormalCompletion); + expect(result.Value).toBe(Value.undefined); +}); + +test('debugger statement should return the value passed to resumeEvaluate', async () => { + const agent = new Agent({ + onDebugger() { + }, + }); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + evalQ((_Q, X) => { + const script = X(realm.compileScript('debugger;')); + let completion!: ValueCompletion; + realm.evaluate(script, (c) => { + completion = c; + }); + // start the evaluation + X(surroundingAgent.resumeEvaluate({})); + // paused at the debugger statement, resume with a value + X(surroundingAgent.resumeEvaluate({ + debuggerStatementCompletion: NormalCompletion(Value(42)), + })); + expect(completion).toBeDefined(); + const value = X(completion) as NumberValue; + expect(value).toBeInstanceOf(NumberValue); + expect(R(value)).toBe(42); + }); +}); diff --git a/test/engine262/error.test.mts b/test/engine262/error.test.mts new file mode 100644 index 0000000..8e05190 --- /dev/null +++ b/test/engine262/error.test.mts @@ -0,0 +1,72 @@ +import { expect, test } from 'vitest'; +import { + Agent, isPromiseObject, JSStringValue, ManagedRealm, NormalCompletion, setSurroundingAgent, + type PromiseObject, +} from '#self'; + +test('stack', () => { + 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; + } + `) as NormalCompletion; + expect(result).toBeInstanceOf(NormalCompletion); + expect(result.Value).toBeInstanceOf(JSStringValue); + expect(result.Value.stringValue()).toMatchInlineSnapshot(` + "Error: owo + at x (:2:36) + at y (:3:20) + at :5:7" + `); +}); + +test('async stack', () => { + 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); + `) as NormalCompletion; + expect(result).toBeInstanceOf(NormalCompletion); + expect(isPromiseObject(result.Value)).toBe(true); + expect(result.Value.PromiseState).toBe('fulfilled'); + if (!(result.Value.PromiseResult instanceof JSStringValue)) { + throw new Error('Expected JSStringValue'); + } + expect(result.Value.PromiseResult.stringValue()).toMatchInlineSnapshot(` + "Error: owo + at async x (:2:51) + at async y (:3:32)" + `); +}); + +test('native stack', () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + const result = realm.evaluateScript(` + function x() { Reflect.get(); } + try { + x(); + } catch (e) { + e.stack; + } + `) as NormalCompletion; + expect(result).toBeInstanceOf(NormalCompletion); + expect(result.Value).toBeInstanceOf(JSStringValue); + expect(result.Value.stringValue()).toMatchInlineSnapshot(` + "TypeError: undefined is not an object + at get (native) + at x (:2:20) + at :4:7" + `); +}); diff --git a/test/engine262/module.test.mts b/test/engine262/module.test.mts new file mode 100644 index 0000000..7399b81 --- /dev/null +++ b/test/engine262/module.test.mts @@ -0,0 +1,119 @@ +import { assert, expect, test } from 'vitest'; +import { + AbstractModuleRecord, Agent, Call, JSStringValue, ManagedRealm, NewPromiseCapability, NormalCompletion, PromiseCapabilityRecord, setSurroundingAgent, skipDebugger, Value, type PromiseObject, +} from '#self'; + +test('Import attributes', () => { + let attributes!: Map; + let calls = 0; + + const agent = new Agent({ + supportedImportAttributes: ['fruit', 'animal'], + loadImportedModule: (_referrer, _specifier, attrs, _hostDefined, finish) => { + calls += 1; + attributes = attrs; + finish(realm.compileModule('')); + }, + }); + setSurroundingAgent(agent); + const realm = new ManagedRealm(); + + realm.evaluateModule('import "test" with {}', 'case 1'); + expect([...attributes]).lengthOf(0); + + realm.evaluateModule('import "test" with { fruit: "banana" }', 'case 2'); + expect([...attributes]).deep.equal([['fruit', 'banana']]); + + realm.evaluateModule('import "test" with { fruit: "banana", animal: "monkey" }', 'case 3'); + expect([...attributes]).deep.equal([['animal', 'monkey'], ['fruit', 'banana']]); + + realm.evaluateModule('import "test" with { animal: "monkey", fruit: "banana" }', 'case 4'); + expect([...attributes]).deep.equal([['animal', 'monkey'], ['fruit', 'banana']]); + + calls = 0; + realm.evaluateModule('import "test" with { fruit: "banana" }; import "test" with { fruit: "banana" }', 'case 5'); + expect(calls).toBe(1); + + calls = 0; + realm.evaluateModule('import "test" with { fruit: "banana" }; import "test" with { animal: "monkey" }', 'case 6'); + expect(calls).toBe(2); + + calls = 0; + realm.evaluateModule('import "test" with { fruit: "banana", animal: "monkey" }; import "test" with { animal: "monkey", fruit: "banana" };', 'case 7'); + expect(calls).toBe(1); + + calls = 0; + realm.evaluateModule('import "test" with { animal: "monkey" }; import "test" with { animal: "elephant" };', 'case 8'); + expect(calls).toBe(2); + + calls = 0; + realm.evaluateModule('import "test"; import "test" with {};', 'case 9'); + expect(calls).toBe(1); +}); + +test('Custom module records', () => { + let evaluationPromise: PromiseObject; + + class CustomModuleRecord extends AbstractModuleRecord { + _pc(): PromiseCapabilityRecord { + const it = NewPromiseCapability(this.Realm.Intrinsics['%Promise%']); + const completion = skipDebugger(it) as NormalCompletion; + if (completion.Type !== 'normal') { + throw new Error('Expected normal completion'); + } + return completion.Value; + } + + override LoadRequestedModules(): PromiseObject { + const pc = this._pc(); + Call(pc.Resolve, Value.undefined, []); + return pc.Promise; + } + + override Link() {} + + override* Evaluate() { + const pc = this._pc(); + yield* Call(pc.Reject, Value.undefined, [Value('error!')]); + evaluationPromise = pc.Promise; + return evaluationPromise; + } + + override GetExportedNames(): readonly JSStringValue[] { + return []; + } + + override ResolveExport(): never { + throw new Error('Not implemented'); + } + } + + const agent = new Agent({ + loadImportedModule(referrer, specifier, _attributes, _hostDefined, finish) { + if (specifier !== 'dep') { + throw new Error('Invalid specifier'); + } + finish(new CustomModuleRecord({ + Realm: (referrer as AbstractModuleRecord).Realm, + Environment: undefined, + Namespace: undefined, + HostDefined: {}, + })); + }, + }); + setSurroundingAgent(agent); + + const calls: unknown[] = []; + + const realm = new ManagedRealm({ + promiseRejectionTracker(promise, operation) { + calls.push([promise, operation]); + }, + }); + + realm.evaluateModule('import "dep"', 'entrypoint'); + + assert(calls.length >= 2); // there is a third call, for the promise of the entrypoint + assert.deepStrictEqual(calls[0], [evaluationPromise!, 'reject'], "first call should be 'reject'"); + assert.deepStrictEqual(calls[1], [evaluationPromise!, 'handle'], "second call should be 'handle'"); +}); diff --git a/test/engine262/section.test.mts b/test/engine262/section.test.mts new file mode 100644 index 0000000..b17b18d --- /dev/null +++ b/test/engine262/section.test.mts @@ -0,0 +1,93 @@ +import { expect, test } from 'vitest'; +import { createAgent, createRealm } from '../base.mts'; +import { + CreateArrayFromList, CreateBuiltinFunction, CreateDataProperty, EnsureCompletion, FEATURES, NormalCompletion, setSurroundingAgent, skipDebugger, ToString, UndefinedValue, Value, type Arguments, +} from '#self'; + +test('Every built-in function should have a section property', () => { + const agent = createAgent({ + features: FEATURES.map((f) => f.name), + }); + setSurroundingAgent(agent); + const { realm } = createRealm(); + realm.scope(() => { + skipDebugger(CreateDataProperty( + realm.GlobalObject, + Value('fail'), + CreateBuiltinFunction(([path = Value.undefined]: Arguments) => { + const o = EnsureCompletion(skipDebugger(ToString(path))); + if (o.Type === 'throw') { + return o; + } + throw new Error(`${o.Value.stringValue()} did not have a section`); + }, 1, Value(''), []), + )); + const targets: Value[] = []; + Object.entries(realm.Intrinsics) + .forEach(([k, v]) => { + targets.push(CreateArrayFromList([Value(k), v])); + }); + skipDebugger(CreateDataProperty( + realm.GlobalObject, + 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]); + }); + } + `) as NormalCompletion; + expect(result).toBeInstanceOf(NormalCompletion); + expect(result.Value).toBe(Value.undefined); +}); diff --git a/test/eslint-plugin-engine262/index.mts b/test/eslint-plugin-engine262/index.mts new file mode 100644 index 0000000..2a952db --- /dev/null +++ b/test/eslint-plugin-engine262/index.mts @@ -0,0 +1,9 @@ +import mathematicalValue from './mathematical-value.mjs'; +import safeFunctionWithQ from './safe-function-with-q.mjs'; +import noFloatingGenerator from './no-floating-generator.mjs'; + +export const rules = { + 'mathematical-value': mathematicalValue, + 'safe-function-with-q': safeFunctionWithQ, + 'no-floating-generator': noFloatingGenerator, +}; diff --git a/test/eslint-plugin-engine262/mathematical-value.mts b/test/eslint-plugin-engine262/mathematical-value.mts new file mode 100644 index 0000000..4d03bd2 --- /dev/null +++ b/test/eslint-plugin-engine262/mathematical-value.mts @@ -0,0 +1,206 @@ +import path from 'node:path'; +import type { Rule, Scope } from 'eslint'; +import type * as ESTree from 'estree'; + +export default { + meta: { + fixable: 'code', + hasSuggestions: true, + }, + create(context) { + type FixableCallExpression = ESTree.CallExpression & { callee: ESTree.MemberExpression & { computed: false, property: ESTree.Identifier } }; + + let needsImportForR: { node: FixableCallExpression, fixable: boolean, reachable: boolean, name: string }[]; + let importSpecifiersForR: ESTree.ImportSpecifier[]; + let importForAllModule: ESTree.ImportDeclaration | undefined; + let importForSpecTypesModule: ESTree.ImportDeclaration | undefined; + let lastImport: ESTree.ImportDeclaration | undefined; + const pathToAbstractOps = `${path.resolve(context.cwd, 'src/abstract-ops').replaceAll('\\', '/')}/`; + const pathToSpecTypesModule = path.resolve(pathToAbstractOps, 'spec-types.mjs').replaceAll('\\', '/'); + const pathToAllModule = path.resolve(pathToAbstractOps, 'all.mjs').replaceAll('\\', '/'); + + return { + Program() { + needsImportForR = []; + importSpecifiersForR = []; + importForAllModule = undefined; + importForSpecTypesModule = undefined; + lastImport = undefined; + }, + 'Program:exit': function Program_exit(node) { + for (const importName of ['R', 'MathematicalValue']) { + const needsImportForRNonFixable = needsImportForR.filter((entry) => !entry.fixable && entry.reachable && entry.name === importName); + if (needsImportForRNonFixable.length) { + // If some calls aren't fixable and there are no imports of 'R', report the need to import 'R'. + // Include a fix, if possible. + const importNamePart = importName === 'R' ? 'R' : `R (imported as ${importName})`; + const importSpecifier = importName === 'R' ? 'R' : `R as ${importName}`; + const fixable = !lookup(context.sourceCode.getScope(node), importName); + const fix: Rule.ReportFixer = function* fix(fixer) { + if (importForSpecTypesModule) { + const last = importForSpecTypesModule.specifiers.at(-1)!; + yield fixer.insertTextAfter(last, `, ${importSpecifier}`); + } else if (importForAllModule) { + const last = importForAllModule.specifiers.at(-1)!; + yield fixer.insertTextAfter(last, `, ${importSpecifier}`); + } else { + const filename = path.resolve(context.filename).replaceAll('\\', '/'); + let relativePath; + if (filename.startsWith(pathToAbstractOps)) { + relativePath = path.relative(path.dirname(filename), pathToSpecTypesModule).replaceAll('\\', '/'); + } else { + relativePath = path.relative(path.dirname(filename), pathToAllModule).replaceAll('\\', '/'); + } + if (!path.isAbsolute(relativePath) + && !relativePath.startsWith('../') + && !relativePath.startsWith('./')) { + relativePath = `./${relativePath}`; + } + if (lastImport) { + yield fixer.insertTextAfter(lastImport, `\nimport { ${importSpecifier} } from ${JSON.stringify(relativePath)};`); + } else { + yield fixer.insertTextAfterRange([0, 0], `import { ${importSpecifier} } from ${JSON.stringify(relativePath)};\n`); + } + } + }; + + context.report({ + node: needsImportForRNonFixable[0].node, + message: `Import ${importNamePart} to convert mathematical values`, + fix: fixable ? fix : undefined, + }); + } + } + + for (const { node: callNode, fixable, name } of needsImportForR) { + const fix: Rule.ReportFixer = function* fix(fixer) { + // foo.numberValue() + // -> foo + yield fixer.removeRange([callNode.callee.object.range![1], callNode.range![1]]); + + // foo + // -> R(foo) + yield fixer.insertTextBefore(callNode, `${name}(`); + yield fixer.insertTextAfter(callNode, ')'); + }; + + // Report the need to use 'R'. Include a fix, if possible. + const namePart = name === 'R' ? 'R' : `R (imported as ${name})`; + const methodNamePart = callNode.callee.property.name; + context.report({ + node: callNode.callee, + message: `Use ${namePart}, not .${methodNamePart}(), to get a mathematical value`, + fix: fixable ? fix : undefined, + }); + } + }, + ImportDeclaration(node) { + lastImport = node; + switch (isImportOfRModule(node)) { + case 'spec-types': + importForSpecTypesModule ??= node; + break; + case 'all': + importForAllModule ??= node; + break; + default: + break; + } + }, + ImportSpecifier(node) { + if (isImportOfR(node)) { + importSpecifiersForR.push(node); + } + }, + CallExpression(node) { + if (node.callee.type === 'MemberExpression' + && node.callee.computed === false + && node.callee.property.type === 'Identifier') { + if (node.callee.property.name === 'numberValue' + || node.callee.property.name === 'bigintValue') { + const { fixable, reachable, name } = getUsableReferenceToR(context.sourceCode.getScope(node)); + needsImportForR.push({ + node: node as FixableCallExpression, fixable, reachable, name, + }); + } + } + }, + }; + + function lookup(scope: Scope.Scope | null, name: string) { + while (scope) { + const v = scope.set.get(name); + if (v) { + return v; + } + scope = scope.upper; + } + return undefined; + } + + function isImportOfRModule(node: ESTree.ImportDeclaration) { + if (!node.specifiers.length) { + // `import {} from ...` not currently usable + } + if (node.specifiers.length && node.specifiers[0].type === 'ImportNamespaceSpecifier') { + // `import * as ns from ...` not currently usable + return false; + } + if (node.specifiers.length && node.specifiers[0].type === 'ImportDefaultSpecifier') { + // `import X from ...` and `import X, {} from ...` not currently usable + return false; + } + const importPath = path.resolve(path.dirname(context.filename), node.source.value as string).replaceAll('\\', '/'); + if (importPath === pathToAllModule) { + return 'all'; + } else if (importPath === pathToSpecTypesModule) { + return 'spec-types'; + } + return false; + } + + function isImportOfR(node: ESTree.ImportSpecifier & Rule.NodeParentExtension) { + if ((node.imported as ESTree.Identifier).name === 'R' + && node.parent.type === 'ImportDeclaration') { + return !!isImportOfRModule(node.parent); + } + return false; + } + + function getUsableReferenceToR(scope: Scope.Scope) { + let candidate; + for (const spec of importSpecifiersForR) { + const varDecl = lookup(scope, spec.local.name); + if (varDecl?.defs.some((def) => def.type === 'ImportBinding' && def.node === spec)) { + if (spec.local.name === 'R') { + // prefer 'R' if it is found + return { fixable: true, reachable: true, name: spec.local.name }; + } + if (spec.local.name === 'MathematicalValue') { + candidate = 'MathematicalValue'; + } else { + candidate ??= spec.local.name; + } + } + } + + // if we found a candidate, return it + if (candidate) { + return { fixable: true, reachable: true, name: candidate }; + } + + // if no imports were found, try R + if (!lookup(scope, 'R')) { + return { fixable: false, reachable: true, name: 'R' }; + } + + // if R isn't reachable, try MathematicalValue + if (!lookup(scope, 'MathematicalValue')) { + return { fixable: false, reachable: true, name: 'MathematicalValue' }; + } + + // no imports were found or usable + return { fixable: false, reachable: false, name: 'R' }; + } + }, +} satisfies Rule.RuleModule; diff --git a/test/eslint-plugin-engine262/no-floating-generator.mts b/test/eslint-plugin-engine262/no-floating-generator.mts new file mode 100644 index 0000000..5207f02 --- /dev/null +++ b/test/eslint-plugin-engine262/no-floating-generator.mts @@ -0,0 +1,69 @@ +import type { Rule } from 'eslint'; +import type { ParserServices } from '@typescript-eslint/parser'; +// eslint-disable-next-line import/no-extraneous-dependencies +import ts from 'typescript'; + +declare module 'typescript' { + interface Type { + typeArguments?: ts.Type[]; + } +} + +const rule = { + meta: { + messages: { + floating: 'Generator is not stepped. It should be yield* evaluator', + }, + fixable: 'code', + }, + create(context) { + const services = getParserServices(context); + if (!services.program) { + throw new Error('No ts program found'); + } + const checker = services.program.getTypeChecker(); + const GeneratorSymbol = checker.resolveName('Generator', undefined, ts.SymbolFlags.Interface, false); + const AsyncGeneratorSymbol = checker.resolveName('AsyncGenerator', undefined, ts.SymbolFlags.Interface, false); + if (!GeneratorSymbol || !AsyncGeneratorSymbol) { + throw new Error('Cannot find necessary symbols'); + } + + return { + 'ExpressionStatement[expression.type="CallExpression"]': + (function VisitCallExpression({ expression }) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callNode = services.esTreeNodeToTSNodeMap.get(expression as any); + if (!ts.isCallExpression(callNode)) { + return; + } + const callType = checker.getTypeAtLocation(callNode); + if (callType?.getSymbol() === GeneratorSymbol) { + context.report({ + node: expression, + messageId: 'floating', + * fix(fixer) { + yield fixer.insertTextBefore(expression, 'yield* '); + }, + }); + } + } satisfies Rule.RuleListener['ExpressionStatement']), + }; + }, +} satisfies Rule.RuleModule; + +export default rule; + +function getParserServices(context: Rule.RuleContext): ParserServices { + if ( + context.sourceCode.parserServices?.esTreeNodeToTSNodeMap == null + || context.sourceCode.parserServices.tsNodeToESTreeNodeMap == null + ) { + throw new Error(); + } + + if (context.sourceCode.parserServices.program == null) { + throw new Error(); + } + + return context.sourceCode.parserServices; +} diff --git a/test/eslint-plugin-engine262/package.json b/test/eslint-plugin-engine262/package.json new file mode 100644 index 0000000..47b037e --- /dev/null +++ b/test/eslint-plugin-engine262/package.json @@ -0,0 +1,6 @@ +{ + "name": "@engine262/eslint-plugin", + "version": "0.0.0", + "main": "./lib/index.mjs", + "private": true +} diff --git a/test/eslint-plugin-engine262/safe-function-with-q.mts b/test/eslint-plugin-engine262/safe-function-with-q.mts new file mode 100644 index 0000000..e194862 --- /dev/null +++ b/test/eslint-plugin-engine262/safe-function-with-q.mts @@ -0,0 +1,155 @@ +import { resolve } from 'node:path'; +import type { Rule } from 'eslint'; +import type { ParserServices } from '@typescript-eslint/parser'; +import type { TSESTree } from '@typescript-eslint/types'; +import type * as ESTree from 'estree'; +// eslint-disable-next-line import/no-extraneous-dependencies +import ts from 'typescript'; + +const __dirname = import.meta.dirname; + +declare module 'typescript' { + interface Type { + typeArguments?: ts.Type[]; + } +} + +const rule = { + meta: { + messages: { + noAbruptCompletion: 'The function return type does not include AbruptCompletion', + noThrowCompletion: 'The function return type does not include ThrowCompletion', + noNeedToUseQ: 'Unnecessary Q() call', + evaluator: 'It should be Q(yield* evaluator) instead of Q(evaluator)', + }, + fixable: 'code', + }, + create(context) { + const services = getParserServices(context); + if (!services.program) { + throw new Error('No ts program found'); + } + const checker = services.program.getTypeChecker(); + const CompletionFile = services.program.getSourceFile(resolve(__dirname, '../../../src/completion.mts')); + const PromiseFile = services.program.getSourceFile(resolve(__dirname, '../../../src/intrinsics/Promise.mts')); + if (!CompletionFile || !PromiseFile) { + throw new Error('Cannot load src/completion.mts or src/intrinsics/Promise.mts'); + } + const AbruptCompletion = CompletionFile.statements.find((s) => ts.isTypeAliasDeclaration(s) && s.name.text === 'AbruptCompletion'); + const ThrowCompletion = CompletionFile.statements.find((s) => ts.isTypeAliasDeclaration(s) && s.name.text === 'ThrowCompletion'); + const PromiseObject = PromiseFile.statements.find((s) => ts.isInterfaceDeclaration(s) && s.name.text === 'PromiseObject'); + const GeneratorSymbol = checker.resolveName('Generator', undefined, ts.SymbolFlags.Interface, false); + if (!AbruptCompletion || !PromiseObject || !ThrowCompletion || !GeneratorSymbol) { + throw new Error('Cannot find necessary symbols'); + } + const AbruptCompletionType = checker.getTypeAtLocation(AbruptCompletion); + const ThrowCompletionType = checker.getTypeAtLocation(ThrowCompletion); + const PromiseObjectType = checker.getTypeAtLocation(PromiseObject); + const reported = new WeakSet(); + + return { + // eslint-disable-next-line func-names + "CallExpression[callee.name='Q'],[callee.name='ReturnIfAbrupt'],[callee.name='IfAbruptRejectPromise'],[callee.name='IfAbruptCloseIterator']": + (function (node) { // eslint-disable-line func-names + const firstArg = node.arguments[0]; + if (firstArg?.type === 'SpreadElement') { + return; + } + + const containingFunction = ts.findAncestor(services.esTreeNodeToTSNodeMap.get(node as TSESTree.Node), ts.isFunctionLike); + if (!containingFunction) { + throw new Error('Cannot find containing function'); + } + + const firstArgType = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(firstArg as TSESTree.Node)); + if (firstArgType?.getSymbol() === GeneratorSymbol) { + context.report({ + node, + messageId: 'evaluator', + * fix(fixer) { + yield fixer.insertTextBefore(firstArg, 'yield* '); + }, + }); + } + const containingFunctionType = checker.getTypeAtLocation(containingFunction); + if ((containingFunctionType.flags & ts.TypeFlags.Any) || (containingFunctionType.flags & ts.TypeFlags.Any)) { + throw new Error('Unexpected any'); + } + + let returnType = containingFunctionType.getCallSignatures().at(-1)?.getReturnType(); + if (ts.isMethodDeclaration(containingFunction) && ts.isIdentifier(containingFunction.name) && ts.isExpression(containingFunction.parent)) { + const contextualObjectType = checker.getContextualType(containingFunction.parent); + const currentFunctionName = containingFunction.name; + if (contextualObjectType) { + const contextualPropertySymbol = contextualObjectType.getProperty(currentFunctionName.text); + if (contextualPropertySymbol) { + let contextualPropertyType = checker.getTypeOfSymbol(contextualPropertySymbol); + if (contextualPropertyType.isUnion()) { + const excludeUndefined = contextualPropertyType.types.find((x) => x.flags & ~ts.TypeFlags.Undefined); + if (excludeUndefined) { + contextualPropertyType = excludeUndefined; + } + } + returnType = contextualPropertyType.getCallSignatures().at(-1)?.getReturnType(); + // returnType && console.log('returnType', checker.typeToString(returnType)); + } + } + } + // internal api. no api to insatiate the global Generator type. + if (returnType?.getSymbol() === GeneratorSymbol && returnType?.typeArguments?.[1]) { + returnType = returnType.typeArguments[1]; + } + if (!returnType) { + throw new Error('Cannot find return type'); + } + + const f = (node.callee as ESTree.Identifier).name; + let ExpectedReturnType; + // const ExpectedReturnType = f === 'IfAbruptRejectPromise' ? PromiseObjectType : AbruptCompletionType; + if (checker.isTypeAssignableTo(AbruptCompletionType, firstArgType)) { + ExpectedReturnType = AbruptCompletionType; + } + if (checker.isTypeAssignableTo(ThrowCompletionType, firstArgType)) { + ExpectedReturnType = ThrowCompletionType; + } + if (f === 'IfAbruptRejectPromise') { + ExpectedReturnType = PromiseObjectType; + } + if (!ExpectedReturnType) { + // context.report({ + // node, + // messageId: 'noNeedToUseQ', + // }); + return; + } + if (reported.has(containingFunction)) { + return; + } + reported.add(containingFunction); + if (!checker.isTypeAssignableTo(ExpectedReturnType, returnType)) { + context.report({ + node, + messageId: ExpectedReturnType === AbruptCompletionType ? 'noAbruptCompletion' : 'noThrowCompletion', + }); + } + } satisfies Rule.RuleListener['CallExpression']), + }; + }, +} satisfies Rule.RuleModule; + +export default rule; + +function getParserServices(context: Rule.RuleContext): ParserServices { + if ( + context.sourceCode.parserServices?.esTreeNodeToTSNodeMap == null + || context.sourceCode.parserServices.tsNodeToESTreeNodeMap == null + ) { + throw new Error(); + } + + if (context.sourceCode.parserServices.program == null) { + throw new Error(); + } + + return context.sourceCode.parserServices; +} diff --git a/test/eslint-plugin-engine262/tsconfig.json b/test/eslint-plugin-engine262/tsconfig.json new file mode 100644 index 0000000..c8b5224 --- /dev/null +++ b/test/eslint-plugin-engine262/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./lib", + "declaration": false, + "sourceMap": false, + "declarationMap": false, + "erasableSyntaxOnly": true, + "allowImportingTsExtensions": false, + "strict": false + }, + "include": ["./"] +} diff --git a/test/inspector/__snapshots__/console.test.mts.snap b/test/inspector/__snapshots__/console.test.mts.snap new file mode 100644 index 0000000..5306760 --- /dev/null +++ b/test/inspector/__snapshots__/console.test.mts.snap @@ -0,0 +1,71 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`preview evaluation > [] 1`] = ` +{ + "className": "Array", + "description": "Array(0)", + "objectId": "default:1", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`preview evaluation > [1, 2, 3].map(x => x + 1) 1`] = ` +{ + "className": "Array", + "description": "Array(3)", + "objectId": "default:4", + "preview": { + "description": "Array(3)", + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "2", + }, + { + "name": "1", + "type": "number", + "value": "3", + }, + { + "name": "2", + "type": "number", + "value": "4", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`preview evaluation > { let a = 1; a } 1`] = ` +{ + "description": "1", + "type": "number", + "value": 1, +} +`; + +exports[`preview evaluation > { var a = 1; a } 1`] = `"side effect"`; + +exports[`preview evaluation > 1 1`] = ` +{ + "description": "1", + "type": "number", + "value": 1, +} +`; + +exports[`preview evaluation > globalThis.x = 1 1`] = `"side effect"`; diff --git a/test/inspector/__snapshots__/source.test.mts.snap b/test/inspector/__snapshots__/source.test.mts.snap new file mode 100644 index 0000000..f3959a5 --- /dev/null +++ b/test/inspector/__snapshots__/source.test.mts.snap @@ -0,0 +1,95 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`code in ShadowRealm 1`] = ` +[ + [ + { + "columnNumber": 6, + "functionName": "", + "lineNumber": 0, + "scriptId": "1", + "url": "", + }, + undefined, + { + "columnNumber": 15, + "functionName": "", + "lineNumber": 1, + "scriptId": "0", + "url": "", + }, + ], +] +`; + +exports[`code in eval 1`] = ` +[ + [ + { + "columnNumber": 6, + "functionName": "", + "lineNumber": 0, + "scriptId": "1", + "url": "", + }, + { + "columnNumber": 5, + "functionName": "", + "lineNumber": 0, + "scriptId": "0", + "url": "", + }, + ], + [ + { + "columnNumber": 12, + "functionName": "f", + "lineNumber": 1, + "scriptId": "2", + "url": "", + }, + { + "columnNumber": 4, + "functionName": "", + "lineNumber": 3, + "scriptId": "2", + "url": "", + }, + { + "columnNumber": 5, + "functionName": "", + "lineNumber": 1, + "scriptId": "0", + "url": "", + }, + ], +] +`; + +exports[`code in new Function 1`] = ` +[ + [ + { + "columnNumber": 14, + "functionName": "x", + "lineNumber": 4, + "scriptId": "1", + "url": "", + }, + { + "columnNumber": 8, + "functionName": "y", + "lineNumber": 7, + "scriptId": "1", + "url": "", + }, + { + "columnNumber": 17, + "functionName": "", + "lineNumber": 1, + "scriptId": "0", + "url": "", + }, + ], +] +`; diff --git a/test/inspector/__snapshots__/toRemoteObject.test.mts.snap b/test/inspector/__snapshots__/toRemoteObject.test.mts.snap new file mode 100644 index 0000000..dfa4140 --- /dev/null +++ b/test/inspector/__snapshots__/toRemoteObject.test.mts.snap @@ -0,0 +1,4298 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`array > [,,,] 1`] = ` +{ + "className": "Array", + "description": "Array(3)", + "objectId": "default:5", + "preview": { + "description": "Array(3)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`array > [,,,] properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "length", + "set": undefined, + "symbol": undefined, + "value": { + "description": "3", + "type": "number", + "value": 3, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`array > [] 1`] = ` +{ + "className": "Array", + "description": "Array(0)", + "objectId": "default:1", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`array > [] properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "length", + "set": undefined, + "symbol": undefined, + "value": { + "description": "0", + "type": "number", + "value": 0, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`array > [1] 1`] = ` +{ + "className": "Array", + "description": "Array(1)", + "objectId": "default:3", + "preview": { + "description": "Array(1)", + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "1", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`array > [1] properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "length", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "0", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`array > Array(10) 1`] = ` +{ + "className": "Array", + "description": "Array(10)", + "objectId": "default:4", + "preview": { + "description": "Array(10)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`array > Array(10) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "length", + "set": undefined, + "symbol": undefined, + "value": { + "description": "10", + "type": "number", + "value": 10, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`array > var a = [1,,2]; a.x = 1; a 1`] = ` +{ + "className": "Array", + "description": "Array(3)", + "objectId": "default:6", + "preview": { + "description": "Array(3)", + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "1", + }, + { + "name": "2", + "type": "number", + "value": "2", + }, + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", +} +`; + +exports[`array > var a = [1,,2]; a.x = 1; a properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "length", + "set": undefined, + "symbol": undefined, + "value": { + "description": "3", + "type": "number", + "value": 3, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "0", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "2", + "set": undefined, + "symbol": undefined, + "value": { + "description": "2", + "type": "number", + "value": 2, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "x", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`array buffer > new ArrayBuffer(0) 1`] = ` +{ + "className": "ArrayBuffer", + "description": "ArrayBuffer(0)", + "objectId": "default:1", + "preview": { + "description": "ArrayBuffer(0)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "arraybuffer", + "type": "object", + }, + "subtype": "arraybuffer", + "type": "object", +} +`; + +exports[`array buffer > new ArrayBuffer(0) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[ArrayBufferByteLength]]", + "value": { + "type": "number", + "value": 0, + }, + }, + { + "name": "[[ArrayBufferData]]", + "value": { + "type": "number", + "value": 1, + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`array buffer > new ArrayBuffer(10) 1`] = ` +{ + "className": "ArrayBuffer", + "description": "ArrayBuffer(10)", + "objectId": "default:3", + "preview": { + "description": "ArrayBuffer(10)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "arraybuffer", + "type": "object", + }, + "subtype": "arraybuffer", + "type": "object", +} +`; + +exports[`array buffer > new ArrayBuffer(10) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[ArrayBufferByteLength]]", + "value": { + "type": "number", + "value": 10, + }, + }, + { + "name": "[[ArrayBufferData]]", + "value": { + "type": "number", + "value": 2, + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`array buffer > var x = new ArrayBuffer(10); x.a = 1; x 1`] = ` +{ + "className": "ArrayBuffer", + "description": "ArrayBuffer(10)", + "objectId": "default:4", + "preview": { + "description": "ArrayBuffer(10)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "1", + }, + ], + "subtype": "arraybuffer", + "type": "object", + }, + "subtype": "arraybuffer", + "type": "object", +} +`; + +exports[`array buffer > var x = new ArrayBuffer(10); x.a = 1; x properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[ArrayBufferByteLength]]", + "value": { + "type": "number", + "value": 10, + }, + }, + { + "name": "[[ArrayBufferData]]", + "value": { + "type": "number", + "value": 3, + }, + }, + ], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "a", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`data view > new DataView(new ArrayBuffer(0)) 1`] = ` +{ + "className": "DataView", + "description": "DataView(0)", + "objectId": "default:1", + "preview": { + "description": "DataView(0)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "dataview", + "type": "object", + }, + "subtype": "dataview", + "type": "object", +} +`; + +exports[`data view > new DataView(new ArrayBuffer(0)) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`data view > new DataView(new ArrayBuffer(10)) 1`] = ` +{ + "className": "DataView", + "description": "DataView(10)", + "objectId": "default:3", + "preview": { + "description": "DataView(10)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "dataview", + "type": "object", + }, + "subtype": "dataview", + "type": "object", +} +`; + +exports[`data view > new DataView(new ArrayBuffer(10)) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`data view > var x = new DataView(new ArrayBuffer(10), 0); x.a = 1; x 1`] = ` +{ + "className": "DataView", + "description": "DataView(10)", + "objectId": "default:4", + "preview": { + "description": "DataView(10)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "1", + }, + ], + "subtype": "dataview", + "type": "object", + }, + "subtype": "dataview", + "type": "object", +} +`; + +exports[`data view > var x = new DataView(new ArrayBuffer(10), 0); x.a = 1; x properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "a", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`date > new Date(-1) 1`] = ` +{ + "className": "Date", + "description": "1969-12-31T23:59:59.999Z", + "objectId": "default:3", + "preview": { + "description": "1969-12-31T23:59:59.999Z", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "date", + "type": "object", + }, + "subtype": "date", + "type": "object", +} +`; + +exports[`date > new Date(0) 1`] = ` +{ + "className": "Date", + "description": "1970-01-01T00:00:00.000Z", + "objectId": "default:1", + "preview": { + "description": "1970-01-01T00:00:00.000Z", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "date", + "type": "object", + }, + "subtype": "date", + "type": "object", +} +`; + +exports[`date > new Date(9999999999999) 1`] = ` +{ + "className": "Date", + "description": "2286-11-20T17:46:39.999Z", + "objectId": "default:4", + "preview": { + "description": "2286-11-20T17:46:39.999Z", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "date", + "type": "object", + }, + "subtype": "date", + "type": "object", +} +`; + +exports[`date > new Date(NaN) 1`] = ` +{ + "className": "Date", + "description": "Invalid Date", + "objectId": "default:2", + "preview": { + "description": "Invalid Date", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "date", + "type": "object", + }, + "subtype": "date", + "type": "object", +} +`; + +exports[`error > new (class MyError extends Error { constructor() { super(); this.message = "hello" } })() 1`] = ` +{ + "className": "SyntaxError", + "description": "Error + at new MyError (:1:52) + at :1:28", + "objectId": "default:6", + "preview": { + "description": "Error + at new MyError (:1:52) + at :1:28", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "hello", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", +} +`; + +exports[`error > new (class MyError extends Error {})() 1`] = ` +{ + "className": "SyntaxError", + "description": "Error + at new MyError (native) + at :1:28", + "objectId": "default:5", + "preview": { + "description": "Error + at new MyError (native) + at :1:28", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", +} +`; + +exports[`error > new Error("message") 1`] = ` +{ + "className": "SyntaxError", + "description": "Error: message + at :1:11", + "objectId": "default:2", + "preview": { + "description": "Error: message + at :1:11", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "message", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", +} +`; + +exports[`error > new Error("message", { cause: new Error("cause") }) 1`] = ` +{ + "className": "SyntaxError", + "description": "Error: message + at :1:41", + "objectId": "default:3", + "preview": { + "description": "Error: message + at :1:41", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "message", + }, + { + "name": "cause", + "subtype": "error", + "type": "object", + "value": "Error: cause + at :1:41", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", +} +`; + +exports[`error > new Error() 1`] = ` +{ + "className": "SyntaxError", + "description": "Error + at :1:5", + "objectId": "default:1", + "preview": { + "description": "Error + at :1:5", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", +} +`; + +exports[`error > new RangeError() 1`] = ` +{ + "className": "SyntaxError", + "description": "RangeError + at :1:5", + "objectId": "default:4", + "preview": { + "description": "RangeError + at :1:5", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", +} +`; + +exports[`functions > (() => { /* comment */ }) 1`] = ` +{ + "className": "Function", + "description": "() => { /* comment */ }", + "objectId": "default:13", + "type": "function", +} +`; + +exports[`functions > (() => 42) 1`] = ` +{ + "description": "() => 42", + "objectId": "default:14", + "type": "function", +} +`; + +exports[`functions > ({ *[Symbol.iterator]() {} })[Symbol.iterator] 1`] = ` +{ + "className": "GeneratorFunction", + "description": "*[Symbol.iterator]() {}", + "objectId": "default:18", + "type": "function", +} +`; + +exports[`functions > (async () => { /* comment */ }) 1`] = ` +{ + "className": "AsyncFunction", + "description": "async () => { /* comment */ }", + "objectId": "default:15", + "type": "function", +} +`; + +exports[`functions > (async () => 42) 1`] = ` +{ + "description": "async () => 42", + "objectId": "default:16", + "type": "function", +} +`; + +exports[`functions > (async function *f() { /* comment */ }) 1`] = ` +{ + "className": "AsyncGeneratorFunction", + "description": "async function *f() { /* comment */ }", + "objectId": "default:12", + "type": "function", +} +`; + +exports[`functions > (async function f() { /* comment */ }) 1`] = ` +{ + "className": "AsyncFunction", + "description": "async function f() { /* comment */ }", + "objectId": "default:10", + "type": "function", +} +`; + +exports[`functions > (async function* f() { /* comment */ }) 1`] = ` +{ + "className": "AsyncGeneratorFunction", + "description": "async function* f() { /* comment */ }", + "objectId": "default:11", + "type": "function", +} +`; + +exports[`functions > (function *f() { /* comment */ }) 1`] = ` +{ + "className": "GeneratorFunction", + "description": "function *f() { /* comment */ }", + "objectId": "default:9", + "type": "function", +} +`; + +exports[`functions > (function f() { /* comment */ }) 1`] = ` +{ + "className": "Function", + "description": "function f() { /* comment */ }", + "objectId": "default:7", + "type": "function", +} +`; + +exports[`functions > (function* f() { /* comment */ }) 1`] = ` +{ + "className": "GeneratorFunction", + "description": "function* f() { /* comment */ }", + "objectId": "default:8", + "type": "function", +} +`; + +exports[`functions > Array.prototype.map 1`] = ` +{ + "className": "Function", + "description": "function map() { [native code] }", + "objectId": "default:23", + "type": "function", +} +`; + +exports[`functions > Reflect.getOwnPropertyDescriptor(Function.prototype, "caller").get 1`] = ` +{ + "className": "Function", + "description": "function () { [native code] }", + "objectId": "default:24", + "type": "function", +} +`; + +exports[`functions > async function *f() { /* comment */ }; f 1`] = ` +{ + "className": "AsyncGeneratorFunction", + "description": "async function *f() { /* comment */ }", + "objectId": "default:6", + "type": "function", +} +`; + +exports[`functions > async function f() { /* comment */ }; f 1`] = ` +{ + "className": "AsyncFunction", + "description": "async function f() { /* comment */ }", + "objectId": "default:4", + "type": "function", +} +`; + +exports[`functions > async function* f() { /* comment */ }; f 1`] = ` +{ + "className": "AsyncGeneratorFunction", + "description": "async function* f() { /* comment */ }", + "objectId": "default:5", + "type": "function", +} +`; + +exports[`functions > class C { constructor() {}; #f }; C.prototype.constructor 1`] = ` +{ + "exceptionDetails": { + "columnNumber": 0, + "exception": { + "className": "SyntaxError", + "description": "SyntaxError: 'C' is already declared + at ", + "objectId": "default:27", + "preview": { + "description": "SyntaxError: 'C' is already declared + at ", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "'C' is already declared", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", + }, + "exceptionId": 26, + "lineNumber": 0, + "scriptId": "25", + "stackTrace": { + "callFrames": [ + { + "columnNumber": 0, + "functionName": "", + "lineNumber": 0, + "scriptId": "25", + "url": "", + }, + ], + }, + "text": "Uncaught", + "url": "", + }, + "result": { + "className": "SyntaxError", + "description": "SyntaxError: 'C' is already declared + at ", + "objectId": "default:27", + "preview": { + "description": "SyntaxError: 'C' is already declared + at ", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "'C' is already declared", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", + }, +} +`; + +exports[`functions > class C { static method() {} }; C.method 1`] = ` +{ + "className": "Function", + "description": "method() {}", + "objectId": "default:25", + "type": "function", +} +`; + +exports[`functions > function *f() { /* comment */ }; f 1`] = ` +{ + "className": "GeneratorFunction", + "description": "function *f() { /* comment */ }", + "objectId": "default:3", + "type": "function", +} +`; + +exports[`functions > function f() { /* comment */ }; f 1`] = ` +{ + "className": "Function", + "description": "function f() { /* comment */ }", + "objectId": "default:1", + "type": "function", +} +`; + +exports[`functions > function* f() { /* comment */ }; f 1`] = ` +{ + "className": "GeneratorFunction", + "description": "function* f() { /* comment */ }", + "objectId": "default:2", + "type": "function", +} +`; + +exports[`functions > var a = 1; ({ [a]() {} })[a] 1`] = ` +{ + "className": "Function", + "description": "[a]() {}", + "objectId": "default:17", + "type": "function", +} +`; + +exports[`functions > var o = { get [Symbol.iterator]() { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, Symbol.iterator).get 1`] = ` +{ + "className": "Function", + "description": "get [Symbol.iterator]() { /* comment */ }", + "objectId": "default:21", + "type": "function", +} +`; + +exports[`functions > var o = { get f() { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, "f").get 1`] = ` +{ + "className": "Function", + "description": "get f() { /* comment */ }", + "objectId": "default:19", + "type": "function", +} +`; + +exports[`functions > var o = { set [Symbol.iterator](v) { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, Symbol.iterator).set 1`] = ` +{ + "className": "Function", + "description": "set [Symbol.iterator](v) { /* comment */ }", + "objectId": "default:22", + "type": "function", +} +`; + +exports[`functions > var o = { set f(v) { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, "f").set 1`] = ` +{ + "className": "Function", + "description": "set f(v) { /* comment */ }", + "objectId": "default:20", + "type": "function", +} +`; + +exports[`map and set > new Map 1`] = ` +{ + "className": "Map", + "description": "Map(0)", + "objectId": "default:1", + "preview": { + "description": "Map(0)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "size", + "type": "number", + "value": "0", + }, + ], + "subtype": "map", + "type": "object", + }, + "subtype": "map", + "type": "object", +} +`; + +exports[`map and set > new Map properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(0)", + "objectId": "default:2", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new Map([["a", 1], ["b", 2]]) 1`] = ` +{ + "className": "Map", + "description": "Map(2)", + "objectId": "default:4", + "preview": { + "description": "Map(2)", + "entries": [ + { + "key": { + "description": "a", + "overflow": false, + "properties": [], + "type": "string", + }, + "value": { + "description": "1", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + { + "key": { + "description": "b", + "overflow": false, + "properties": [], + "type": "string", + }, + "value": { + "description": "2", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + ], + "overflow": false, + "properties": [ + { + "name": "size", + "type": "number", + "value": "2", + }, + ], + "subtype": "map", + "type": "object", + }, + "subtype": "map", + "type": "object", +} +`; + +exports[`map and set > new Map([["a", 1], ["b", 2]]) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(2)", + "objectId": "default:5", + "preview": { + "description": "Array(2)", + "overflow": false, + "properties": [ + { + "name": "0", + "subtype": "internal#entry", + "type": "object", + "value": "{a => 1}", + }, + { + "name": "1", + "subtype": "internal#entry", + "type": "object", + "value": "{b => 2}", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new Set 1`] = ` +{ + "className": "Set", + "description": "Set(0)", + "objectId": "default:8", + "preview": { + "description": "Set(0)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "size", + "type": "number", + "value": "0", + }, + ], + "subtype": "set", + "type": "object", + }, + "subtype": "set", + "type": "object", +} +`; + +exports[`map and set > new Set properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(0)", + "objectId": "default:9", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new Set(["a", 1, "b", 2]) 1`] = ` +{ + "className": "Set", + "description": "Set(4)", + "objectId": "default:11", + "preview": { + "description": "Set(4)", + "entries": [ + { + "value": { + "description": "a", + "overflow": false, + "properties": [], + "type": "string", + }, + }, + { + "value": { + "description": "1", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + { + "value": { + "description": "b", + "overflow": false, + "properties": [], + "type": "string", + }, + }, + { + "value": { + "description": "2", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + ], + "overflow": false, + "properties": [ + { + "name": "size", + "type": "number", + "value": "4", + }, + ], + "subtype": "set", + "type": "object", + }, + "subtype": "set", + "type": "object", +} +`; + +exports[`map and set > new Set(["a", 1, "b", 2]) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(4)", + "objectId": "default:12", + "preview": { + "description": "Array(4)", + "overflow": false, + "properties": [ + { + "name": "0", + "type": "string", + "value": "a", + }, + { + "name": "1", + "type": "number", + "value": "1", + }, + { + "name": "2", + "type": "string", + "value": "b", + }, + { + "name": "3", + "type": "number", + "value": "2", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new WeakMap 1`] = ` +{ + "className": "WeakMap", + "description": "WeakMap", + "objectId": "default:15", + "preview": { + "description": "WeakMap", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "weakmap", + "type": "object", + }, + "subtype": "weakmap", + "type": "object", +} +`; + +exports[`map and set > new WeakMap properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(0)", + "objectId": "default:16", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new WeakMap([[{}, 1], [{}, 2]]) 1`] = ` +{ + "className": "WeakMap", + "description": "WeakMap", + "objectId": "default:18", + "preview": { + "description": "WeakMap", + "entries": [ + { + "key": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "value": { + "description": "1", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + { + "key": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "value": { + "description": "2", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + ], + "overflow": false, + "properties": [], + "subtype": "weakmap", + "type": "object", + }, + "subtype": "weakmap", + "type": "object", +} +`; + +exports[`map and set > new WeakMap([[{}, 1], [{}, 2]]) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(2)", + "objectId": "default:19", + "preview": { + "description": "Array(2)", + "overflow": false, + "properties": [ + { + "name": "0", + "subtype": "internal#entry", + "type": "object", + "value": "{Object => 1}", + }, + { + "name": "1", + "subtype": "internal#entry", + "type": "object", + "value": "{Object => 2}", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new WeakSet 1`] = ` +{ + "className": "WeakSet", + "description": "WeakSet", + "objectId": "default:22", + "preview": { + "description": "WeakSet", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "weakset", + "type": "object", + }, + "subtype": "weakset", + "type": "object", +} +`; + +exports[`map and set > new WeakSet properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(0)", + "objectId": "default:23", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > new WeakSet([{}, {}]) 1`] = ` +{ + "className": "WeakSet", + "description": "WeakSet", + "objectId": "default:25", + "preview": { + "description": "WeakSet", + "entries": [ + { + "value": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + }, + { + "value": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + }, + ], + "overflow": false, + "properties": [], + "subtype": "weakset", + "type": "object", + }, + "subtype": "weakset", + "type": "object", +} +`; + +exports[`map and set > new WeakSet([{}, {}]) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(2)", + "objectId": "default:26", + "preview": { + "description": "Array(2)", + "overflow": false, + "properties": [ + { + "name": "0", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "1", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`map and set > var x = new Map([["a", 1], ["b", 2]]); x.x = 1; x 1`] = ` +{ + "className": "Map", + "description": "Map(2)", + "objectId": "default:6", + "preview": { + "description": "Map(2)", + "entries": [ + { + "key": { + "description": "a", + "overflow": false, + "properties": [], + "type": "string", + }, + "value": { + "description": "1", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + { + "key": { + "description": "b", + "overflow": false, + "properties": [], + "type": "string", + }, + "value": { + "description": "2", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + ], + "overflow": false, + "properties": [ + { + "name": "size", + "type": "number", + "value": "2", + }, + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": "map", + "type": "object", + }, + "subtype": "map", + "type": "object", +} +`; + +exports[`map and set > var x = new Map([["a", 1], ["b", 2]]); x.x = 1; x properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(2)", + "objectId": "default:7", + "preview": { + "description": "Array(2)", + "overflow": false, + "properties": [ + { + "name": "0", + "subtype": "internal#entry", + "type": "object", + "value": "{a => 1}", + }, + { + "name": "1", + "subtype": "internal#entry", + "type": "object", + "value": "{b => 2}", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "x", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`map and set > var x = new Set(["a", 1, "b", 2]); x.x = 1; x 1`] = ` +{ + "className": "Set", + "description": "Set(4)", + "objectId": "default:13", + "preview": { + "description": "Set(4)", + "entries": [ + { + "value": { + "description": "a", + "overflow": false, + "properties": [], + "type": "string", + }, + }, + { + "value": { + "description": "1", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + { + "value": { + "description": "b", + "overflow": false, + "properties": [], + "type": "string", + }, + }, + { + "value": { + "description": "2", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + ], + "overflow": false, + "properties": [ + { + "name": "size", + "type": "number", + "value": "4", + }, + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": "set", + "type": "object", + }, + "subtype": "set", + "type": "object", +} +`; + +exports[`map and set > var x = new Set(["a", 1, "b", 2]); x.x = 1; x properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(4)", + "objectId": "default:14", + "preview": { + "description": "Array(4)", + "overflow": false, + "properties": [ + { + "name": "0", + "type": "string", + "value": "a", + }, + { + "name": "1", + "type": "number", + "value": "1", + }, + { + "name": "2", + "type": "string", + "value": "b", + }, + { + "name": "3", + "type": "number", + "value": "2", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "x", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`map and set > var x = new WeakMap([[{}, 1], [{}, 2]]); x.x = 1; x 1`] = ` +{ + "className": "WeakMap", + "description": "WeakMap", + "objectId": "default:20", + "preview": { + "description": "WeakMap", + "entries": [ + { + "key": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "value": { + "description": "1", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + { + "key": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "value": { + "description": "2", + "overflow": false, + "properties": [], + "type": "number", + }, + }, + ], + "overflow": false, + "properties": [ + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": "weakmap", + "type": "object", + }, + "subtype": "weakmap", + "type": "object", +} +`; + +exports[`map and set > var x = new WeakMap([[{}, 1], [{}, 2]]); x.x = 1; x properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(2)", + "objectId": "default:21", + "preview": { + "description": "Array(2)", + "overflow": false, + "properties": [ + { + "name": "0", + "subtype": "internal#entry", + "type": "object", + "value": "{Object => 1}", + }, + { + "name": "1", + "subtype": "internal#entry", + "type": "object", + "value": "{Object => 2}", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "x", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`map and set > var x = new WeakSet([{}, {}]); x.x = 1; x 1`] = ` +{ + "className": "WeakSet", + "description": "WeakSet", + "objectId": "default:27", + "preview": { + "description": "WeakSet", + "entries": [ + { + "value": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + }, + { + "value": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + }, + ], + "overflow": false, + "properties": [ + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": "weakset", + "type": "object", + }, + "subtype": "weakset", + "type": "object", +} +`; + +exports[`map and set > var x = new WeakSet([{}, {}]); x.x = 1; x properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[Entries]]", + "value": { + "className": "Array", + "description": "Array(2)", + "objectId": "default:28", + "preview": { + "description": "Array(2)", + "overflow": false, + "properties": [ + { + "name": "0", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "1", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + ], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "x", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`module namespace > properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "Symbol(Symbol.toStringTag)", + "set": undefined, + "symbol": { + "description": "Symbol(Symbol.toStringTag)", + "objectId": "default:2", + "type": "symbol", + }, + "value": { + "type": "string", + "value": "Module", + }, + "wasThrown": false, + "writable": false, + }, + ], +} +`; + +exports[`module namespace > export const a = 1 1`] = ` +{ + "className": "Module", + "description": "Module", + "objectId": "default:3", + "preview": { + "description": "Module", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "Symbol(Symbol.toStringTag)", + "type": "string", + "value": "Module", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`module namespace > export const a = 1 properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "Symbol(Symbol.toStringTag)", + "set": undefined, + "symbol": { + "description": "Symbol(Symbol.toStringTag)", + "objectId": "default:2", + "type": "symbol", + }, + "value": { + "type": "string", + "value": "Module", + }, + "wasThrown": false, + "writable": false, + }, + ], +} +`; + +exports[`module namespace > export const b = 2; export { b as c } 1`] = ` +{ + "className": "Module", + "description": "Module", + "objectId": "default:4", + "preview": { + "description": "Module", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "Symbol(Symbol.toStringTag)", + "type": "string", + "value": "Module", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`module namespace > export const b = 2; export { b as c } properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "Symbol(Symbol.toStringTag)", + "set": undefined, + "symbol": { + "description": "Symbol(Symbol.toStringTag)", + "objectId": "default:2", + "type": "symbol", + }, + "value": { + "type": "string", + "value": "Module", + }, + "wasThrown": false, + "writable": false, + }, + ], +} +`; + +exports[`module namespace > export default 42 1`] = ` +{ + "className": "Module", + "description": "Module", + "objectId": "default:5", + "preview": { + "description": "Module", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "Symbol(Symbol.toStringTag)", + "type": "string", + "value": "Module", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`module namespace > export default 42 properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "Symbol(Symbol.toStringTag)", + "set": undefined, + "symbol": { + "description": "Symbol(Symbol.toStringTag)", + "objectId": "default:2", + "type": "symbol", + }, + "value": { + "type": "string", + "value": "Module", + }, + "wasThrown": false, + "writable": false, + }, + ], +} +`; + +exports[`module namespace > export default function() {} 1`] = ` +{ + "className": "Module", + "description": "Module", + "objectId": "default:6", + "preview": { + "description": "Module", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "Symbol(Symbol.toStringTag)", + "type": "string", + "value": "Module", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`module namespace > export default function() {} properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": false, + "enumerable": false, + "get": undefined, + "isOwn": true, + "name": "Symbol(Symbol.toStringTag)", + "set": undefined, + "symbol": { + "description": "Symbol(Symbol.toStringTag)", + "objectId": "default:2", + "type": "symbol", + }, + "value": { + "type": "string", + "value": "Module", + }, + "wasThrown": false, + "writable": false, + }, + ], +} +`; + +exports[`module namespace 1`] = ` +{ + "className": "Module", + "description": "Module", + "objectId": "default:1", + "preview": { + "description": "Module", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "Symbol(Symbol.toStringTag)", + "type": "string", + "value": "Module", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ [Symbol.iterator]: () => {} }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:8", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "Symbol(Symbol.iterator)", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ [Symbol.iterator]: () => {} }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "Symbol(Symbol.iterator)", + "set": undefined, + "symbol": { + "description": "Symbol(Symbol.iterator)", + "objectId": "default:10", + "type": "symbol", + }, + "value": { + "className": "Function", + "description": "() => {}", + "objectId": "default:9", + "type": "function", + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`normal object > ({ __proto__: { a: 1 } }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:6", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ __proto__: { a: 1 } }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`normal object > ({ __proto__: null }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:5", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ __proto__: null }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`normal object > ({ a: 1 }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:3", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "1", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ a: 1 }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "a", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`normal object > ({ a: 1, b: 2 }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:4", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "1", + }, + { + "name": "b", + "type": "number", + "value": "2", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ a: 1, b: 2 }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "a", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "b", + "set": undefined, + "symbol": undefined, + "value": { + "description": "2", + "type": "number", + "value": 2, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`normal object > ({ a: 1n, b: undefined, c: null, d: true, e: Symbol.iterator, f: [] }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:20", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "1n", + }, + { + "name": "b", + "type": "undefined", + "value": "undefined", + }, + { + "name": "c", + "subtype": "null", + "type": "object", + "value": "null", + }, + { + "name": "d", + "type": "boolean", + "value": "true", + }, + { + "name": "e", + "type": "symbol", + "value": "Symbol(Symbol.iterator)", + }, + { + "name": "f", + "subtype": "array", + "type": "object", + "value": "Array(0)", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ a: 1n, b: undefined, c: null, d: true, e: Symbol.iterator, f: [] }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "a", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1n", + "type": "bigint", + "unserializableValue": "1n", + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "b", + "set": undefined, + "symbol": undefined, + "value": { + "type": "undefined", + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "c", + "set": undefined, + "symbol": undefined, + "value": { + "subtype": "null", + "type": "object", + "value": null, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "d", + "set": undefined, + "symbol": undefined, + "value": { + "type": "boolean", + "value": true, + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "e", + "set": undefined, + "symbol": undefined, + "value": { + "description": "Symbol(Symbol.iterator)", + "objectId": "default:10", + "type": "symbol", + }, + "wasThrown": false, + "writable": true, + }, + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "f", + "set": undefined, + "symbol": undefined, + "value": { + "className": "Array", + "description": "Array(0)", + "objectId": "default:21", + "preview": { + "description": "Array(0)", + "overflow": false, + "properties": [], + "subtype": "array", + "type": "object", + }, + "subtype": "array", + "type": "object", + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`normal object > ({ f() {} }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:11", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "f", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ f() {} }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "f", + "set": undefined, + "symbol": undefined, + "value": { + "className": "Function", + "description": "f() {}", + "objectId": "default:12", + "type": "function", + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`normal object > ({ get f() {}, set f(v) {} }) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:13", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "f", + "type": "accessor", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({ get f() {}, set f(v) {} }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": { + "className": "Function", + "description": "get f() {}", + "objectId": "default:14", + "type": "function", + }, + "isOwn": true, + "name": "f", + "set": { + "className": "Function", + "description": "set f(v) {}", + "objectId": "default:15", + "type": "function", + }, + "symbol": undefined, + "value": undefined, + "wasThrown": false, + "writable": false, + }, + ], +} +`; + +exports[`normal object > ({}) 1`] = ` +{ + "className": "Object", + "description": "Object", + "objectId": "default:1", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > ({}) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`normal object > { class T { #priv = 1 }; new T } 1`] = ` +{ + "className": "Object", + "description": "T", + "objectId": "default:16", + "preview": { + "description": "T", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "#priv", + "type": "number", + "value": "1", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > { class T { #priv = 1 }; new T } properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [ + { + "get": undefined, + "name": "#priv", + "set": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + }, + ], + "result": [], +} +`; + +exports[`normal object > { class T { #priv = 1; normal = 2 }; new T } 1`] = ` +{ + "className": "Object", + "description": "T", + "objectId": "default:18", + "preview": { + "description": "T", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "normal", + "type": "number", + "value": "2", + }, + { + "name": "#priv", + "type": "number", + "value": "1", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`normal object > { class T { #priv = 1; normal = 2 }; new T } properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [ + { + "get": undefined, + "name": "#priv", + "set": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + }, + ], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "normal", + "set": undefined, + "symbol": undefined, + "value": { + "description": "2", + "type": "number", + "value": 2, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`primitive values > "engine262" 1`] = ` +{ + "type": "string", + "value": "engine262", +} +`; + +exports[`primitive values > -0 1`] = ` +{ + "description": "-0", + "type": "number", + "unserializableValue": "0", +} +`; + +exports[`primitive values > -42 1`] = ` +{ + "description": "-42", + "type": "number", + "value": -42, +} +`; + +exports[`primitive values > -42n 1`] = ` +{ + "description": "-42n", + "type": "bigint", + "unserializableValue": "-42n", +} +`; + +exports[`primitive values > -Infinity 1`] = ` +{ + "description": "-Infinity", + "type": "number", + "unserializableValue": "-Infinity", +} +`; + +exports[`primitive values > 0 1`] = ` +{ + "description": "0", + "type": "number", + "value": 0, +} +`; + +exports[`primitive values > 42 1`] = ` +{ + "description": "42", + "type": "number", + "value": 42, +} +`; + +exports[`primitive values > 42n 1`] = ` +{ + "description": "42n", + "type": "bigint", + "unserializableValue": "42n", +} +`; + +exports[`primitive values > Infinity 1`] = ` +{ + "description": "Infinity", + "type": "number", + "unserializableValue": "Infinity", +} +`; + +exports[`primitive values > NaN 1`] = ` +{ + "description": "NaN", + "type": "number", + "unserializableValue": "NaN", +} +`; + +exports[`primitive values > Symbol("desc") 1`] = ` +{ + "description": "Symbol(desc)", + "objectId": "default:2", + "type": "symbol", +} +`; + +exports[`primitive values > Symbol() 1`] = ` +{ + "description": "Symbol()", + "objectId": "default:1", + "type": "symbol", +} +`; + +exports[`primitive values > Symbol.for("symbol") 1`] = ` +{ + "description": "Symbol(symbol)", + "objectId": "default:3", + "type": "symbol", +} +`; + +exports[`primitive values > Symbol.iterator 1`] = ` +{ + "description": "Symbol(Symbol.iterator)", + "objectId": "default:4", + "type": "symbol", +} +`; + +exports[`primitive values > false 1`] = ` +{ + "type": "boolean", + "value": false, +} +`; + +exports[`primitive values > null 1`] = ` +{ + "subtype": "null", + "type": "object", + "value": null, +} +`; + +exports[`primitive values > true 1`] = ` +{ + "type": "boolean", + "value": true, +} +`; + +exports[`primitive values > undefined 1`] = ` +{ + "type": "undefined", +} +`; + +exports[`promise > Promise.reject() 1`] = ` +{ + "className": "Promise", + "description": "Promise", + "objectId": "default:7", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "rejected", + }, + { + "name": "[[PromiseResult]]", + "type": "undefined", + "value": "undefined", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", +} +`; + +exports[`promise > Promise.reject() properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[PromiseState]]", + "value": { + "type": "string", + "value": "rejected", + }, + }, + { + "name": "[[PromiseResult]]", + "value": { + "type": "undefined", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`promise > Promise.reject(42) 1`] = ` +{ + "className": "Promise", + "description": "Promise", + "objectId": "default:9", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "rejected", + }, + { + "name": "[[PromiseResult]]", + "type": "number", + "value": "42", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", +} +`; + +exports[`promise > Promise.reject(42) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[PromiseState]]", + "value": { + "type": "string", + "value": "rejected", + }, + }, + { + "name": "[[PromiseResult]]", + "value": { + "description": "42", + "type": "number", + "value": 42, + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`promise > Promise.resolve() 1`] = ` +{ + "className": "Promise", + "description": "Promise", + "objectId": "default:4", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "fulfilled", + }, + { + "name": "[[PromiseResult]]", + "type": "undefined", + "value": "undefined", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", +} +`; + +exports[`promise > Promise.resolve() properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[PromiseState]]", + "value": { + "type": "string", + "value": "fulfilled", + }, + }, + { + "name": "[[PromiseResult]]", + "value": { + "type": "undefined", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`promise > Promise.resolve(42) 1`] = ` +{ + "className": "Promise", + "description": "Promise", + "objectId": "default:5", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "fulfilled", + }, + { + "name": "[[PromiseResult]]", + "type": "number", + "value": "42", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", +} +`; + +exports[`promise > Promise.resolve(42) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[PromiseState]]", + "value": { + "type": "string", + "value": "fulfilled", + }, + }, + { + "name": "[[PromiseResult]]", + "value": { + "description": "42", + "type": "number", + "value": 42, + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`promise > new Promise(() => {}) 1`] = ` +{ + "className": "Promise", + "description": "Promise", + "objectId": "default:1", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "pending", + }, + { + "name": "[[PromiseResult]]", + "type": "undefined", + "value": "undefined", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", +} +`; + +exports[`promise > new Promise(() => {}) properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[PromiseState]]", + "value": { + "type": "string", + "value": "pending", + }, + }, + { + "name": "[[PromiseResult]]", + "value": { + "type": "undefined", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`promise > var a = new Promise(() => {}); a.x = 1; a 1`] = ` +{ + "className": "Promise", + "description": "Promise", + "objectId": "default:3", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "pending", + }, + { + "name": "[[PromiseResult]]", + "type": "undefined", + "value": "undefined", + }, + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", +} +`; + +exports[`promise > var a = new Promise(() => {}); a.x = 1; a properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[PromiseState]]", + "value": { + "type": "string", + "value": "pending", + }, + }, + { + "name": "[[PromiseResult]]", + "value": { + "type": "undefined", + }, + }, + ], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "x", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; + +exports[`proxy > new Proxy(() => {}, {}) 1`] = ` +{ + "className": "Proxy", + "description": "Proxy(Function)", + "objectId": "default:5", + "preview": { + "description": "Proxy(Function)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "proxy", + "type": "object", + }, + "subtype": "proxy", + "type": "object", +} +`; + +exports[`proxy > new Proxy(() => {}, {}) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`proxy > new Proxy({ a: 1 }, {}) 1`] = ` +{ + "className": "Proxy", + "description": "Proxy(Object)", + "objectId": "default:3", + "preview": { + "description": "Proxy(Object)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "proxy", + "type": "object", + }, + "subtype": "proxy", + "type": "object", +} +`; + +exports[`proxy > new Proxy({ a: 1 }, {}) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`proxy > new Proxy({}, { get: () => {} }) 1`] = ` +{ + "className": "Proxy", + "description": "Proxy(Object)", + "objectId": "default:2", + "preview": { + "description": "Proxy(Object)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "proxy", + "type": "object", + }, + "subtype": "proxy", + "type": "object", +} +`; + +exports[`proxy > new Proxy({}, { get: () => {} }) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`proxy > new Proxy({}, {}) 1`] = ` +{ + "className": "Proxy", + "description": "Proxy(Object)", + "objectId": "default:1", + "preview": { + "description": "Proxy(Object)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "proxy", + "type": "object", + }, + "subtype": "proxy", + "type": "object", +} +`; + +exports[`proxy > new Proxy({}, {}) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`proxy > new Proxy(Function, {}) 1`] = ` +{ + "className": "Proxy", + "description": "Proxy(Function)", + "objectId": "default:4", + "preview": { + "description": "Proxy(Function)", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "proxy", + "type": "object", + }, + "subtype": "proxy", + "type": "object", +} +`; + +exports[`proxy > new Proxy(Function, {}) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`proxy > var a = Proxy.revocable({}, {}); a.revoke(); a.proxy 1`] = ` +{ + "className": "Proxy", + "description": "Proxy", + "objectId": "default:6", + "preview": { + "description": "Proxy", + "entries": undefined, + "overflow": false, + "properties": [], + "subtype": "proxy", + "type": "object", + }, + "subtype": "proxy", + "type": "object", +} +`; + +exports[`proxy > var a = Proxy.revocable({}, {}); a.revoke(); a.proxy properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`regex > /cat/ 1`] = ` +{ + "className": "RegExp", + "description": "/cat/", + "objectId": "default:1", + "preview": { + "description": "/cat/", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "lastIndex", + "type": "number", + "value": "0", + }, + ], + "subtype": "regexp", + "type": "object", + }, + "subtype": "regexp", + "type": "object", +} +`; + +exports[`regex > /cat/g 1`] = ` +{ + "className": "RegExp", + "description": "/cat/g", + "objectId": "default:2", + "preview": { + "description": "/cat/g", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "lastIndex", + "type": "number", + "value": "0", + }, + ], + "subtype": "regexp", + "type": "object", + }, + "subtype": "regexp", + "type": "object", +} +`; + +exports[`regex > /cat/i 1`] = ` +{ + "className": "RegExp", + "description": "/cat/i", + "objectId": "default:3", + "preview": { + "description": "/cat/i", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "lastIndex", + "type": "number", + "value": "0", + }, + ], + "subtype": "regexp", + "type": "object", + }, + "subtype": "regexp", + "type": "object", +} +`; + +exports[`regex > var a = /cat/; a.lastIndex = 1; a 1`] = ` +{ + "className": "RegExp", + "description": "/cat/", + "objectId": "default:4", + "preview": { + "description": "/cat/", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "lastIndex", + "type": "number", + "value": "1", + }, + ], + "subtype": "regexp", + "type": "object", + }, + "subtype": "regexp", + "type": "object", +} +`; + +exports[`shadow realm > ShadowRealm function 1`] = ` +{ + "className": "Function", + "description": "() => {}", + "objectId": "default:4", + "type": "function", +} +`; + +exports[`shadow realm > new ShadowRealm 1`] = ` +{ + "className": "ShadowRealm", + "description": "ShadowRealm", + "objectId": "default:1", + "preview": { + "description": "ShadowRealm", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[GlobalObject]]", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", +} +`; + +exports[`shadow realm > new ShadowRealm properties 1`] = ` +{ + "internalProperties": [ + { + "name": "[[GlobalObject]]", + "value": { + "className": "Object", + "description": "Object", + "objectId": "default:2", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": true, + "properties": [ + { + "name": "Infinity", + "type": "number", + "value": "Infinity", + }, + { + "name": "NaN", + "type": "number", + "value": "NaN", + }, + { + "name": "undefined", + "type": "undefined", + "value": "undefined", + }, + { + "name": "globalThis", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "eval", + "type": "function", + "value": "", + }, + { + "name": "isFinite", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + }, + ], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > new Int32Array() 1`] = ` +{ + "className": "TypedArray", + "description": "Int32Array(0)", + "objectId": "default:6", + "preview": { + "description": "Int32Array(0)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(0)", + }, + { + "name": "byteLength", + "type": "number", + "value": "0", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "0", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > new Int32Array() properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > new Int32Array([1, 2, 3]) 1`] = ` +{ + "className": "TypedArray", + "description": "Int32Array(3)", + "objectId": "default:9", + "preview": { + "description": "Int32Array(3)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "1", + }, + { + "name": "1", + "type": "number", + "value": "2", + }, + { + "name": "2", + "type": "number", + "value": "3", + }, + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(12)", + }, + { + "name": "byteLength", + "type": "number", + "value": "3", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "3", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > new Int32Array([1, 2, 3]) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > new Int32Array(10) 1`] = ` +{ + "className": "TypedArray", + "description": "Int32Array(10)", + "objectId": "default:8", + "preview": { + "description": "Int32Array(10)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "0", + }, + { + "name": "1", + "type": "number", + "value": "0", + }, + { + "name": "2", + "type": "number", + "value": "0", + }, + { + "name": "3", + "type": "number", + "value": "0", + }, + { + "name": "4", + "type": "number", + "value": "0", + }, + { + "name": "5", + "type": "number", + "value": "0", + }, + { + "name": "6", + "type": "number", + "value": "0", + }, + { + "name": "7", + "type": "number", + "value": "0", + }, + { + "name": "8", + "type": "number", + "value": "0", + }, + { + "name": "9", + "type": "number", + "value": "0", + }, + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(40)", + }, + { + "name": "byteLength", + "type": "number", + "value": "10", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "10", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > new Int32Array(10) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > new Uint8Array() 1`] = ` +{ + "className": "TypedArray", + "description": "Uint8Array(0)", + "objectId": "default:1", + "preview": { + "description": "Uint8Array(0)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(0)", + }, + { + "name": "byteLength", + "type": "number", + "value": "0", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "0", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > new Uint8Array() properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > new Uint8Array([1, 2, 3]) 1`] = ` +{ + "className": "TypedArray", + "description": "Uint8Array(3)", + "objectId": "default:4", + "preview": { + "description": "Uint8Array(3)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "1", + }, + { + "name": "1", + "type": "number", + "value": "2", + }, + { + "name": "2", + "type": "number", + "value": "3", + }, + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(3)", + }, + { + "name": "byteLength", + "type": "number", + "value": "3", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "3", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > new Uint8Array([1, 2, 3]) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > new Uint8Array(10) 1`] = ` +{ + "className": "TypedArray", + "description": "Uint8Array(10)", + "objectId": "default:3", + "preview": { + "description": "Uint8Array(10)", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "0", + "type": "number", + "value": "0", + }, + { + "name": "1", + "type": "number", + "value": "0", + }, + { + "name": "2", + "type": "number", + "value": "0", + }, + { + "name": "3", + "type": "number", + "value": "0", + }, + { + "name": "4", + "type": "number", + "value": "0", + }, + { + "name": "5", + "type": "number", + "value": "0", + }, + { + "name": "6", + "type": "number", + "value": "0", + }, + { + "name": "7", + "type": "number", + "value": "0", + }, + { + "name": "8", + "type": "number", + "value": "0", + }, + { + "name": "9", + "type": "number", + "value": "0", + }, + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(10)", + }, + { + "name": "byteLength", + "type": "number", + "value": "10", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "10", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > new Uint8Array(10) properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [], +} +`; + +exports[`typed array > var x = new Uint8Array(10); x.a = 1; x 1`] = ` +{ + "className": "TypedArray", + "description": "Uint8Array(10)", + "objectId": "default:5", + "preview": { + "description": "Uint8Array(10)", + "entries": undefined, + "overflow": true, + "properties": [ + { + "name": "0", + "type": "number", + "value": "0", + }, + { + "name": "1", + "type": "number", + "value": "0", + }, + { + "name": "2", + "type": "number", + "value": "0", + }, + { + "name": "3", + "type": "number", + "value": "0", + }, + { + "name": "4", + "type": "number", + "value": "0", + }, + { + "name": "5", + "type": "number", + "value": "0", + }, + { + "name": "6", + "type": "number", + "value": "0", + }, + { + "name": "7", + "type": "number", + "value": "0", + }, + { + "name": "8", + "type": "number", + "value": "0", + }, + { + "name": "9", + "type": "number", + "value": "0", + }, + { + "name": "buffer", + "subtype": "arraybuffer", + "type": "object", + "value": "ArrayBuffer(10)", + }, + { + "name": "byteLength", + "type": "number", + "value": "10", + }, + { + "name": "byteOffset", + "type": "number", + "value": "0", + }, + { + "name": "length", + "type": "number", + "value": "10", + }, + ], + "subtype": "typedarray", + "type": "object", + }, + "subtype": "typedarray", + "type": "object", +} +`; + +exports[`typed array > var x = new Uint8Array(10); x.a = 1; x properties 1`] = ` +{ + "internalProperties": [], + "privateProperties": [], + "result": [ + { + "configurable": true, + "enumerable": true, + "get": undefined, + "isOwn": true, + "name": "a", + "set": undefined, + "symbol": undefined, + "value": { + "description": "1", + "type": "number", + "value": 1, + }, + "wasThrown": false, + "writable": true, + }, + ], +} +`; diff --git a/test/inspector/console.test.mts b/test/inspector/console.test.mts new file mode 100644 index 0000000..8204a77 --- /dev/null +++ b/test/inspector/console.test.mts @@ -0,0 +1,278 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable quotes */ +import { expect, test } from 'vitest'; +import { TestInspector } from './utils.mts'; +import { Agent, ManagedRealm, setSurroundingAgent } from '#self'; + +test('compile script (for invalid code break line)', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + const result = await inspector.runtime.compileScript({ + expression: 'function f() {', + persistScript: false, + sourceURL: '', + executionContextId: 0, + }); + expect(result).toMatchInlineSnapshot(` + { + "exceptionDetails": { + "columnNumber": 0, + "exception": { + "className": "SyntaxError", + "description": "SyntaxError: Unexpected end of input", + "objectId": "default:2", + "preview": { + "description": " + function f() { + ^ + SyntaxError: Unexpected end of source", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "Unexpected end of source", + }, + { + "name": "stack", + "type": "string", + "value": " + function f() { + ^ + SyntaxError: Unexpected end of source", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", + }, + "exceptionId": 1, + "lineNumber": 0, + "scriptId": undefined, + "stackTrace": { + "callFrames": [], + }, + "text": "Uncaught", + "url": undefined, + }, + } + `); +}); + +test('preview evaluation', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const code of [ + `1`, + `[]`, + `{ var a = 1; a }`, + `{ let a = 1; a }`, + `[1, 2, 3].map(x => x + 1)`, + `globalThis.x = 1`, + ]) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result: any = await inspector.perview(code); + if (result.exceptionDetails?.exception.preview.properties[0].value === 'Preview evaluator cannot evaluate side-effecting code') { + expect('side effect').toMatchSnapshot(code); + } else { + expect(result).toMatchSnapshot(code); + } + } +}); + +test('get local lexical names', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + expect(await inspector.runtime.globalLexicalScopeNames({ + executionContextId: 0, + })).toMatchInlineSnapshot(` + { + "names": [ + "Infinity", + "NaN", + "undefined", + "globalThis", + "eval", + "isFinite", + "isNaN", + "parseFloat", + "parseInt", + "decodeURI", + "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + "AggregateError", + "Array", + "ArrayBuffer", + "Boolean", + "BigInt", + "BigInt64Array", + "BigUint64Array", + "DataView", + "Date", + "Error", + "EvalError", + "FinalizationRegistry", + "Float32Array", + "Float64Array", + "Function", + "Int8Array", + "Int16Array", + "Int32Array", + "Iterator", + "Map", + "Number", + "Object", + "Promise", + "Proxy", + "RangeError", + "ReferenceError", + "RegExp", + "Set", + "ShadowRealm", + "String", + "Symbol", + "SyntaxError", + "TypeError", + "Uint8Array", + "Uint8ClampedArray", + "Uint16Array", + "Uint32Array", + "URIError", + "WeakMap", + "WeakRef", + "WeakSet", + "JSON", + "Math", + "Reflect", + ], + } + `); +}); + +test('call function on', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + inspector.flush(); + await inspector.eval('const a = { x: 1 }; a'); + expect(inspector.flush()).toMatchInlineSnapshot(` + [ + { + "method": "Debugger.scriptParsed", + "params": { + "buildId": "", + "endColumn": 21, + "endLine": 1, + "executionContextId": 0, + "hash": "", + "isModule": false, + "scriptId": "0", + "startColumn": 0, + "startLine": 0, + "url": "vm:///0", + }, + }, + { + "id": 0, + "method": "Runtime.evaluate", + "params": { + "expression": "const a = { x: 1 }; a", + "uniqueContextId": "0", + }, + }, + { + "id": 0, + "result": { + "exceptionDetails": undefined, + "result": { + "className": "Object", + "description": "Object", + "objectId": "default:1", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "x", + "type": "number", + "value": "1", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + }, + }, + ] + `); + expect(await inspector.runtime.callFunctionOn({ + functionDeclaration: 'function (x) { return this.x + x }', + arguments: [{ value: 1 }], + executionContextId: 0, + objectId: 'default:1', + })).toMatchInlineSnapshot(` + { + "description": "2", + "type": "number", + "value": 2, + } + `); + expect(await inspector.runtime.callFunctionOn({ + functionDeclaration: 'function (x) { return [this.x, x, 2, 3] }', + arguments: [{ value: 1 }], + executionContextId: 0, + objectId: 'default:1', + returnByValue: true, + })).toMatchInlineSnapshot(` + { + "type": "object", + "value": [ + 1, + 1, + 2, + 3, + ], + } + `); +}); + +test('private field jailbreak', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + await inspector.eval('class A { #x = 1; }; globalThis.a = new A();'); + await inspector.debugger.engine262_setEvaluateMode({ mode: 'console' }); + expect(await inspector.runtime.evaluate({ expression: 'a.#x', uniqueContextId: '0' })).toMatchInlineSnapshot(` + { + "description": "1", + "type": "number", + "value": 1, + } + `); +}); diff --git a/test/inspector/debugger.test.mts b/test/inspector/debugger.test.mts new file mode 100644 index 0000000..6148e20 --- /dev/null +++ b/test/inspector/debugger.test.mts @@ -0,0 +1,489 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable quotes */ +import { expect, test } from 'vitest'; +import { TestInspector } from './utils.mts'; +import { Agent, ManagedRealm, setSurroundingAgent } from '#self'; + +test('evaluate on frame', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + const paused = inspector.eval(` + 'use strict'; + function f() { + const a = 1; + debugger; + } + function y() { + const a = 0; + f(); + } + y(); + `); + expect(inspector.flush()).toMatchInlineSnapshot(` + [ + { + "method": "Runtime.executionContextCreated", + "params": { + "context": { + "id": 0, + "name": "engine262", + "origin": "vm://repl", + "uniqueId": "0", + }, + }, + }, + { + "method": "Debugger.scriptParsed", + "params": { + "buildId": "", + "endColumn": 2, + "endLine": 12, + "executionContextId": 0, + "hash": "", + "isModule": false, + "scriptId": "0", + "startColumn": 0, + "startLine": 0, + "url": "vm:///0", + }, + }, + { + "method": "Debugger.paused", + "params": { + "callFrames": [ + { + "callFrameId": "3", + "canBeRestarted": false, + "functionLocation": { + "columnNumber": 17, + "lineNumber": 2, + "scriptId": "0", + }, + "functionName": "f", + "location": { + "columnNumber": 6, + "lineNumber": 4, + "scriptId": "0", + }, + "scopeChain": [ + { + "object": { + "className": "Object", + "description": "Object", + "objectId": "default:1", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "1", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + "type": "local", + }, + { + "object": { + "className": "Object", + "description": "Object", + "objectId": "default:2", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": true, + "properties": [ + { + "name": "Infinity", + "type": "number", + "value": "Infinity", + }, + { + "name": "NaN", + "type": "number", + "value": "NaN", + }, + { + "name": "undefined", + "type": "undefined", + "value": "undefined", + }, + { + "name": "globalThis", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "eval", + "type": "function", + "value": "", + }, + { + "name": "isFinite", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + "type": "global", + }, + ], + "this": { + "type": "undefined", + }, + "url": "", + }, + { + "callFrameId": "2", + "canBeRestarted": false, + "functionLocation": { + "columnNumber": 17, + "lineNumber": 6, + "scriptId": "0", + }, + "functionName": "y", + "location": { + "columnNumber": 6, + "lineNumber": 8, + "scriptId": "0", + }, + "scopeChain": [ + { + "object": { + "className": "Object", + "description": "Object", + "objectId": "default:3", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "a", + "type": "number", + "value": "0", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + "type": "local", + }, + { + "object": { + "className": "Object", + "description": "Object", + "objectId": "default:2", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": true, + "properties": [ + { + "name": "Infinity", + "type": "number", + "value": "Infinity", + }, + { + "name": "NaN", + "type": "number", + "value": "NaN", + }, + { + "name": "undefined", + "type": "undefined", + "value": "undefined", + }, + { + "name": "globalThis", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "eval", + "type": "function", + "value": "", + }, + { + "name": "isFinite", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + "type": "global", + }, + ], + "this": { + "type": "undefined", + }, + "url": "", + }, + { + "callFrameId": "1", + "canBeRestarted": false, + "functionLocation": undefined, + "functionName": "", + "location": { + "columnNumber": 4, + "lineNumber": 10, + "scriptId": "0", + }, + "scopeChain": [ + { + "object": { + "className": "Object", + "description": "Object", + "objectId": "default:2", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": true, + "properties": [ + { + "name": "Infinity", + "type": "number", + "value": "Infinity", + }, + { + "name": "NaN", + "type": "number", + "value": "NaN", + }, + { + "name": "undefined", + "type": "undefined", + "value": "undefined", + }, + { + "name": "globalThis", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "eval", + "type": "function", + "value": "", + }, + { + "name": "isFinite", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + "type": "global", + }, + ], + "this": { + "className": "Object", + "description": "Object", + "objectId": "default:2", + "preview": { + "description": "Object", + "entries": undefined, + "overflow": true, + "properties": [ + { + "name": "Infinity", + "type": "number", + "value": "Infinity", + }, + { + "name": "NaN", + "type": "number", + "value": "NaN", + }, + { + "name": "undefined", + "type": "undefined", + "value": "undefined", + }, + { + "name": "globalThis", + "subtype": undefined, + "type": "object", + "value": "Object", + }, + { + "name": "eval", + "type": "function", + "value": "", + }, + { + "name": "isFinite", + "type": "function", + "value": "", + }, + ], + "subtype": undefined, + "type": "object", + }, + "subtype": undefined, + "type": "object", + }, + "url": "", + }, + ], + "reason": "debugCommand", + }, + }, + { + "id": 0, + "method": "Runtime.evaluate", + "params": { + "expression": " + 'use strict'; + function f() { + const a = 1; + debugger; + } + function y() { + const a = 0; + f(); + } + y(); + ", + "uniqueContextId": "0", + }, + }, + ] + `); + expect(await inspector.debugger.evaluateOnCallFrame({ + callFrameId: "3", + expression: 'a', + })).toMatchInlineSnapshot(` + { + "description": "1", + "type": "number", + "value": 1, + } + `); + expect(await inspector.debugger.evaluateOnCallFrame({ + callFrameId: "2", + expression: 'a', + })).toMatchInlineSnapshot(` + { + "description": "0", + "type": "number", + "value": 0, + } + `); + expect(await inspector.debugger.evaluateOnCallFrame({ + callFrameId: "1", + expression: 'a', + })).toMatchInlineSnapshot(` + { + "exceptionDetails": { + "columnNumber": 0, + "exception": { + "className": "SyntaxError", + "description": "ReferenceError: 'a' is not defined + at :1:1 + at :11:5", + "objectId": "default:5", + "preview": { + "description": "ReferenceError: 'a' is not defined + at :1:1 + at :11:5", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "'a' is not defined", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", + }, + "exceptionId": 4, + "lineNumber": 0, + "scriptId": "0", + "stackTrace": { + "callFrames": [ + { + "columnNumber": 0, + "functionName": "", + "lineNumber": 0, + "scriptId": "0", + "url": "", + }, + { + "columnNumber": 4, + "functionName": "", + "lineNumber": 10, + "scriptId": "0", + "url": "", + }, + ], + }, + "text": "Uncaught", + "url": "", + }, + "result": { + "className": "SyntaxError", + "description": "ReferenceError: 'a' is not defined + at :1:1 + at :11:5", + "objectId": "default:5", + "preview": { + "description": "ReferenceError: 'a' is not defined + at :1:1 + at :11:5", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "message", + "type": "string", + "value": "'a' is not defined", + }, + ], + "subtype": "error", + "type": "object", + }, + "subtype": "error", + "type": "object", + }, + } + `); + await inspector.debugger.resume(); + await paused; +}); diff --git a/test/inspector/reports.test.mts b/test/inspector/reports.test.mts new file mode 100644 index 0000000..75ff6ef --- /dev/null +++ b/test/inspector/reports.test.mts @@ -0,0 +1,224 @@ +import { expect, test } from 'vitest'; +import { TestInspector } from './utils.mts'; +import { + Agent, ManagedRealm, runJobQueue, setSurroundingAgent, +} from '#self'; +import { createConsole } from '#self/inspector'; + +test('console', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + let count = 0; + createConsole(realm, { + log(args) { + count += args.length; + }, + }); + + inspector.flush(); + await inspector.eval('console.log("hello", "world")'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const a: any = inspector.flush(); + a[1].params.timestamp = 0; + expect(a).toMatchInlineSnapshot(` + [ + { + "method": "Debugger.scriptParsed", + "params": { + "buildId": "", + "endColumn": 29, + "endLine": 1, + "executionContextId": 0, + "hash": "", + "isModule": false, + "scriptId": "0", + "startColumn": 0, + "startLine": 0, + "url": "vm:///0", + }, + }, + { + "method": "Runtime.consoleAPICalled", + "params": { + "args": [ + { + "type": "string", + "value": "hello", + }, + { + "type": "string", + "value": "world", + }, + ], + "executionContextId": 0, + "timestamp": 0, + "type": "log", + }, + }, + { + "id": 0, + "method": "Runtime.evaluate", + "params": { + "expression": "console.log("hello", "world")", + "uniqueContextId": "0", + }, + }, + { + "id": 0, + "result": { + "exceptionDetails": undefined, + "result": { + "type": "undefined", + }, + }, + }, + ] + `); + expect(count).eq(2); + + await inspector.perview('console.log("hello", "world")'); + expect(count).eq(2); +}); + +test('unhandled promise rejection', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + inspector.flush(); + await inspector.eval('var a = Promise.reject(new Error())'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const i: any = inspector.flush(); + i[1].params.timestamp = 0; + expect(i).toMatchInlineSnapshot(` + [ + { + "method": "Debugger.scriptParsed", + "params": { + "buildId": "", + "endColumn": 35, + "endLine": 1, + "executionContextId": 0, + "hash": "", + "isModule": false, + "scriptId": "0", + "startColumn": 0, + "startLine": 0, + "url": "vm:///0", + }, + }, + { + "method": "Runtime.exceptionThrown", + "params": { + "exceptionDetails": { + "columnNumber": 0, + "exception": { + "className": "Promise", + "description": "Promise", + "objectId": "default:2", + "preview": { + "description": "Promise", + "entries": undefined, + "overflow": false, + "properties": [ + { + "name": "[[PromiseState]]", + "type": "string", + "value": "rejected", + }, + { + "name": "[[PromiseResult]]", + "subtype": "error", + "type": "object", + "value": "Error + at :1:28", + }, + ], + "subtype": "promise", + "type": "object", + }, + "subtype": "promise", + "type": "object", + }, + "exceptionId": 1, + "lineNumber": 0, + "scriptId": undefined, + "stackTrace": undefined, + "text": "Uncaught (in promise)", + "url": undefined, + }, + "timestamp": 0, + }, + }, + { + "id": 0, + "method": "Runtime.evaluate", + "params": { + "expression": "var a = Promise.reject(new Error())", + "uniqueContextId": "0", + }, + }, + { + "id": 0, + "result": { + "exceptionDetails": undefined, + "result": { + "type": "undefined", + }, + }, + }, + ] + `); + + await inspector.eval('void a.catch(() => {});'); + runJobQueue(); + expect(inspector.flush()).toMatchInlineSnapshot(` + [ + { + "method": "Debugger.scriptParsed", + "params": { + "buildId": "", + "endColumn": 23, + "endLine": 1, + "executionContextId": 0, + "hash": "", + "isModule": false, + "scriptId": "1", + "startColumn": 0, + "startLine": 0, + "url": "vm:///1", + }, + }, + { + "method": "Runtime.exceptionRevoked", + "params": { + "exceptionId": 1, + "reason": "Handler added to rejected promise", + }, + }, + { + "id": 1, + "method": "Runtime.evaluate", + "params": { + "expression": "void a.catch(() => {});", + "uniqueContextId": "0", + }, + }, + { + "id": 1, + "result": { + "exceptionDetails": undefined, + "result": { + "type": "undefined", + }, + }, + }, + ] + `); +}); diff --git a/test/inspector/source.test.mts b/test/inspector/source.test.mts new file mode 100644 index 0000000..ad4e5ad --- /dev/null +++ b/test/inspector/source.test.mts @@ -0,0 +1,90 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable quotes */ +import { expect, test } from 'vitest'; +import { TestInspector } from './utils.mts'; +import { + Agent, Construct, CreateBuiltinFunction, Descriptor, evalQ, getHostDefinedErrorStack, ManagedRealm, setSurroundingAgent, + surroundingAgent, + Value, + type ShadowRealmObject, +} from '#self'; + +test('code in eval', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + const messages: unknown[] = []; + realm.scope(() => { + realm.GlobalObject.properties.set('e', new Descriptor({ + Value: CreateBuiltinFunction.from(function* e(e = Value.undefined) { + messages.push(getHostDefinedErrorStack(e)?.map((f) => f.toCallFrame())); + }), + })); + }); + + await inspector.eval([ + `e(new Error());`, + `function f() { + e(new Error()); + }; + f();`, + ].map((code) => `eval(\`${code}\`)`).join('\n')); + expect(messages).matchSnapshot(); +}); + +test('code in new Function', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + const messages: unknown[] = []; + realm.scope(() => { + realm.GlobalObject.properties.set('e', new Descriptor({ + Value: CreateBuiltinFunction.from(function* e(e = Value.undefined) { + messages.push(getHostDefinedErrorStack(e)?.map((f) => f.toCallFrame())); + }), + })); + }); + + await inspector.eval(` + new Function(\` + function x() { + e(new Error()); + } + function y() { + x() + } + return y\`)()() + `); + expect(messages).matchSnapshot(); +}); + +test('code in ShadowRealm', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + const messages: unknown[] = []; + realm.scope(() => { + evalQ((_Q, X) => { + const shadowRealm = X(Construct(surroundingAgent.intrinsic('%ShadowRealm%'))) as ShadowRealmObject; + realm.GlobalObject.properties.set('r', new Descriptor({ + Value: shadowRealm, + })); + shadowRealm.ShadowRealm.GlobalObject.properties.set('e', new Descriptor({ + Value: CreateBuiltinFunction.from(function* e(e = Value.undefined) { + messages.push(getHostDefinedErrorStack(e)?.map((f) => f.toCallFrame())); + }), + })); + }); + }); + + await inspector.eval(` + r.evaluate('e(new Error())'); + `); + expect(messages).matchSnapshot(); +}); diff --git a/test/inspector/toRemoteObject.test.mts b/test/inspector/toRemoteObject.test.mts new file mode 100644 index 0000000..8561043 --- /dev/null +++ b/test/inspector/toRemoteObject.test.mts @@ -0,0 +1,341 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-await-in-loop */ +import { expect, test } from 'vitest'; +import type Protocol from 'devtools-protocol'; +import { TestInspector } from './utils.mts'; +import { Agent, ManagedRealm, setSurroundingAgent } from '#self'; + +test('primitive values', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'undefined', + 'null', + 'false', + 'true', + '42', + '-42', + '42n', + '-42n', + '0', + '-0', + 'Infinity', + '-Infinity', + 'NaN', + '"engine262"', + 'Symbol()', + 'Symbol("desc")', + 'Symbol.for("symbol")', + 'Symbol.iterator', + ]) { + // eslint-disable-next-line no-await-in-loop + expect(await inspector.eval(value)).toMatchSnapshot(value); + } +}); + +test('functions', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + // function declaration + 'function f() { /* comment */ }; f', + 'function* f() { /* comment */ }; f', + 'function *f() { /* comment */ }; f', + 'async function f() { /* comment */ }; f', + 'async function* f() { /* comment */ }; f', + 'async function *f() { /* comment */ }; f', + // function expression + '(function f() { /* comment */ })', + '(function* f() { /* comment */ })', + '(function *f() { /* comment */ })', + '(async function f() { /* comment */ })', + '(async function* f() { /* comment */ })', + '(async function *f() { /* comment */ })', + // arrow expression + '(() => { /* comment */ })', + '(() => 42)', + '(async () => { /* comment */ })', + '(async () => 42)', + // computed function name + 'var a = 1; ({ [a]() {} })[a]', + '({ *[Symbol.iterator]() {} })[Symbol.iterator]', + // getter & setter + 'var o = { get f() { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, "f").get', + 'var o = { set f(v) { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, "f").set', + // getter & setter with computed name + 'var o = { get [Symbol.iterator]() { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, Symbol.iterator).get', + 'var o = { set [Symbol.iterator](v) { /* comment */ } }; Reflect.getOwnPropertyDescriptor(o, Symbol.iterator).set', + // built-in function + 'Array.prototype.map', + // built-in getter + 'Reflect.getOwnPropertyDescriptor(Function.prototype, "caller").get', + // method + 'class C { static method() {} }; C.method', + 'class C { constructor() {}; #f }; C.prototype.constructor', + ]) { + // eslint-disable-next-line no-await-in-loop + expect(await inspector.eval(value), value).toMatchSnapshot(value); + } +}); + +async function snapshotObject(inspector: TestInspector, value: string) { + const result = await inspector.eval(value); + expect(result).toMatchSnapshot(value); + const properties = await inspector.runtime.getProperties({ objectId: (result as any).objectId!, ownProperties: true, generatePreview: true }) as Protocol.Protocol.Runtime.GetPropertiesResponse; + properties.internalProperties = properties.internalProperties?.filter((prop) => prop.name !== '[[Prototype]]'); + expect(properties).toMatchSnapshot(`${value} properties`); +} + +test('array', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + '[]', + '[1]', + 'Array(10)', + '[,,,]', + 'var a = [1,,2]; a.x = 1; a', + ]) { + await snapshotObject(inspector, value); + } +}); + +test('regex', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + '/cat/', + '/cat/g', + '/cat/i', + 'var a = /cat/; a.lastIndex = 1; a', + ]) { + expect(await inspector.eval(value)).toMatchSnapshot(value); + } +}); + +test('date', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new Date(0)', + 'new Date(NaN)', + 'new Date(-1)', + 'new Date(9999999999999)', + ]) { + expect(await inspector.eval(value)).toMatchSnapshot(value); + } +}); + +test('map and set', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new Map', + 'new Map([["a", 1], ["b", 2]])', + 'var x = new Map([["a", 1], ["b", 2]]); x.x = 1; x', + 'new Set', + 'new Set(["a", 1, "b", 2])', + 'var x = new Set(["a", 1, "b", 2]); x.x = 1; x', + 'new WeakMap', + 'new WeakMap([[{}, 1], [{}, 2]])', + 'var x = new WeakMap([[{}, 1], [{}, 2]]); x.x = 1; x', + 'new WeakSet', + 'new WeakSet([{}, {}])', + 'var x = new WeakSet([{}, {}]); x.x = 1; x', + ]) { + await snapshotObject(inspector, value); + } +}); + +// TODO: generator + +test('error', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new Error()', + 'new Error("message")', + 'new Error("message", { cause: new Error("cause") })', + 'new RangeError()', + 'new (class MyError extends Error {})()', + // TODO: className should not be syntaxError + 'new (class MyError extends Error { constructor() { super(); this.message = "hello" } })()', + ]) { + expect(await inspector.eval(value)).toMatchSnapshot(value); + } +}); + +test('proxy', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new Proxy({}, {})', + 'new Proxy({}, { get: () => {} })', + 'new Proxy({ a: 1 }, {})', + 'new Proxy(Function, {})', + 'new Proxy(() => {}, {})', + 'var a = Proxy.revocable({}, {}); a.revoke(); a.proxy', + ]) { + await snapshotObject(inspector, value); + } +}); + +test('promise', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new Promise(() => {})', + 'var a = new Promise(() => {}); a.x = 1; a', + 'Promise.resolve()', + 'Promise.resolve(42)', + 'Promise.reject()', + 'Promise.reject(42)', + ]) { + await snapshotObject(inspector, value); + } +}); + +test('typed array', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new Uint8Array()', + 'new Uint8Array(10)', + 'new Uint8Array([1, 2, 3])', + 'var x = new Uint8Array(10); x.a = 1; x', + 'new Int32Array()', + 'new Int32Array(10)', + 'new Int32Array([1, 2, 3])', + // TODO: test with detached arraybuffer + ]) { + await snapshotObject(inspector, value); + } +}); + +test('array buffer', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new ArrayBuffer(0)', + 'new ArrayBuffer(10)', + 'var x = new ArrayBuffer(10); x.a = 1; x', + // TODO: test with detached arraybuffer + ]) { + await snapshotObject(inspector, value); + } +}); + +test('data view', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + 'new DataView(new ArrayBuffer(0))', + 'new DataView(new ArrayBuffer(10))', + 'var x = new DataView(new ArrayBuffer(10), 0); x.a = 1; x', + ]) { + await snapshotObject(inspector, value); + } +}); + +test('module namespace', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + await inspector.debugger.engine262_setEvaluateMode({ mode: 'module' }); + + for (const value of [ + // Note: our inspector will return the module namespace object after evaluation + '', + 'export const a = 1', + 'export const b = 2; export { b as c }', + 'export default 42', + 'export default function() {}', + ]) { + await snapshotObject(inspector, value); + } +}); + +test('shadow realm', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + await snapshotObject(inspector, 'new ShadowRealm'); + expect(await inspector.eval('new ShadowRealm().evaluate("(() => {})")')).toMatchSnapshot('ShadowRealm function'); +}); + +test('normal object', async () => { + const agent = new Agent(); + setSurroundingAgent(agent); + const inspector = new TestInspector(); + const realm = new ManagedRealm(); + inspector.attachAgent(agent, [realm]); + + for (const value of [ + '({})', + '({ a: 1 })', + '({ a: 1, b: 2 })', + '({ __proto__: null })', + '({ __proto__: { a: 1 } })', + '({ [Symbol.iterator]: () => {} })', + '({ f() {} })', + '({ get f() {}, set f(v) {} })', + '{ class T { #priv = 1 }; new T }', + '{ class T { #priv = 1; normal = 2 }; new T }', + '({ a: 1n, b: undefined, c: null, d: true, e: Symbol.iterator, f: [] })', + ]) { + await snapshotObject(inspector, value); + } +}); diff --git a/test/inspector/utils.mts b/test/inspector/utils.mts new file mode 100644 index 0000000..7b7dc78 --- /dev/null +++ b/test/inspector/utils.mts @@ -0,0 +1,86 @@ +import type { DebuggerContext, DebuggerNamespace, RuntimeNamespace } from '../../lib/inspector/types.d.mts'; +import { Inspector } from '#self/inspector'; + +export class TestInspector extends Inspector { + messages: object[] = []; + + flush() { + const old = this.messages; + this.messages = []; + return old; + } + + onInspectorMessage?: (message: object) => void; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected override send(data: any): void { + this.messages.push(data); + if ('id' in data) { + let meaningfulData = data; + if (data.result) { + meaningfulData = data.result; + if (!data.result.exceptionDetails) { + if (data.result.result && Object.keys(data.result).length <= 2) { + meaningfulData = data.result.result; + } + } + } + this.#callbacks.at(data.id)?.resolve(meaningfulData); + } + this.onInspectorMessage?.(data); + } + + protected override onMessage(id: number, method: string, params: object | void): void { + super.onMessage(id, method, params); + this.messages.push({ id, method, params }); + } + + debugger: { + [T in keyof DebuggerNamespace]-?: (params: DebuggerNamespace[T] extends undefined | ((params: infer O, context: DebuggerContext) => unknown) ? O : void) => Promise; + }; + + runtime: { + [T in keyof RuntimeNamespace]-?: (params: RuntimeNamespace[T] extends undefined | ((params: infer O, context: DebuggerContext) => unknown) ? O : void) => Promise; + }; + + #callbacks: PromiseWithResolvers[] = []; + + constructor() { + super(); + const object = (namespace: string) => Object.create( + new Proxy({}, { + get: (_, p, receiver) => { + if (typeof p === 'symbol') { + return undefined; + } + const f = (params: object) => { + this.onMessage(this.#callbacks.length, `${namespace}.${p}`, params); + const promise = Promise.withResolvers(); + this.#callbacks.push(promise); + return promise.promise; + }; + Reflect.defineProperty(receiver, p, { configurable: true, value: f }); + return f; + }, + }), + ); + this.runtime = object('Runtime'); + this.debugger = object('Debugger'); + } + + // helpers + eval(expression: string) { + return this.runtime.evaluate({ + expression, + uniqueContextId: '0', + }); + } + + perview(expression: string) { + return this.runtime.evaluate({ + expression, + uniqueContextId: '0', + throwOnSideEffect: true, + }); + } +} diff --git a/test/json/JSONTestSuite b/test/json/JSONTestSuite new file mode 160000 index 0000000..1ef36fa --- /dev/null +++ b/test/json/JSONTestSuite @@ -0,0 +1 @@ +Subproject commit 1ef36fa01286573e846ac449e8683f8833c5b26a diff --git a/test/json/json.mts b/test/json/json.mts new file mode 100644 index 0000000..2267d2e --- /dev/null +++ b/test/json/json.mts @@ -0,0 +1,100 @@ +/* eslint-disable no-console */ +/* eslint-disable no-await-in-loop */ +import fs from 'node:fs'; +import path from 'node:path'; +import { styleText } from 'node:util'; +import { globSync } from 'tinyglobby'; +import { createTestReporter, annotateFileWithURL } from '../tui.mts'; +import { Test } from '../base.mts'; +import { + Agent, + setSurroundingAgent, + ManagedRealm, + AbruptCompletion, + inspect, +} from '#self'; + +const failed = [ + // stack overflow for us + 'n_structure_100000_opening_arrays.json', + 'n_structure_open_array_object.json', +]; + +const BASE_DIR = path.resolve(import.meta.dirname, 'JSONTestSuite'); + +const agent = new Agent(); +setSurroundingAgent(agent); + +const reporter = createTestReporter(); +reporter.start(); + +function test(filename: string) { + const realm = new ManagedRealm(); + + const source = fs.readFileSync(filename, 'utf8'); + const test = new Test(filename, filename, [], null!, '', source); + reporter.addTest(test); + + if (failed.includes(path.basename(filename))) { + reporter.skipTest(test.id, 'skip-list'); + return; + } + + reporter.updateWorker(0, test.id); + let result; + try { + result = realm.evaluateScript(`JSON.parse(${JSON.stringify(source)});`); + } catch (error) { + reporter.updateWorker(0, null); + console.error(filename, error); + fail(filename, test.id, ''); + return; + } + reporter.updateWorker(0, null); + + const testName = path.basename(filename); + + if (!result || result instanceof AbruptCompletion) { + if (testName.startsWith('n_')) { + reporter.testPassed(test.id); + } else if (testName.startsWith('i_')) { + reporter.testPassed(test.id); + } else { + console.error(inspect(result)); + fail(filename, test.id, ''); + } + } else { + if (testName.startsWith('n_')) { + fail(filename, test.id, 'Expected failure but got success'); + } else { + reporter.testPassed(test.id); + } + } +} + +const tests = globSync( + 'test_{parsing,transform}/**/*.json', + { cwd: BASE_DIR, absolute: true }, +); + +for (const t of tests) { + test(t); + await new Promise((resolve) => { + setImmediate(resolve); + }); +} + +reporter.allTestsDiscovered(); +reporter.exit(); +setTimeout(() => { + process.exit(); +}); + +function fail(file: string, testId: number, message: string) { + process.exitCode = 1; + + // FAILED filename.js + const line1 = `${styleText('red', `FAILED ${annotateFileWithURL(file)}`)}\n`; + reporter.stdout(line1, message); + reporter.testFailed(testId); +} diff --git a/test/test262/failed b/test/test262/failed new file mode 100644 index 0000000..7072d84 --- /dev/null +++ b/test/test262/failed @@ -0,0 +1,106 @@ +##################### +### Failed Tests ### +##################### + +# Comments start with `#` or `;`. +# Paths are relative to the `test262/test` directory. +# Paths may contain globs. + +# TODO: import defer +language/expressions/dynamic-import/import-defer/import-defer-transitive-async-module/main.js +language/expressions/dynamic-import/import-defer/sync/main.js +language/expressions/dynamic-import/import-defer/sync-dependency-of-deferred-async-module/main.js + +# Non-strict mode +# Annex B +## RegExp.prototype.compile +staging/sm/RegExp/compile-lastIndex.js +staging/sm/RegExp/constructor-ordering.js +staging/sm/RegExp/flags-param-handling.js +staging/sm/RegExp/match-local-tolength-recompilation.js +staging/sm/RegExp/prototype.js +staging/sm/RegExp/replace-compile-elembase.js +staging/sm/RegExp/replace-compile.js +staging/sm/RegExp/replace-local-tolength-recompilation.js +staging/sm/String/matchAll.js +# SourceCharacterIdentityEscape +language/literals/regexp/S7.8.5_A1.4_T2.js +language/literals/regexp/S7.8.5_A2.4_T2.js +# IdentityEscape + ExtendedPatternCharacter +staging/sm/RegExp/unicode-braced.js +built-ins/String/prototype/split/separator-regexp.js # references /\k/, /\XA0/, /\X/ +# Block +staging/sm/lexical-environment/block-scoped-functions-annex-b-arguments.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-eval.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-if.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-label.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-notapplicable.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-parameter.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-same-name.js +staging/sm/lexical-environment/block-scoped-functions-annex-b-with.js +staging/sm/lexical-environment/block-scoped-functions-annex-b.js +staging/sm/lexical-environment/block-scoped-functions-deprecated-redecl.js + +# TODO: https://github.com/tc39/ecma262/issues/3592 +built-ins/RegExp/regexp-modifiers/add-ignoreCase-affects-slash-upper-p.js + +# Failed tests appended by --update-failed-tests +built-ins/String/prototype/localeCompare/15.5.4.9_CE.js +staging/sm/Array/toLocaleString-01.js +staging/sm/async-functions/await-in-arrow-parameters.js +staging/sm/async-functions/await-in-parameters-of-async-func.js +staging/sm/async-functions/property.js +staging/sm/AsyncGenerators/for-await-bad-syntax.js +staging/sm/Atomics/cross-compartment.js +staging/sm/Atomics/detached-buffers.js +staging/sm/class/newTargetDefaults.js +staging/sm/class/superCallBadNewTargetPrototype.js +staging/sm/class/superPropDestructuring.js +staging/sm/Date/setTime-argument-shortcircuiting.js +staging/sm/Date/time-components-negative-zero.js +staging/sm/Date/toISOString-01.js +staging/sm/destructuring/bug1396261.js +staging/sm/destructuring/order-super.js +staging/sm/expressions/destructuring-pattern-parenthesized.js +staging/sm/expressions/nullish-coalescing.js +staging/sm/expressions/optional-chain.js +staging/sm/extensions/arguments-property-access-in-function.js +staging/sm/extensions/censor-strict-caller.js +staging/sm/extensions/clone-v1-typed-array-data.dat +staging/sm/extensions/function-caller-skips-eval-frames.js +staging/sm/extensions/function-properties.js +staging/sm/extensions/recursion.js +staging/sm/fields/await-identifier-module-3.js +staging/sm/fields/await-identifier-script.js +staging/sm/Function/function-caller-restrictions.js +staging/sm/Function/function-name-for.js +staging/sm/Function/function-toString-builtin-name.js +staging/sm/Function/function-toString-builtin.js +staging/sm/Function/invalid-parameter-list.js +staging/sm/generators/runtime.js +staging/sm/generators/syntax.js +staging/sm/generators/yield-star-throw-htmldda.js +staging/sm/lexical-environment/for-loop.js +staging/sm/lexical-environment/var-in-catch-body-annex-b-eval.js +staging/sm/Math/f16round.js +staging/sm/misc/explicit-undefined-optional-argument.js +staging/sm/misc/future-reserved-words.js +staging/sm/Proxy/revoked-get-function-realm-typeerror.js +staging/sm/regress/regress-554955-5.js +staging/sm/regress/regress-577648-1.js +staging/sm/regress/regress-577648-2.js +staging/sm/regress/regress-584355.js +staging/sm/regress/regress-586482-1.js +staging/sm/regress/regress-586482-2.js +staging/sm/regress/regress-586482-3.js +staging/sm/regress/regress-586482-4.js +staging/sm/regress/regress-586482-5.js +staging/sm/regress/regress-602621.js +staging/sm/statements/for-in-with-declaration.js +staging/sm/statements/regress-642975.js +staging/sm/strict/strict-function-statements.js +staging/sm/String/unicode-braced.js +staging/sm/syntax/declaration-forbidden-in-label.js +staging/sm/syntax/linefeed-at-eof-in-unterminated-string-or-template.js +staging/sm/TypedArray/sort-negative-nan.js +staging/sm/TypedArray/toString.js diff --git a/test/test262/features b/test/test262/features new file mode 100644 index 0000000..e3314e9 --- /dev/null +++ b/test/test262/features @@ -0,0 +1,27 @@ +# https://github.com/tc39/test262/blob/main/features.txt + +# Start with `-` to skip feature +# Map feature to engine262 feature using `feature = engine262featurename` +# e.g.: +# import-defer = import-defer +decorators = decorators +Temporal = temporal + +# Update with test262 on Feb 2025, to be investigated/implemented +-Float16Array +-arraybuffer-transfer +-explicit-resource-management +-source-phase-imports +-source-phase-imports-module-source +-immutable-arraybuffer + +# Added before Feb 2025 + +-Atomics +-Atomics.waitAsync +-Atomics.pause +-caller +-SharedArrayBuffer +-tail-call-optimization +-Temporal +-resizable-arraybuffer diff --git a/test/test262/skip b/test/test262/skip new file mode 100644 index 0000000..9edf502 --- /dev/null +++ b/test/test262/skip @@ -0,0 +1,124 @@ +##################### +### Skipped Tests ### +##################### + +# Comments start with `#` or `;`. +# Paths are relative to the `test262/test` directory. +# Paths may contain globs. + +annexB +intl402 + +# fix CI? +harness/nativeFunctionMatcher.js + +# Our date parser is now calling the host algorithm, may unstable based on the host. +built-ins/Date/parse/without-utc-offset.js + +# Decorators (not merged yet) https://github.com/tc39/test262/pull/4103/ +## wrong test, we are correct +language/expressions/class/decorator/class/error/class-deco-invalid-return-arrow.js +language/statements/class/decorator/class/error/class-deco-invalid-return-arrow.js +language/expressions/class/decorator/class/class-deco-returns-proxy.js +language/statements/class/decorator/class/class-deco-returns-proxy.js + +# TODO (Feb 2026) +language/import/import-defer/evaluation-triggers/ignore-super-property-set-exported.js +language/import/import-defer/evaluation-triggers/ignore-super-property-set-not-exported.js +language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-import-star-as-and-export.js +language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from.js +language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from-and-import-star-as-and-export.js +language/identifiers/start-unicode-17.0.0-escaped.js +language/identifiers/part-unicode-17.0.0-class-escaped.js +language/identifiers/part-unicode-17.0.0.js +language/identifiers/start-unicode-17.0.0-class-escaped.js +language/identifiers/part-unicode-17.0.0-class.js +language/identifiers/start-unicode-17.0.0.js +language/identifiers/start-unicode-17.0.0-class.js +language/identifiers/part-unicode-17.0.0-escaped.js +built-ins/Promise/allSettledKeyed/not-a-constructor.js +built-ins/Promise/allSettledKeyed/proto.js +built-ins/Promise/allSettledKeyed/name.js +built-ins/Promise/allSettledKeyed/extensible.js +built-ins/Promise/allSettledKeyed/prop-desc.js +built-ins/Promise/allSettledKeyed/length.js +built-ins/Promise/allKeyed/not-a-constructor.js +built-ins/Promise/allKeyed/prop-desc.js +built-ins/Promise/allKeyed/proto.js +built-ins/Promise/allKeyed/extensible.js +built-ins/Promise/allKeyed/length.js +built-ins/Promise/allKeyed/name.js +built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-return.js +built-ins/Iterator/zipKeyed/iterables-containing-string-objects.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-step-value-abrupt-completion.js +built-ins/Iterator/zipKeyed/results-object-has-default-attributes.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js +built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-next.js +built-ins/Iterator/zipKeyed/padding-iteration.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-close-abrupt-completion.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js +built-ins/Iterator/zipKeyed/proto.js +built-ins/Iterator/zipKeyed/iterables-iteration-inherited.js +built-ins/Iterator/zipKeyed/iterables-iteration-symbol-key.js +built-ins/Iterator/zipKeyed/basic-longest.js +built-ins/Iterator/zipKeyed/basic-strict.js +built-ins/Iterator/zipKeyed/iterables-iteration-get-iterator-flattenable-abrupt-completion.js +built-ins/Iterator/zipKeyed/iterables-iteration-after-reading-options.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js +built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-next.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js +built-ins/Iterator/zipKeyed/padding-iteration-get-abrupt-completion.js +built-ins/Iterator/zipKeyed/prop-desc.js +built-ins/Iterator/zipKeyed/iterables-iteration-get-abrupt-completion.js +built-ins/Iterator/zipKeyed/results-object-from-array.js +built-ins/Iterator/zipKeyed/iterables-iteration.js +built-ins/Iterator/zipKeyed/is-function.js +built-ins/Iterator/zipKeyed/iterables-iteration-undefined.js +built-ins/Iterator/zipKeyed/iterables-iteration-get-own-property-abrupt-completion.js +built-ins/Iterator/zipKeyed/options.js +built-ins/Iterator/zipKeyed/iterables-iteration-deleted.js +built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-return.js +built-ins/Iterator/zipKeyed/basic-shortest.js +built-ins/Iterator/zipKeyed/options-padding.js +built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js +built-ins/Iterator/zipKeyed/length.js +built-ins/Iterator/zipKeyed/results-object-has-no-undefined-iterables-properties.js +built-ins/Iterator/zipKeyed/options-mode.js +built-ins/Iterator/zipKeyed/name.js +built-ins/Iterator/zipKeyed/result-is-iterator.js +built-ins/Iterator/zipKeyed/iterator-zip-iteration.js +built-ins/Iterator/zip/iterables-containing-string-objects.js +built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js +built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js +built-ins/Iterator/zip/suspended-start-iterator-close-calls-return.js +built-ins/Iterator/zip/suspended-start-iterator-close-calls-next.js +built-ins/Iterator/zip/iterator-zip-iteration-iterator-step-value-abrupt-completion.js +built-ins/Iterator/zip/padding-iteration.js +built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js +built-ins/Iterator/zip/iterator-zip-iteration-iterator-close-abrupt-completion.js +built-ins/Iterator/zip/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js +built-ins/Iterator/zip/proto.js +built-ins/Iterator/zip/basic-longest.js +built-ins/Iterator/zip/basic-strict.js +built-ins/Iterator/zip/iterables-iteration-get-iterator-flattenable-abrupt-completion.js +built-ins/Iterator/zip/iterables-iteration-after-reading-options.js +built-ins/Iterator/zip/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js +built-ins/Iterator/zip/suspended-yield-iterator-close-calls-next.js +built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js +built-ins/Iterator/zip/prop-desc.js +built-ins/Iterator/zip/iterables-iteration.js +built-ins/Iterator/zip/is-function.js +built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js +built-ins/Iterator/zip/options.js +built-ins/Iterator/zip/suspended-yield-iterator-close-calls-return.js +built-ins/Iterator/zip/options-padding.js +built-ins/Iterator/zip/iterables-iteration-iterator-step-value-abrupt-completion.js +built-ins/Iterator/zip/basic-shortest.js +built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js +built-ins/Iterator/zip/length.js +built-ins/Iterator/zip/name.js +built-ins/Iterator/zip/iterator-zip-iteration.js +built-ins/Iterator/zip/result-is-iterator.js +built-ins/Iterator/zip/options-mode.js +built-ins/RegExp/unicodeSets/generated/rgi-emoji-17.0.js diff --git a/test/test262/slow b/test/test262/slow new file mode 100644 index 0000000..55cfe05 --- /dev/null +++ b/test/test262/slow @@ -0,0 +1,97 @@ +################## +### Slow Tests ### +################## + +# Comments start with `#` or `;`. +# Paths are relative to the `test262/test` directory. +# Paths may contain globs. + +built-ins/RegExp/property-escapes/generated +built-ins/RegExp/CharacterClassEscapes + +# Slow on CI +built-ins/Array/prototype/concat/Array.prototype.concat_small-typed-array.js +built-ins/Array/prototype/every/15.4.4.16-7-c-ii-2.js +built-ins/Array/prototype/indexOf/15.4.4.14-10-1.js +built-ins/Array/prototype/lastIndexOf/15.4.4.15-9-1.js +built-ins/Array/prototype/map/15.4.4.19-8-c-ii-1.js +built-ins/Array/prototype/some/15.4.4.17-7-c-ii-2.js +built-ins/decodeURI +built-ins/encodeURIComponent +language/literals/regexp/S7.8.5_A1.4_T2.js +language/literals/regexp/S7.8.5_A2.4_T2.js +staging/sm/expressions/object-literal-__proto__.js +staging/sm/misc/getter-setter-outerize-this.js +staging/sm/Proxy/ownkeys-linear.js +staging/sm/RegExp/unicode-class-braced.js +staging/sm/String/replace-math.js +staging/sm/TypedArray/entries.js +staging/sm/TypedArray/fill.js +staging/sm/TypedArray/forEach.js +staging/sm/TypedArray/sort_small.js +staging/sm/TypedArray/sort-negative-nan.js + +# Slow tests appended by --update-slow-tests=10 +built-ins/Array/fromAsync/asyncitems-arraylike-too-long.js +built-ins/Array/prototype/concat/Array.prototype.concat_large-typed-array.js +built-ins/decodeURI/S15.1.3.1_A1.10_T1.js +built-ins/decodeURI/S15.1.3.1_A1.11_T1.js +built-ins/decodeURI/S15.1.3.1_A1.11_T2.js +built-ins/decodeURI/S15.1.3.1_A1.12_T1.js +built-ins/decodeURI/S15.1.3.1_A1.12_T2.js +built-ins/decodeURI/S15.1.3.1_A1.12_T3.js +built-ins/decodeURI/S15.1.3.1_A1.2_T1.js +built-ins/decodeURI/S15.1.3.1_A1.2_T2.js +built-ins/decodeURI/S15.1.3.1_A2.1_T1.js +built-ins/decodeURI/S15.1.3.1_A2.4_T1.js +built-ins/decodeURI/S15.1.3.1_A2.5_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A1.10_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A1.11_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A1.11_T2.js +built-ins/decodeURIComponent/S15.1.3.2_A1.12_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A1.12_T2.js +built-ins/decodeURIComponent/S15.1.3.2_A1.12_T3.js +built-ins/decodeURIComponent/S15.1.3.2_A1.2_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A1.2_T2.js +built-ins/decodeURIComponent/S15.1.3.2_A2.1_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A2.4_T1.js +built-ins/decodeURIComponent/S15.1.3.2_A2.5_T1.js +built-ins/encodeURI/S15.1.3.3_A2.3_T1.js +built-ins/encodeURI/S15.1.3.3_A2.5_T1.js +built-ins/encodeURIComponent/S15.1.3.4_A2.3_T1.js +built-ins/encodeURIComponent/S15.1.3.4_A2.5_T1.js +built-ins/Function/prototype/toString/built-in-function-object.js +built-ins/parseFloat/S15.1.2.3_A6.js +built-ins/parseInt/S15.1.2.2_A8.js +built-ins/RegExp/character-class-escape-non-whitespace.js +language/comments/S7.4_A5.js +language/comments/S7.4_A6.js +language/literals/regexp/S7.8.5_A1.1_T2.js +language/literals/regexp/S7.8.5_A2.1_T2.js +staging/sm/Array/length-truncate-nonconfigurable-sparse.js +staging/sm/Array/toSpliced-dense.js +staging/sm/Date/dst-offset-caching-1-of-8.js +staging/sm/Date/dst-offset-caching-2-of-8.js +staging/sm/Date/dst-offset-caching-3-of-8.js +staging/sm/Date/dst-offset-caching-4-of-8.js +staging/sm/Date/dst-offset-caching-5-of-8.js +staging/sm/Date/dst-offset-caching-6-of-8.js +staging/sm/Date/dst-offset-caching-7-of-8.js +staging/sm/Date/dst-offset-caching-8-of-8.js +staging/sm/Date/two-digit-years.js +staging/sm/expressions/short-circuit-compound-assignment.js +staging/sm/Function/has-instance-jitted.js +staging/sm/JSON/parse-mega-huge-array.js +staging/sm/RegExp/unicode-ignoreCase.js +staging/sm/regress/regress-1507322-deep-weakmap.js +staging/sm/regress/regress-610026.js +staging/sm/String/fromCodePoint.js +staging/sm/String/string-upper-lower-mapping.js +staging/sm/TypedArray/element-setting-converts-using-ToNumber.js +staging/sm/TypedArray/every-and-some.js +staging/sm/TypedArray/map-and-filter.js +staging/sm/TypedArray/set-same-buffer-different-source-target-types.js +staging/sm/TypedArray/sort_large_countingsort.js +staging/sm/TypedArray/sort_modifications.js +staging/sm/TypedArray/sort_snans.js +staging/sm/TypedArray/sort_sorted.js diff --git a/test/test262/slow-ci b/test/test262/slow-ci new file mode 100644 index 0000000..6ae9245 --- /dev/null +++ b/test/test262/slow-ci @@ -0,0 +1,37 @@ +########################## +### Slow Tests (on CI) ### +########################## + +# Comments start with `#` or `;`. +# Paths are relative to the `test262/test` directory. +# Paths may contain globs. + +built-ins/Array/prototype/concat/Array.prototype.concat_small-typed-array.js +built-ins/Array/prototype/every/15.4.4.16-7-c-ii-2.js +built-ins/Array/prototype/filter/15.4.4.20-9-c-ii-1.js +built-ins/Array/prototype/forEach/15.4.4.18-7-c-ii-1.js +built-ins/Array/prototype/indexOf/15.4.4.14-10-1.js +built-ins/Array/prototype/lastIndexOf/15.4.4.15-9-1.js +built-ins/Array/prototype/map/15.4.4.19-8-c-ii-1.js +built-ins/Array/prototype/some/15.4.4.17-7-c-ii-2.js +built-ins/encodeURI/S15.1.3.3_A2.4_T1.js +built-ins/encodeURI/S15.1.3.3_A2.4_T2.js +built-ins/encodeURIComponent/S15.1.3.4_A2.4_T1.js +built-ins/encodeURIComponent/S15.1.3.4_A2.4_T2.js +built-ins/String/prototype/repeat/empty-string-returns-empty.js +built-ins/TypedArray/prototype/copyWithin/coerced-values-end-detached-prototype.js +built-ins/TypedArray/prototype/copyWithin/coerced-values-end-detached.js +built-ins/TypedArray/prototype/copyWithin/coerced-values-start-detached.js +language/literals/regexp/S7.8.5_A1.4_T2.js +language/literals/regexp/S7.8.5_A2.4_T2.js +staging/sm/expressions/object-literal-__proto__.js +staging/sm/misc/getter-setter-outerize-this.js +staging/sm/Proxy/ownkeys-linear.js +staging/sm/RegExp/unicode-class-braced.js +staging/sm/String/replace-math.js +staging/sm/TypedArray/entries.js +staging/sm/TypedArray/fill.js +staging/sm/TypedArray/forEach.js +staging/sm/TypedArray/sort_small.js +staging/sm/TypedArray/sort_small.js +staging/sm/TypedArray/sort-negative-nan.js diff --git a/test/test262/test262 b/test/test262/test262 new file mode 160000 index 0000000..3aa9cb2 --- /dev/null +++ b/test/test262/test262 @@ -0,0 +1 @@ +Subproject commit 3aa9cb2c71afc21aefc1f82e899af1d0403351ba diff --git a/test/test262/test262-runner.mts b/test/test262/test262-runner.mts new file mode 100644 index 0000000..67cb847 --- /dev/null +++ b/test/test262/test262-runner.mts @@ -0,0 +1,446 @@ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { join, resolve, relative } from 'node:path'; +import { createWriteStream } from 'node:fs'; +import { opendir, readFile, stat } from 'node:fs/promises'; +import { stripVTControlCharacters, styleText } from 'node:util'; +import { fork } from 'node:child_process'; +import { cpus } from 'node:os'; +import { glob, isDynamicPattern } from 'tinyglobby'; +import YAML from 'js-yaml'; +import { highlight } from 'cli-highlight'; +import { + type WorkerToSupervisor, type SupervisorToWorker, Test, + readList, + type WorkerToSupervisor_Failed, + type Stack, +} from '../base.mts'; +import { annotateFileWithURL, isCI } from '../tui.mts'; +import { + createTestReporter, + supportColor, + type SkipReason, +} from '../tui.mts'; +import { fatal_exit } from '../base.mts'; +import { args } from './test262.mts'; + +const inputs = { + Test262TestsPath: join(process.env.TEST262 || resolve(import.meta.dirname, 'test262'), 'test'), + AllTests: async () => { + const files: string[] = []; + for await (const file of readdir(inputs.Test262TestsPath)) { + files.push(file); + } + inputs.AllTests = async () => files; + return files; + }, + AssertToBeFailedList: resolve(import.meta.dirname, 'failed'), + SkipList: resolve(import.meta.dirname, 'skip'), + SlowList: resolve(import.meta.dirname, 'slow'), + SlowListCI: resolve(import.meta.dirname, 'slow-ci'), + Features: resolve(import.meta.dirname, 'features'), +}; + +const outputs = { + LastRunFailedList: resolve(import.meta.dirname, 'last-failed-list'), + CurrentRunFailureLog: resolve(import.meta.dirname, 'last-failed.log'), +}; + +if (args.values['failed-only']) { + args.positionals = (await readFile(outputs.LastRunFailedList, { encoding: 'utf-8' })).split('\n'); +} + +const outputStreams = { + SlowList: args.values['update-slow'] ? createWriteStream(inputs.SlowList, { encoding: 'utf-8', flags: 'a' }) : undefined, + LastRunFailedList: createWriteStream(outputs.LastRunFailedList, { encoding: 'utf-8' }), + CurrentRunFailureLog: createWriteStream(outputs.CurrentRunFailureLog, { encoding: 'utf-8' }), + AssertToBeFailedList: args.values['update-failed'] ? createWriteStream(inputs.AssertToBeFailedList, { encoding: 'utf-8', flags: 'a' }) : undefined, +}; + +let allTestsDiscovered = false; + +const disabledFeatures = new Set(); +readList(inputs.Features).forEach((feature) => { + if (feature.startsWith('-')) { + disabledFeatures.add(feature.slice(1)); + } +}); +disabledFeatures.delete(args.values.features!); + +const workersToStart = Math.max( + 1, + process.env.NUM_WORKERS + ? Number.parseInt(process.env.NUM_WORKERS, 10) + : cpus().length - 2, +); +const workers = Array.from({ length: workersToStart }, (_, index) => createWorker(index)); +/** + * Do not replace this with reporter.workers. + * This variable maintains the state in the main thread, but reporter.workers is updated asynchronously based on the feedback from workers. + */ +const workerHasPendingTask: boolean[] = new Array(workersToStart).fill(false); + +const [ + slowList, + slowListCI, + skipList, + assertToBeFailedList, +] = await Promise.all([ + readListPaths(inputs.SlowList, false), + readListPaths(inputs.SlowListCI, true), + readListPaths(inputs.SkipList, false), + readListPaths(inputs.AssertToBeFailedList, false), +]); + +if (outputStreams.AssertToBeFailedList) { + outputStreams.AssertToBeFailedList.write('\n# Failed tests appended by --update-failed-tests\n'); +} + +/** This is for skipping the report of a test if it's variant (strict version) has already failed. */ +const currentRunFailedTestFiles = new Set(); +const pendingTests: Test[] = []; +const reporter = createTestReporter(); + +if (outputStreams.SlowList) { + const second = parseInt(args.values['update-slow']!, 10); + if (Number.isNaN(second)) { + fatal_exit('--update-slow must be a number'); + } + outputStreams.SlowList.write(`\n# Slow tests appended by --update-slow=${second}\n`); + reporter.setSlowTestReporting(second, outputStreams.SlowList); +} + +function discoverTest(test: Test) { + reporter.addTest(test); + const disabledFeature = test.attrs.features?.find((feature: string) => disabledFeatures.has(feature)); + if (disabledFeature) { + return reporter.skipTest(test.id, 'feature-disabled', disabledFeature); + } + if (skipList.has(test.file)) { + return reporter.skipTest(test.id, 'skip-list'); + } + if (slowList.has(test.file) && !args.values['run-slow']) { + return reporter.skipTest(test.id, 'slow-list'); + } + if (isCI && slowListCI.has(test.file) && !args.values['run-slow']) { + return reporter.skipTest(test.id, 'slow-list'); + } + pendingTests.push(test); + distributeTest(); + return undefined; +} + +function distributeTest() { + while (true) { + const candidate = workerHasPendingTask.findIndex((work) => !work); + if (candidate === -1) { + return; + } + if (!pendingTests.length) { + if (allTestsDiscovered && workerHasPendingTask.every((work) => !work)) { + reporter.exit(); + } + return; + } + workers[candidate].send(pendingTests.shift()! satisfies SupervisorToWorker); + workerHasPendingTask[candidate] = true; + } +} + +const visited = new Set(); +reporter.start(); +reporter.addEventListener('exit', () => { + workers.forEach((worker) => worker.kill()); +}); +reporter.onExit.promise.then(() => { + if (args.values.verbose || args.values.vv) { + const skip: Record> = { + 'feature-disabled': new Set(), + 'skip-list': new Set(), + 'slow-list': new Set(), + }; + const passed: Set = new Set(); + const skipByFeature: Record> = {}; + const failed: Set = new Set(); + for (const test of reporter.tests.values()) { + if (test.status === 'skipped') { + if (test.skipFeature) { + (skipByFeature[test.skipFeature] ??= new Set()).add(test.file); + } else if (test.skipReason) { + skip[test.skipReason].add(test.file); + } + } else if (test.status === 'failed') { + failed.add(test.file); + } else if (args.values.vv && test.status === 'passed') { + passed.add(test.file); + } + } + if (args.values.vv && passed.size > 0) { + console.log(styleText('green', 'The following tests passed:')); + for (const test of passed) { + console.log(`- ./test/test262/test262/test/${test}`); + } + } + let skipPrint = () => { + console.log(styleText('yellow', 'The following tests were skipped:')); + skipPrint = () => { }; + }; + for (const [reason, tests] of Object.entries(skip)) { + if (tests.size === 0) { + continue; + } + skipPrint(); + console.log(`- Reason: ${styleText('yellow', reason)} (${tests.size} tests)`); + for (const test of tests) { + console.log(` - ./test/test262/test262/test/${test}`); + } + } + for (const [feature, tests] of Object.entries(skipByFeature)) { + skipPrint(); + console.log(`- Reason: ${styleText('yellow', `feature-disabled (${feature})`)} (${tests.size} tests)`); + for (const test of tests) { + console.log(` - ./test/test262/test262/test/${test}`); + } + } + if (failed.size > 0) { + console.log(styleText('red', '\nThe following tests failed:')); + for (const test of failed) { + console.log(`- ./test/test262/test262/test/${test}`); + } + } + } +}); + +const engineFeatures = [...args.values['engine-features'] || []]; + +const promises = []; +for await (const file of parsePositionals(args.positionals, true)) { + if (visited.has(file) || /_FIXTURE|README\.md|\.py|\.map|\.mts/.test(file)) { + continue; + } + + visited.add(file); + promises.push(readFile(file, 'utf8').then((contents) => { + const frontmatterYaml = contents.match(/\/\*---(.*?)---\*\//s)?.[1]; + const attrs: any = frontmatterYaml ? YAML.load(frontmatterYaml) : {}; + + if (args.values.features && (!attrs.features || !attrs.features.includes(args.values.features))) { + // feature not match + return; + } + + attrs.flags = (attrs.flags || []).reduce((acc: any, c: any) => { + acc[c] = true; + return acc; + }, {}); + attrs.includes = attrs.includes || []; + + const test = new Test(relative(inputs.Test262TestsPath, file), file, engineFeatures, attrs, '', contents); + + if (test.attrs.flags.module) { + discoverTest(test.withDifferentTestFlag('module')); + } else { + if (!test.attrs.flags.onlyStrict && !args.values['strict-only'] && !args.values.fast) { + discoverTest(test); + } + + if (!test.attrs.flags.noStrict && !test.attrs.flags.raw) { + discoverTest(test.withDifferentTestFlag('strict', `'use strict';${test.content}`)); + } + } + })); +} +if (args.positionals.length && !promises.length) { + fatal_exit(`No tests found based on the given globs: ${args.positionals.join(', ')}`); +} +await Promise.all(promises); + +allTestsDiscovered = true; +reporter.allTestsDiscovered(); +distributeTest(); + +async function readListPaths(file: string, defaults: boolean) { + const list = readList(file); + const files = new Set(); + for await (const file of parsePositionals(list, defaults)) { + files.add(relative(inputs.Test262TestsPath, file)); + } + return files; +} + +async function* readdir(dir: string): AsyncGenerator { + for await (const dirent of await opendir(dir)) { + const p = join(dir, dirent.name); + if (dirent.isDirectory()) { + yield* readdir(p); + } else { + yield p; + } + } +} + +async function* parsePositional(pattern: string): AsyncGenerator { + if (!isDynamicPattern(pattern)) { + const a_path = join(inputs.Test262TestsPath, pattern); + const a = await stat(a_path).catch(() => undefined); + if (a?.isDirectory()) { + return yield* readdir(a_path); + } else if (a?.isFile()) { + return yield a_path; + } + + const b_path = join(process.cwd(), pattern); + const b = await stat(b_path).catch(() => undefined); + if (b?.isDirectory()) { + return yield* readdir(b_path); + } else if (b?.isFile()) { + return yield b_path; + } + + const files = await inputs.AllTests(); + const matched = files.filter((f) => f.toLowerCase().includes(pattern.toLowerCase())); + if (matched.length) { + return yield* matched; + } + } + + const files1 = await glob(pattern, { cwd: inputs.Test262TestsPath, absolute: true, caseSensitiveMatch: false }); + if (files1.length) { + return yield* files1; + } + + const files2 = await glob(pattern, { cwd: process.cwd(), absolute: true, caseSensitiveMatch: false }); + if (files2.length) { + return yield* files2; + } + return undefined; +} + +async function* parsePositionals(pattern: string[], defaults: boolean): AsyncGenerator { + if (!pattern.length) { + if (defaults) { + yield* readdir(inputs.Test262TestsPath); + } + return; + } + for (const p of pattern) { + if (p) { + yield* parsePositional(p); + } + } +} + +function createWorker(workerId: number) { + const c = fork(resolve(import.meta.dirname, './test262-worker.mts')); + c.on('message', (message: WorkerToSupervisor) => { + switch (message.status) { + case 'RUNNING': + return reporter.updateWorker(workerId, message.testId); + case 'PASS': + workerHasPendingTask[workerId] = false; + reporter.updateWorker(workerId, null); + distributeTest(); + if (assertToBeFailedList.has(message.file)) { + if (currentRunFailedTestFiles.has(message.file)) { + return reporter.testFailed(message.testId); + } else { + currentRunFailedTestFiles.add(message.file); + return fail({ + file: message.file, + description: 'The test is declared to be failed, but passed.', + error: '', + flags: message.flags, + status: 'FAIL', + testId: message.testId, + stack: [], + }, false); + } + } + return reporter.testPassed(message.testId); + case 'FAIL': { + workerHasPendingTask[workerId] = false; + reporter.updateWorker(workerId, null); + distributeTest(); + if (assertToBeFailedList.has(message.file)) { + return reporter.assertFailedTestFails(message.testId); + } + if (currentRunFailedTestFiles.has(message.file)) { + return reporter.testFailed(message.testId); + } + currentRunFailedTestFiles.add(message.file); + outputStreams.LastRunFailedList.write(`${message.file}\n`); + if (outputStreams.AssertToBeFailedList) { + outputStreams.AssertToBeFailedList.write(`${message.file}\n`); + } + return fail(message, true); + } + default: + console.error(message); + throw new RangeError('Unknown message from worker'); + } + }); + c.on('exit', (code) => { + if (code !== 0 && code !== null) { + fatal_exit(`Worker ${workerId} exited with code ${code}`); + } + }); + return c; +} + + +function fail(message: WorkerToSupervisor_Failed, showSource: boolean) { + const { description, testId, file } = message; + let error = message.error; + error = error.replaceAll(`${process.cwd()}/`, ''); + process.exitCode = 1; + + const desc = styleText('yellow', description.trim()); + const descNeedOwnLine = desc.includes('\n') || desc.length > (process.stdout.columns - file.length - 8); + // FAILED filename.js + const line1 = `${styleText(['bgRed', 'white', 'bold'], ' FAIL ')} ${annotateFileWithURL(file)}${descNeedOwnLine ? '' : ` ${desc}`}\n`; + // Test description in the header + const line2 = descNeedOwnLine ? `${indent(desc, ' ')}\n` : ''; + // Source code with error position annotated + const line3 = showSource ? annotateSourceWithErrorPosition(error, reporter.tests.get(testId)!.content, message.stack) : ''; + // Error message + const line4 = `${indent(error, ' ')}\n`; + const line5 = styleText('red', `${'⎯'.repeat(process.stdout.columns)}\n`); + const output = line1 + line2 + line3 + line4; + reporter.stdout(output, line5); + outputStreams.CurrentRunFailureLog.write(stripVTControlCharacters(output)); + reporter.testFailed(testId); +} + +function indent(string: string, space: string) { + return string.split('\n').map((line) => space + line).join('\n'); +} + +function annotateSourceWithErrorPosition(error: string, sourceCode: string, [stack]: Stack[]) { + sourceCode = stack?.source || sourceCode; + if (!stack) { + return ''; + } + if (sourceCode.endsWith('\n')) { + sourceCode = sourceCode.slice(0, -1); + } + const highLightedLines = (supportColor ? highlight(sourceCode, { language: 'js' }) : sourceCode).split('\n'); + const linesPad = (highLightedLines.length + 1).toString().length; + const decoratedLines = highLightedLines.map((line, index) => ` ${styleText('red', (index + 1).toString().padStart(linesPad))} | ${line}`); + const LINES_BEFORE = 3; + const LINES_AFTER = 2; + const slicedLines = decoratedLines.slice( + Math.max(0, Number(stack.line) - LINES_BEFORE), + Math.min(decoratedLines.length, Number(stack.line)), + ); + slicedLines.push(''.padStart(linesPad + 5) + styleText('red', `${'-'.repeat(Math.max(Number(stack.column) - 1, 0))}^ ${error.split('\n')[0].trim()}`)); + slicedLines.push( + decoratedLines.slice( + Number(stack.line), + Math.min(decoratedLines.length, Number(stack.line) + LINES_AFTER), + ).join('\n'), + ); + + sourceCode = `${slicedLines.join('\n')}`; + sourceCode += '\n'; + return sourceCode; +} diff --git a/test/test262/test262-worker.mts b/test/test262/test262-worker.mts new file mode 100644 index 0000000..12785d0 --- /dev/null +++ b/test/test262/test262-worker.mts @@ -0,0 +1,260 @@ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-multi-assign */ +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import * as util from 'node:util'; +import { + readList, type Stack, type SupervisorToWorker, type Test, type WorkerToSupervisor, +} from '../base.mts'; +import { createRealm, createAgent } from '../base.mts'; +import { + AbruptCompletion, ObjectValue, evalQ, + setSurroundingAgent, + inspect, + Value, + IsCallable, + IsDataDescriptor, + JSStringValue, + skipDebugger, + boostTest262Harness, + ThrowCompletion, + getHostDefinedErrorStack, + CallSite, +} from '#self'; + +const TEST262 = process.env.TEST262 || path.resolve(import.meta.dirname, 'test262'); +const TEST262_TESTS = path.join(TEST262, 'test'); + +const featureMap: Record = Object.create(null); +readList(path.resolve(import.meta.dirname, 'features')).forEach((f) => { + if (f.includes('=')) { + const [k, v] = f.split('='); + featureMap[k.trim()] = v.trim(); + } +}); + +const includeCache: Record = {}; + +process.on('message', (test: SupervisorToWorker) => { + try { + process.send!({ status: 'RUNNING', testId: test.id } satisfies WorkerToSupervisor, handleSendError); + const result = run(test); + if (result.status === 'PASS') { + process.send!({ + status: 'PASS', file: test.file, flags: test.currentTestFlag, testId: test.id, + } satisfies WorkerToSupervisor, handleSendError); + } else { + process.send!(result satisfies WorkerToSupervisor, handleSendError); + } + } catch (e) { + process.send!(fails(test, util.inspect(e), []), handleSendError); + } +}); + +function run(test: Test): WorkerToSupervisor { + const features = [...test.engineFeatures]; + if (test.attrs.features) { + test.attrs.features.forEach((f) => { + if (featureMap[f]) { + features.push(featureMap[f]); + } + }); + } + const agent = createAgent({ features }); + const parsedScripts = new Map(); + setSurroundingAgent(agent); + agent.hostDefinedOptions.errorStackAttachNativeStack = true; + agent.hostDefinedOptions.onScriptParsed = (script, id) => { + parsedScripts.set(id, script.ECMAScriptCode.sourceText); + }; + + + function fail(test: Test, error: Value): WorkerToSupervisor { + const stacks = getHostDefinedErrorStack(error); + const reportStack: Stack[] = []; + for (const stack of stacks || []) { + if (!(stack instanceof CallSite)) { + continue; + } + const scriptId = stack.getScriptId(); + if (!scriptId || stack.columnNumber === null || stack.lineNumber === null) { + continue; + } + const source = parsedScripts.get(scriptId); + const record = agent.parsedSources.get(scriptId); + if (record?.HostDefined.specifier?.includes('harness')) { + continue; + } + reportStack.push({ + column: stack.columnNumber, + line: stack.lineNumber, + source: source === test.content ? undefined : source, + specifier: stack.getSpecifier(), + }); + } + return { + status: 'FAIL', + file: test.file, + flags: test.currentTestFlag, + testId: test.id, + description: test.attrs.description, + error: inspect(error), + stack: reportStack, + }; + } + + const { realm, resolverCache, setPrintHandle } = createRealm({ specifier: test.specifier }); + const r = realm.scope((): WorkerToSupervisor => { + 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(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 fail(test, completion.Value); + } + } + boostTest262Harness(realm); + + { + const DONE = ` +function $DONE(error) { + if (error) { + if (typeof error === 'object' && error !== null && 'stack' in error) { + print('Test262:AsyncTestFailure:' + error.stack, error); + } else { + print('Test262:AsyncTestFailure:Test262Error: ' + error, error); + } + } else { + print('Test262:AsyncTestComplete'); + } +}`; + const completion = realm.evaluateScript(`\ +var Test262Error = class Test262Error extends Error {}; +Test262Error.thrower = (...args) => { + throw new Test262Error(...args); +}; +${test.attrs.flags.async ? DONE : ''}`); + if (completion instanceof AbruptCompletion) { + return fail(test, completion.Value); + } + } + + let asyncResult: WorkerToSupervisor | undefined; + if (test.attrs.flags.async) { + setPrintHandle((m, value) => { + if (m === 'Test262:AsyncTestComplete') { + asyncResult = { + status: 'PASS', flags: test.currentTestFlag, testId: test.id, file: test.file, + }; + } else { + asyncResult = fail(test, value); + } + setPrintHandle(undefined); + }); + } + + const specifier = path.resolve(TEST262_TESTS, test.file); + + const completion = evalQ((Q) => { + if (test.attrs.flags.module) { + const module = Q(realm.compileModule(test.content, { specifier })); + resolverCache.set(specifier, module); + const loadModuleCompletion = module.LoadRequestedModules(); + if (loadModuleCompletion.PromiseState === 'rejected') { + Q(ThrowCompletion(loadModuleCompletion.PromiseResult!)); + } else if (loadModuleCompletion.PromiseState === 'pending') { + throw new Error('Internal error: .LoadRequestedModules() returned a pending promise'); + } + Q(module.Link()); + const evaluateCompletion = Q(skipDebugger(module.Evaluate())); + if (evaluateCompletion.PromiseState === 'rejected') { + Q(ThrowCompletion(evaluateCompletion.PromiseResult!)); + } + } else { + Q(realm.evaluateScript(test.content, { specifier })); + } + }); + + if (completion.Type === 'throw') { + if (test.attrs.negative && isError(test.attrs.negative.type, completion.Value)) { + return { + status: 'PASS', flags: test.currentTestFlag, testId: test.id, file: test.file, + }; + } else { + return fail(test, completion.Value); + } + } + + if (test.attrs.flags.async) { + if (!asyncResult) { + throw new Error('missing async result'); + } + return asyncResult; + } + + if (test.attrs.negative) { + return fails(test, `Expected ${test.attrs.negative.type} during ${test.attrs.negative.phase}`, []); + } else { + return { + status: 'PASS', flags: test.currentTestFlag, testId: test.id, file: test.file, + }; + } + }); + + return r; +} + +function handleSendError(e: any) { + if (e) { + console.error(e); + process.exit(1); + } +} + +function isError(type: string, value: unknown) { + if (!(value instanceof ObjectValue)) { + return false; + } + const proto = (value as any).Prototype; + if (!proto || !(proto instanceof ObjectValue)) { + return false; + } + const ctorDesc = proto.properties.get(Value('constructor')); + if (!ctorDesc || !IsDataDescriptor(ctorDesc)) { + return false; + } + const ctor = ctorDesc.Value; + if (!(ctor instanceof ObjectValue) || !IsCallable(ctor)) { + return false; + } + const namePropDesc = ctor.properties.get(Value('name')); + if (!namePropDesc || !IsDataDescriptor(namePropDesc)) { + return false; + } + const nameProp = namePropDesc.Value; + return nameProp instanceof JSStringValue && nameProp.stringValue() === type; +} + +function fails(test: Test, error: string, stack: Stack[]): WorkerToSupervisor { + return { + status: 'FAIL', + file: test.file, + flags: test.currentTestFlag, + testId: test.id, + description: test.attrs.description, + error, + stack, + }; +} diff --git a/test/test262/test262.mts b/test/test262/test262.mts new file mode 100644 index 0000000..223984e --- /dev/null +++ b/test/test262/test262.mts @@ -0,0 +1,103 @@ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { relative } from 'node:path'; +import util, { styleText } from 'node:util'; +import { cpus } from 'node:os'; +import { link } from '../base.mts'; +import { isCI } from '../tui.mts'; + +export const args = util.parseArgs({ + args: process.argv.slice(2), + allowNegative: true, + allowPositionals: true, + strict: true, + options: { + 'help': { type: 'boolean', short: 'h' }, + 'features': { type: 'string', short: 'f' }, + 'engine-features': { type: 'string', multiple: true, short: 'e' }, + 'update-slow': { type: 'string' }, + 'update-failed': { type: 'boolean', short: 'u' }, + + 'run-slow': { type: 'boolean' }, + 'failed-only': { type: 'boolean', short: 'f' }, + 'strict-only': { type: 'boolean' }, + 'fast': { type: 'boolean', short: 'q', default: !!isCI }, + + 'verbose': { type: 'boolean', short: 'v' }, + 'vv': { type: 'boolean', short: 'V' }, + }, +}); + +async function main() { + if (args.values.help) { + const TEST_PATTERN = styleText('gray', '[TEST-PATTERN]'); + const SLOW_LIST = link('the slow list file', new URL('./slow', import.meta.url)); + const LAST_FAILED_LIST = link('last-failed-list', new URL('./last-failed-list', import.meta.url)); + const LOCAL_FILE = styleText('gray', '(Local file)'); + const usage = ` + Usage: node ${relative(process.cwd(), import.meta.filename)} ${TEST_PATTERN} ... + Run ${link('test262 tests', 'https://github.com/tc39/test262')} against engine262. + + ${TEST_PATTERN} supports glob syntax, and is interpreted relative to + ${link('the test262 "test" subdirectory', new URL('./test262/test262/test/', import.meta.url))} or (if that fails for any pattern) + relative to the working directory. If no patterns are specified, + all tests are run. + + ${styleText('magentaBright', 'Environment variables:')} + ${styleText('magenta', 'TEST262')} ${styleText('gray', process.env.TEST262 ? `(set to '${process.env.TEST262}')` : '(unset)')} + The test262 directory, which contains the "test" subdirectory. + If empty, it defaults to the "test262" sibling of this file. + ${styleText('magenta', 'NUM_WORKERS')} ${styleText('gray', process.env.NUM_WORKERS ? `(set to '${process.env.NUM_WORKERS}')` : '(unset)')} + The count of child processes that should be created to run tests. + If empty, it defaults to ${cpus().length}. + + ${styleText('greenBright', 'Options:')} + ${styleText('green', '--features / -f')} ${styleText('gray', '[feature]')} + Only run tests that has the specified feature. + ${styleText('green', '--engine-features / -e')} ${styleText('gray', '[feature]')} + Enable specified engine features during test execution. + ${styleText('green', '--update-slow')} ${styleText('gray', '[seconds]')} + Append tests that take longer than the given time to ${SLOW_LIST}. + ${styleText('green', '--update-failed / -u')} + Append failed tests to ${link('the failed list', new URL('./failed', import.meta.url))}. + If test in this list passes, it will be an error. + ${styleText('green', '--run-slow')} + Run slow tests that are listed in ${SLOW_LIST}. + ${styleText('green', '--failed-only / -f')} + Run only the tests that failed in the previous run. + Listed in ${LAST_FAILED_LIST}. + ${styleText('green', '--strict-only / --fast / --q')} + Only run strict mode tests. + ${styleText('green', '--verbose / -v')} + Print why tests are skipped or failed. + ${styleText('green', '--vv / -V')} + Print why tests are skipped or failed, and also print passed tests. + + ${styleText('yellowBright', 'Files:')} + ${link(styleText('yellow', 'features'), new URL('./features', import.meta.url))} + Specifies handling of test262 features, notably which ones to skip. + ${link(styleText('yellow', 'skip'), new URL('./skip', import.meta.url))} + Includes patterns of test files to skip. + ${link(styleText('yellow', 'slow'), new URL('./slow', import.meta.url))} + Includes patterns of test files to skip in the absence of ${styleText('green', '--run-slow-tests')}. + ${link(styleText('yellow', 'failed'), new URL('./failed', import.meta.url))} + Includes patterns of test files that are expected to fail. + ${styleText('yellow', LAST_FAILED_LIST)} ${LOCAL_FILE} + The list of test files that failed in the last run. + ${link(styleText('yellow', 'last-failed.log'), new URL('./last-failed.log', import.meta.url))} ${LOCAL_FILE} + The detailed log of test files that failed in the last run. + `.slice(1); + const indent = usage.match(/^\s*/)![0]; + process.stdout.write( + `${usage + .trimEnd() + .split('\n') + .map((line) => line.replace(indent, '')) + .join('\n')}\n`, + ); + process.exit(64); + } + + await import('./test262-runner.mts'); +} +main(); diff --git a/test/test_root.sh b/test/test_root.sh new file mode 100755 index 0000000..4683e03 --- /dev/null +++ b/test/test_root.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -x + +E=0 + +npm run test:supplemental || E=$? +npm run test:json || E=$? +npm run test:test262 || E=$? + +exit $E diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..2fdb272 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,20 @@ +{ + "references": [{ "path": "../src/" }, { "path": "../lib-src/inspector/" }], + "extends": "../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "erasableSyntaxOnly": true, + "rootDir": "../", + "allowImportingTsExtensions": true + }, + "include": [ + "./base.mts", + "./tui.mts", + "./test262/test262.mts", + "./test262/test262-runner.mts", + "./test262/test262-worker.mts", + "./json/json.mts", + "./inspector/*", + "./engine262/*" + ] +} diff --git a/test/tui.mts b/test/tui.mts new file mode 100644 index 0000000..4b7bd1d --- /dev/null +++ b/test/tui.mts @@ -0,0 +1,405 @@ +/* eslint-disable no-console */ +import { styleText } from 'util'; +import { type WriteStream } from 'fs'; +import * as React from 'react'; +import { + render, Text, useApp, useInput, useStdout, +} from 'ink'; +import { TaskList, Task } from 'ink-task-list'; +import { BarChart } from '@pppp606/ink-chart'; +import { link, type Test } from './base.mts'; + +const { createElement: h } = React; +export const isCI = process.env.CI || process.env.CONTINUOUS_INTEGRATION; +export const supportColor = !isCI && styleText('red', 'test') !== 'test'; + +function Fragment(...children: (React.JSX.Element | null)[]) { + return h(React.Fragment, null, ...children); +} + +// from https://github.com/sindresorhus/cli-spinners +const spinner = { + interval: 80, + frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], +}; + +const quitMessage = [styleText('gray', 'Press '), styleText('yellow', 'q'), styleText('gray', ' to exit.')].join(''); +function TerminalUI({ runner }: { runner: TerminalUIReporter }) { + useFlushStdout(runner); + const exiting = useExit(runner); + + const tasks = useWorkers(runner); + const runtime = JSON.parse(useRuntimeUpdate(runner)) as number[]; + const needPad = runtime.some((time) => time >= runner.slowThreshold); + const runtimePadLength = needPad ? String(Math.max(...runtime)).length : 0; + const previous = React.useRef([]); + + return Fragment( + h(ProgressBar, { runner, running: !exiting, exiting }), + h( + TaskList, + null, + ...(exiting ? [] : tasks).map((test, index) => { + previous.current.length = tasks.length; + previous.current[index] = test?.id ?? previous.current[index]; + + let label: string; + let padding = ''; + let status = ''; + let state: 'loading' | 'success' = 'loading'; + if (test) { + label = test.file; + status = test.currentTestFlag; + if (runtime[index] >= runner.slowThreshold) { + padding = styleText('red', `[${String(runtime[index]).padStart(runtimePadLength)}s] `); + } + } else { + let test = runner.tests.get(previous.current[index]); + if (test && (Date.now() - test.endTime! > 200)) { + test = undefined; + } + label = styleText('dim', test?.file ?? 'Idle'); + status = test?.currentTestFlag ?? ''; + state = test ? 'loading' : 'success'; + if (!test && needPad) { + padding = ' '.repeat(runtimePadLength + 3); + } + } + if (!padding && needPad) { + padding = ' '.repeat(runtimePadLength + 4); + } + label = `${padding}${label}`; + + return h(Task, { + key: index, label, state, spinner, status, + }); + }), + ), + ); +} + +function ProgressBar({ runner, running, exiting }: { runner: TestReporter, running: boolean, exiting: boolean }) { + const stats = useStats(runner); + if (!stats) { + return null; + } + const { + failed, passed, pending, skipped, total, ready, + } = stats; + if (!ready && running) { + return h( + Text, + null, + `Discovering tests... ${total} found so far. ${passed} passed, ${skipped} skipped, ${failed} failed, ${pending} pending. `, + exiting ? '' : quitMessage, + ); + } + return Fragment( + h(Text, null, `${total} tests in total. `, exiting ? '' : quitMessage), + h( + BarChart, + { + data: [ + { label: `${passed} passed`, value: passed, color: 'green' }, + { label: `${skipped} skipped`, value: skipped, color: 'yellow' }, + { label: `${failed} failed`, value: failed, color: 'red' }, + running || pending ? { label: `${pending} pending`, value: pending, color: 'cyan' } : null!, + ].filter(Boolean), + width: 'full', + max: total, + }, + ), + ); +} + +function useStats(runner: TestReporter) { + return React.useSyncExternalStore( + (onUpdate) => { + runner.addEventListener('stats', onUpdate); + return () => runner.removeEventListener('stats', onUpdate); + }, + () => runner.getStats(), + ); +} + +function useWorkers(runner: TestReporter) { + return React.useSyncExternalStore( + (onUpdate) => { + runner.addEventListener('update', onUpdate); + return () => runner.removeEventListener('update', onUpdate); + }, + () => runner.workers, + ); +} + +function useRuntimeUpdate(runner: TestReporter) { + return React.useSyncExternalStore( + (onUpdate) => { + runner.addEventListener('update', onUpdate); + return () => runner.removeEventListener('update', onUpdate); + }, + () => JSON.stringify( + runner.workers.map((test) => test?.getRuntimeSeconds()), + ), + ); +} + +function useExit(runner: TestReporter) { + const exitRequested = React.useSyncExternalStore( + (onExit) => { + runner.addEventListener('exit', onExit); + return () => runner.removeEventListener('exit', onExit); + }, + () => runner.exited, + ); + const { exit } = useApp(); + const [previousExitState, setExitState] = React.useState(false); + if (exitRequested && !previousExitState) { + setExitState(true); + setTimeout(exit, 10); + } + useInput((input, key) => { + if (input === 'q' || (key.ctrl && input === 'c')) { + setExitState(true); + runner.exit(); + } + }); + return previousExitState; +} + +function useFlushStdout(runner: TerminalUIReporter) { + const stdout = useStdout(); + React.useEffect(() => { + function flush() { + stdout.write(runner.pending_stdout.join('')); + runner.pending_stdout.length = 0; + } + runner.addEventListener('flush', flush); + return () => runner.removeEventListener('flush', flush); + }); +} + +export type SkipReason = 'feature-disabled' | 'skip-list' | 'slow-list'; + +export abstract class TestReporter extends EventTarget { + tests: Map = new Map(); + + workers: (Test | undefined)[] = []; + + protected skipped = 0; + + protected passed = 0; + + protected failed = 0; + + protected ready = false; + + setSlowTestReporting(threshold: number, stream: WriteStream) { + this.slowThreshold = threshold; + this.slowStream = stream; + } + + slowThreshold = 2; + + protected slowStream: WriteStream | null = null; + + protected stats: { total: number; pending: number; passed: number; failed: number; skipped: number; ready: boolean } = this.getStats(); + + protected statsStale = false; + + getStats() { + if (!this.statsStale) { + return this.stats; + } + this.statsStale = false; + this.stats = { + total: this.tests.size, + pending: this.tests.size - this.passed - this.failed - this.skipped, + passed: this.passed, + failed: this.failed, + skipped: this.skipped, + ready: this.ready, + }; + return this.stats; + } + + exited = false; + + abstract stdout(...message: string[]): void + + abstract start(): void + + protected slowTimer: NodeJS.Timeout | null = null; + + protected startSlowTimer() { + const seenSlow = new Set(); + this.slowTimer = setInterval(() => { + let hasSlow = false; + for (const test of this.tests.values()) { + if (test.getRuntimeSeconds() >= this.slowThreshold) { + if (this.slowStream && !seenSlow.has(test.file)) { + this.slowStream?.write(`${test.file}\n`); + seenSlow.add(test.file); + } + hasSlow = true; + } + } + if (hasSlow) { + this.dispatchEvent(new Event('update')); + } + }, 400); + } + + allTestsDiscovered() { + this.ready = true; + this.statsStale = true; + this.dispatchEvent(new Event('stats')); + } + + onExit = Promise.withResolvers(); + + exit() { + this.exited = true; + if (this.slowTimer) { + clearInterval(this.slowTimer); + this.slowTimer = null; + } + this.dispatchEvent(new Event('exit')); + } + + addTest(test: Test) { + this.tests.set(test.id, test); + this.statsStale = true; + } + + updateWorker(workerId: number, taskId: number | null) { + if (this.workers.length <= workerId) { + const next = [...this.workers]; + next.length = workerId + 1; + this.workers = next; + } + if (taskId === null) { + this.workers = this.workers.with(workerId, undefined); + } else { + const test = this.tests.get(taskId)!; + test.status = 'running'; + test.startTime = Date.now(); + this.workers = this.workers.with(workerId, test!); + } + this.statsStale = true; + this.dispatchEvent(new Event('update')); + } + + skipTest(testId: number, reason: SkipReason, feature?: string) { + const test = this.tests.get(testId)!; + test.status = 'skipped'; + test.skipReason = reason; + test.skipFeature = feature ?? null; + test.content = ''; + this.skipped += 1; + this.statsStale = true; + test.endTime = Date.now(); + this.dispatchEvent(new Event('stats')); + } + + testFailed(testId: number) { + const test = this.tests.get(testId)!; + test.status = 'failed'; + test.endTime = Date.now(); + test.content = ''; + this.failed += 1; + this.statsStale = true; + this.dispatchEvent(new Event('stats')); + } + + assertFailedTestFails(testId: number) { + this.testPassed(testId); + } + + testPassed(testId: number) { + const test = this.tests.get(testId)!; + test.status = 'passed'; + test.endTime = Date.now(); + test.content = ''; + this.passed += 1; + this.statsStale = true; + this.dispatchEvent(new Event('stats')); + } +} + +class BasicReporter extends TestReporter { + stdout(...message: string[]): void { + process.stdout.write(message.join('')); + } + + start(): void { + this.timer = setInterval(() => { + const { + failed, passed, pending, ready, skipped, total, + } = this.getStats(); + if (ready) { + console.log(`Total ${total}, ${passed} passed, ${skipped} skipped, ${failed} failed, ${pending} pending`); + } else { + console.log(`Total ${total}, ${passed} passed, ${skipped} skipped, ${failed} failed`); + } + const seen = new Set(); + this.workers.forEach((task) => { + if (!task || seen.has(task.file)) { + return; + } + seen.add(task.file); + const time = task?.getRuntimeSeconds() || 0; + if (time >= this.slowThreshold) { + console.log(`${task?.file} is slow. Taking ${time}s.`); + } + }); + }, 1000); + } + + private timer: NodeJS.Timeout | undefined; + + override exit(): void { + super.exit(); + clearInterval(this.timer); + const { + failed, passed, pending, skipped, total, + } = this.getStats(); + console.log(`Total ${total}, ${passed} passed, ${skipped} skipped, ${failed} failed, ${pending} pending`); + this.onExit.resolve(); + } +} +class TerminalUIReporter extends TestReporter { + pending_stdout: string[] = []; + + stdout(...message: string[]) { + this.pending_stdout.push(...message); + this.statsStale = true; + this.dispatchEvent(new Event('flush')); + } + + start() { + render(h(TerminalUI, { runner: this }), { incrementalRendering: true, exitOnCtrlC: false }).waitUntilExit().then(this.onExit.resolve); + this.startSlowTimer(); + } + + override exit() { + super.exit(); + process.stdin.setRawMode(false); + } +} + +export function createTestReporter(): TestReporter { + if (isCI) { + return new BasicReporter(); + } else { + return new TerminalUIReporter(); + } +} + +export function annotateFileWithURL(filePath: string) { + if (supportColor) { + const fileLink = link(filePath, new URL(`./test262/test262/test/${filePath}`, import.meta.url)); + return `${fileLink} ${link('[GitHub]', `https://github.com/tc39/test262/blob/main/test/${filePath}`)}`; + } + return filePath; +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..ccc1de9 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,38 @@ +{ + "references": [{ "path": "./lib-src/node" }, { "path": "./lib-src/inspector" }, { "path": "./src" }], + "compilerOptions": { + "strict": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true, + + "module": "NodeNext", + "moduleResolution": "NodeNext", + "verbatimModuleSyntax": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + + "declaration": true, + // to make declaration map useful, we need to publish src + // but it might help when debugging + "declarationMap": true, + "stripInternal": true, + "sourceMap": true, + + "allowJs": true, + // "checkJs": true, + + "forceConsistentCasingInFileNames": true, + "lib": ["ES2024"], + + "incremental": true, + "skipLibCheck": true, + "paths": { + "#self": ["./src/index.mts"] + } + }, + "files": [] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..df0207c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "references": [ + { "path": "./test/eslint-plugin-engine262" }, + { "path": "./lib-src/node" }, + { "path": "./lib-src/inspector" }, + { "path": "./src" }, + { "path": "./test" } + ], + "files": [] +} diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 0000000..c90591b --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + reportsDirectory: 'coverage/inspector/', + include: ['lib-src/inspector/**', 'lib/inspector.mjs'], + }, + }, +}); diff --git a/website b/website new file mode 160000 index 0000000..d755bd3 --- /dev/null +++ b/website @@ -0,0 +1 @@ +Subproject commit d755bd3dc76e8ca1b095ef0e722e025b5f0b5e05