mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
!.eslintrc.js
|
||||
lib
|
||||
bin/engine262.mjs
|
||||
test/test262/test262
|
||||
test/json/JSONTestSuite
|
||||
website
|
||||
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
// TODO:
|
||||
// - Any spec object must inherit from OrdinaryObject or ExoticObject.
|
||||
// - JSString | SymbolValue => PropertyKeyValue
|
||||
// - NormalCompletion<T> | ThrowCompletion => PlainCompletion<T>
|
||||
// - PlainCompletion<Value> => 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',
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
Vendored
+12
@@ -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
|
||||
+62
@@ -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}}
|
||||
Vendored
+48
@@ -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}}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
test
|
||||
src
|
||||
declaration/.tsbuildinfo
|
||||
declaration/**/*.map
|
||||
scripts
|
||||
coverage
|
||||
rollup.config.mts
|
||||
.eslintcache
|
||||
.eslintrc.js
|
||||
.eslintignore
|
||||
.travis.yml
|
||||
Vendored
+21
@@ -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": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}\\bin\\engine262.js",
|
||||
"args": ["d:/dev/scratch/dispose.js"],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/lib/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+13
@@ -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"
|
||||
},
|
||||
}
|
||||
@@ -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/
|
||||
@@ -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.
|
||||
@@ -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<ParseNode.DoExpression>();
|
||||
+ 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 \<string> / -e \<string>
|
||||
|
||||
Evaluate the given string and exit.
|
||||
|
||||
#### --features=\<featureA,featureB> / --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.
|
||||
|
||||
- <https://github.com/NeilFraser/JS-Interpreter>
|
||||
- <https://github.com/metaes/metaes>
|
||||
- <https://github.com/Siubaak/sval>
|
||||
|
||||
[Babel]: https://babeljs.io/
|
||||
[COC]: https://github.com/engine262/engine262/blob/master/CODE_OF_CONDUCT.md
|
||||
[do expressions]: https://github.com/tc39/proposal-do-expressions
|
||||
[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
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["@babel/plugin-transform-explicit-resource-management"]
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
|
||||
// this file is for esvu compatibility
|
||||
(async () => import('../lib/node/bin.mjs'))();
|
||||
@@ -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<string, ObjectValue | SymbolValue>();
|
||||
|
||||
// id 0 is falsy, skip it
|
||||
#idToArrayBufferBlock: (undefined | ArrayBuffer)[] = [undefined];
|
||||
|
||||
#objectToId = new Map<ObjectValue | SymbolValue, string>();
|
||||
|
||||
#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<Value, number>();
|
||||
|
||||
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() || '<anonymous>',
|
||||
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');
|
||||
}
|
||||
@@ -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<string, object>)[namespace];
|
||||
if (!(method in ns)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Unknown method requested: ${namespace}.${method}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const f = (ns as Record<string, (args: unknown, context: DebuggerContext) => 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<string, unknown>) => {
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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<T extends Value> {
|
||||
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<NullValue> = {
|
||||
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<UndefinedValue> = {
|
||||
toRemoteObject: () => ({ type: 'undefined' }),
|
||||
toObjectPreview: () => ({
|
||||
type: 'undefined', properties: [], overflow: false,
|
||||
}),
|
||||
toPropertyPreview: (name) => ({
|
||||
name, type: 'undefined', value: 'undefined',
|
||||
}),
|
||||
toDescription: () => 'undefined',
|
||||
};
|
||||
|
||||
const Boolean: Inspector<BooleanValue> = {
|
||||
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<SymbolValue> = {
|
||||
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<JSStringValue> = {
|
||||
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<NumberValue> = {
|
||||
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<FunctionObject> = {
|
||||
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<T extends ObjectValue> implements Inspector<T> {
|
||||
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<ObjectValue>('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<ObjectValue>('Object', undefined, (object) => {
|
||||
const [ctor] = object.ConstructedBy;
|
||||
if (!ctor) {
|
||||
return 'Object';
|
||||
}
|
||||
return propertyNameToString(ctor.HostInitialName);
|
||||
});
|
||||
|
||||
const ArrayBuffer = new ObjectInspector<ArrayBufferObject>('ArrayBuffer', 'arraybuffer', (value) => `ArrayBuffer(${value.ArrayBufferByteLength})`, {});
|
||||
const DataView = new ObjectInspector<DataViewObject>('DataView', 'dataview', (value) => `DataView(${value.ByteLength})`);
|
||||
const Error = new ObjectInspector<ObjectValue>('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<MapObject>('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<SetObject>('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<WeakMapObject>('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<WeakSetObject>('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<DateObject>('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<JSStringValue>).stringValue();
|
||||
}));
|
||||
const TemporalInstant = new ObjectInspector<TemporalInstantObject>(
|
||||
'Temporal.Instant',
|
||||
'date',
|
||||
(value) => `Temporal.Instant <${TemporalInstantToString(value, undefined, 'auto')}>`,
|
||||
);
|
||||
const TemporalDuration = new ObjectInspector<TemporalDurationObject>('Temporal.Duration', 'date', (value) => `Temporal.Duration <${TemporalDurationToString(value, 'auto')}>`);
|
||||
const TemporalPlainDate = new ObjectInspector<TemporalPlainDateObject>('Temporal.PlainDate', 'date', (value) => `Temporal.PlainDate <${TemporalDateToString(value, 'auto')}>`);
|
||||
const TemporalPlainDateTime = new ObjectInspector<TemporalPlainDateTimeObject>(
|
||||
'Temporal.PlainDateTime',
|
||||
'date',
|
||||
(value) => `Temporal.PlainDateTime <${ISODateTimeToString(value.ISODateTime, value.Calendar, 'auto', 'auto')}>`,
|
||||
);
|
||||
const TemporalPlainMonthDay = new ObjectInspector<TemporalPlainMonthDayObject>(
|
||||
'Temporal.PlainMonthDay',
|
||||
'date',
|
||||
(value) => `Temporal.PlainMonthDay <${TemporalMonthDayToString(value, 'auto')}>`,
|
||||
);
|
||||
const TemporalPlainTime = new ObjectInspector<TemporalPlainTimeObject>('Temporal.PlainTime', 'date', (value) => `Temporal.PlainTime <${TimeRecordToString(value.Time, 'auto')}>`);
|
||||
const TemporalPlainYearMonth = new ObjectInspector<TemporalPlainYearMonthObject>(
|
||||
'Temporal.PlainYearMonth',
|
||||
'date',
|
||||
(value) => `Temporal.PlainYearMonth <${TemporalYearMonthToString(value, 'auto')}>`,
|
||||
);
|
||||
const TemporalZonedDateTime = new ObjectInspector<TemporalZonedDateTimeObject>(
|
||||
'Temporal.ZonedDateTime',
|
||||
'date',
|
||||
(value) => `Temporal.ZonedDateTime <${TemporalZonedDateTimeToString(value, 'auto', 'auto', 'auto', 'auto')}>`,
|
||||
);
|
||||
const Promise = new ObjectInspector<PromiseObject>('Promise', 'promise', () => 'Promise', {
|
||||
internalProperties: (value) => [['[[PromiseState]]', Value(value.PromiseState)], ['[[PromiseResult]]', value.PromiseResult || Value.undefined]],
|
||||
});
|
||||
const Proxy = new ObjectInspector<ProxyObject>('Proxy', 'proxy', (value) => {
|
||||
if (IsCallable(value.ProxyTarget)) {
|
||||
return 'Proxy(Function)';
|
||||
}
|
||||
if (value.ProxyTarget instanceof ObjectValue) {
|
||||
return 'Proxy(Object)';
|
||||
}
|
||||
return 'Proxy';
|
||||
});
|
||||
const RegExp = new ObjectInspector<RegExpObject>('RegExp', 'regexp', (value) => `/${value.OriginalSource.stringValue()}/${value.OriginalFlags.stringValue()}`);
|
||||
const Module = new ObjectInspector<ModuleNamespaceObject>('Module', undefined, () => 'Module', {});
|
||||
const ShadowRealm = new ObjectInspector<ShadowRealmObject>('ShadowRealm', undefined, () => 'ShadowRealm', {
|
||||
internalProperties: (realm) => [['[[GlobalObject]]', realm.ShadowRealm.GlobalObject]],
|
||||
});
|
||||
|
||||
const Array: Inspector<ObjectValue> = {
|
||||
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<TypedArrayObject>('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<Value> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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: '',
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<FunctionObject>;
|
||||
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<Protocol.Runtime.EvaluateResponse> {
|
||||
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<Protocol.Runtime.EvaluateResponse>((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;
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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<Protocol.Debugger.EvaluateOnCallFrameResponse>;
|
||||
/** 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<Protocol.Runtime.EvaluateResponse>;
|
||||
/** 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;
|
||||
}
|
||||
@@ -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<Record<ConsoleMethod, (args: Arguments) => void | PlainCompletion<void> | PlainEvaluator<void>>> & { default?: (method: ConsoleMethod, args: Arguments) => void | PlainCompletion<void> | PlainEvaluator<void> },
|
||||
) {
|
||||
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<Value> {
|
||||
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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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<string> {
|
||||
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());
|
||||
}
|
||||
@@ -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<string> {
|
||||
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));
|
||||
}
|
||||
});
|
||||
@@ -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<string, unknown>) => {
|
||||
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<http.IncomingMessage>) {
|
||||
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<NodeWebsocketInspector>((resolve) => {
|
||||
server.listen(port, host, () => {
|
||||
resolve(inspector);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._server.close();
|
||||
this._ws.close();
|
||||
}
|
||||
}
|
||||
@@ -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<string, string>, 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<string, string>, 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<NonNullable<AgentHostDefined['loadImportedModule']>>
|
||||
) => {
|
||||
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<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
|
||||
readFile(path, 'utf8', callback);
|
||||
});
|
||||
const loadImportedModuleSync: NonNullable<AgentHostDefined['loadImportedModule']> = 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();
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Generated
+9250
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<string, void, void> {
|
||||
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<string, readonly Range[]> = {};
|
||||
|
||||
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<string, string> = {};
|
||||
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<string, string> = {};
|
||||
const sc: Record<string, string> = {};
|
||||
const scx: Record<string, string> = {};
|
||||
// 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(),
|
||||
]);
|
||||
@@ -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<string> {
|
||||
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<string>();
|
||||
const promises: Promise<void>[] = [];
|
||||
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');
|
||||
}
|
||||
@@ -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<string, string>;
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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__<T>(_value: unknown): asserts _value is T { }
|
||||
|
||||
function findParentStatementPath(path: NodePath): NodePath<t.Statement> | 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<Record<NeededNames, boolean>>;
|
||||
}
|
||||
|
||||
interface Macro<R extends PublicReplacements = Record<string, Node | null>> {
|
||||
template(sourceLocation: Node, replacements: Readonly<R>): 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<State> => {
|
||||
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<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> | NodePath<t.ExportNamedDeclaration>) {
|
||||
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<Node, 'start' | 'end' | 'loc'>;
|
||||
|
||||
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);
|
||||
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<t.CallExpression>) {
|
||||
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);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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<ObjectInternalMethods<MappedArgumentsObject>>;
|
||||
|
||||
/** 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<UnmappedArgumentsObject>;
|
||||
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;
|
||||
}
|
||||
@@ -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<BooleanValue> {
|
||||
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<ObjectInternalMethods<OrdinaryObject>>;
|
||||
|
||||
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<OrdinaryObject> {
|
||||
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<OrdinaryObject>;
|
||||
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<ObjectValue> {
|
||||
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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<NumberValue> {
|
||||
// 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<GeneratorObject> {
|
||||
// 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;
|
||||
}
|
||||
@@ -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<ArrayBufferObject> {
|
||||
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<ArrayBufferObject>;
|
||||
// 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<ResizableArrayBufferObject>).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<ArrayBufferObject>, 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<ArrayBufferObject> {
|
||||
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<NumberValue | BigIntValue>;
|
||||
// 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<UndefinedValue> {
|
||||
// 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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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__<AsyncGeneratorObject>(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';
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__<DataViewObject>(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__<DataViewObject>(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;
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<ObjectValue>;
|
||||
}
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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<ObjectValue> {
|
||||
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<ECMAScriptFunctionObject>;
|
||||
// 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<ECMAScriptFunctionObject> | 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<ECMAScriptFunctionObject>).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<FunctionObject>): void {
|
||||
Assert(F.IsClassConstructor === Value.false);
|
||||
F.IsClassConstructor = Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-makemethod */
|
||||
export function MakeMethod(F: Mutable<ECMAScriptFunctionObject>, 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<void> {
|
||||
// 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<DescriptorInit> = { 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<FunctionObject>).InitialName = name;
|
||||
}
|
||||
if ('HostInitialName' in F) {
|
||||
// a. Set F.[[InitialName]] to name.
|
||||
(F as Mutable<ECMAScriptFunctionObject>).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<ObjectValue> {
|
||||
// Assert in the BuiltinCallOrConstruct
|
||||
return BuiltinCallOrConstruct(this, 'uninitialized', argumentsList, newTarget) as ValueEvaluator<ObjectValue>;
|
||||
}
|
||||
|
||||
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<BuiltinFunctionObject>;
|
||||
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();
|
||||
}
|
||||
@@ -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__<GeneratorObject>(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__<GeneratorObject>(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__<GeneratorObject>(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<GeneratorObject> {
|
||||
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<GeneratorObject>;
|
||||
// 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;
|
||||
}
|
||||
@@ -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<void | Value> = 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__<EnvironmentRecord>(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);
|
||||
}
|
||||
@@ -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<BooleanValue> {
|
||||
// 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;
|
||||
}
|
||||
@@ -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<AbstractModuleRecord>,
|
||||
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.
|
||||
}
|
||||
@@ -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<IteratorRecord> {
|
||||
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<IteratorRecord> {
|
||||
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<IteratorRecord> {
|
||||
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<IteratorRecord> {
|
||||
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<ObjectValue> {
|
||||
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<BooleanValue> {
|
||||
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<ObjectValue | 'done'> {
|
||||
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<Value | 'done'> {
|
||||
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<T, C extends Completion<T>>(iteratorRecord: IteratorRecord, completion: C): Evaluator<C | ThrowCompletion> {
|
||||
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<C>(iters: Iterable<IteratorRecord>, completion: Completion<C>): Evaluator<Completion<C>> {
|
||||
for (const iter of [...iters].reverse()) {
|
||||
completion = yield* IteratorClose(iter, completion);
|
||||
}
|
||||
return completion;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-asynciteratorclose */
|
||||
export function* AsyncIteratorClose<T, C extends Completion<T>>(iteratorRecord: IteratorRecord, completion: C | T) {
|
||||
Assert(iteratorRecord.Iterator instanceof ObjectValue);
|
||||
const iterator = iteratorRecord.Iterator;
|
||||
let innerResult: NormalCompletion<Value> | 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<Value>): 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<Value[]> {
|
||||
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<AsyncFromSyncIteratorObject>;
|
||||
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<PromiseObject> {
|
||||
const done = yield* IteratorComplete(result);
|
||||
IfAbruptRejectPromise(done, promiseCapability);
|
||||
__ts_cast__<BooleanValue>(done);
|
||||
const value = yield* IteratorValue(result);
|
||||
IfAbruptRejectPromise(value, promiseCapability);
|
||||
__ts_cast__<Value>(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__<PromiseObject>(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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<ObjectInternalMethods<ModuleNamespaceObject>>;
|
||||
|
||||
/** 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<ModuleNamespaceObject>;
|
||||
// 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<AbstractModuleRecord>).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<AbstractModuleRecord>).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<JSStringSet> {
|
||||
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<CyclicModuleRecord>): 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;
|
||||
}
|
||||
@@ -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<CyclicModuleRecord>();
|
||||
|
||||
PendingModules = 1;
|
||||
|
||||
constructor({ PromiseCapability, HostDefined }: Pick<GraphLoadingState, 'PromiseCapability' | 'HostDefined'>) {
|
||||
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<AbstractModuleRecord>) {
|
||||
// 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<number> {
|
||||
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<undefined> {
|
||||
// 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<number> {
|
||||
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>): 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<AbstractModuleRecord>, 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<unknown>, 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);
|
||||
}
|
||||
@@ -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<unknown>): 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<unknown>) => {
|
||||
/* 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;
|
||||
}
|
||||
@@ -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<const T extends string>(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<T, unknown>;
|
||||
Object.assign(obj, internalSlotsList.reduce((extraFields, currentField) => {
|
||||
extraFields[currentField] = Value.undefined;
|
||||
return extraFields;
|
||||
}, {} as Record<T, unknown>));
|
||||
// 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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<UndefinedValue | FunctionObject> {
|
||||
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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<ObjectValue> {
|
||||
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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<number> {
|
||||
// 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<Value[]>
|
||||
export function CreateListFromArrayLike(obj: Value, validElementTypes: 'property-key'): PlainEvaluator<PropertyKeyValue[]>
|
||||
export function* CreateListFromArrayLike(obj: Value, validElementTypes: 'all' | 'property-key' = 'all'): PlainEvaluator<Value[]> {
|
||||
// 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<BooleanValue> {
|
||||
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<FunctionObject> {
|
||||
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<JSStringValue[]>
|
||||
export function EnumerableOwnProperties(O: ObjectValue, kind: 'value'): PlainEvaluator<Value[]>
|
||||
export function EnumerableOwnProperties(O: ObjectValue, kind: 'key' | 'value' | 'key+value'): PlainEvaluator<ObjectValue[]>
|
||||
export function* EnumerableOwnProperties(O: ObjectValue, kind: 'key' | 'value' | 'key+value'): PlainEvaluator<Value[]> {
|
||||
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<Realm> {
|
||||
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<ObjectValue> {
|
||||
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<KeyedGroupRecord[]> {
|
||||
/*
|
||||
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__<Value>(key);
|
||||
|
||||
if (keyCoercion === 'property') {
|
||||
key = yield* ToPropertyKey(key);
|
||||
IfAbruptCloseIterator(key, iteratorRecord);
|
||||
} else {
|
||||
Assert(keyCoercion === 'collection');
|
||||
key = CanonicalizeKeyedCollectionKey(key);
|
||||
}
|
||||
__ts_cast__<PropertyKeyValue>(key);
|
||||
|
||||
AddValueToKeyedGroup(groups, key, value);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
@@ -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<DescriptorInit> = {};
|
||||
|
||||
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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<BooleanValue> {
|
||||
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<const T extends string>(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<string, Value | CanBeNativeSteps>, 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<const T extends string>(constructor: FunctionObject, intrinsicDefaultProto: keyof Intrinsics, internalSlotsList?: readonly T[]): ValueEvaluator<ObjectValue> {
|
||||
// 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<ObjectValue> {
|
||||
// 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;
|
||||
}
|
||||
@@ -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<void> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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<UndefinedValue> {
|
||||
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<PromiseCapabilityRecord> {
|
||||
// 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<PromiseCapabilityRecord>;
|
||||
// 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<PromiseObject> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<ProxyObject>;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-proxycreate */
|
||||
export function ProxyCreate(target: Value, handler: Value): ValueCompletion<ProxyObject> {
|
||||
// 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;
|
||||
}
|
||||
@@ -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,
|
||||
})));
|
||||
}
|
||||
@@ -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<ReferenceRecord['Base'], 'unresolvable' | EnvironmentRecord>,
|
||||
};
|
||||
|
||||
/** 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<Value> {
|
||||
// 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__<PropertyReference>(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<ReferenceRecord>, 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,
|
||||
});
|
||||
}
|
||||
@@ -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<RegExpObject> {
|
||||
const obj = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%RegExp.prototype%', ['RegExpMatcher', 'OriginalSource', 'OriginalFlags'])) as Mutable<RegExpObject>;
|
||||
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<RegExpObject>, 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<RegExpObject> {
|
||||
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;
|
||||
}
|
||||
@@ -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<WrappedFunctionExoticObject>;
|
||||
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<Value | void> = 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<void> {
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Descriptor> {
|
||||
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);
|
||||
}
|
||||
@@ -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<ObjectInternalMethods<StringObject>>;
|
||||
|
||||
/** 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<StringObject>;
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<number> {
|
||||
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<const T extends readonly string[], D extends T[number] | undefined>(options: ObjectValue, property: PropertyKeyValue | string, type: 'string', values: T | undefined, defaultValue: '~required~' | D): PlainEvaluator<D | T[number]>;
|
||||
export function GetOption<D extends boolean | undefined>(options: ObjectValue, property: PropertyKeyValue | string, type: 'boolean', values: undefined, defaultValue: '~required~' | D): PlainEvaluator<D>;
|
||||
export function* GetOption(options: ObjectValue, property: PropertyKeyValue | string, type: 'boolean' | 'string', values: readonly string[] | undefined, defaultValue: '~required~' | string | boolean | undefined): PlainEvaluator<string | boolean> {
|
||||
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<RoundingMode> {
|
||||
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<number> {
|
||||
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,
|
||||
}];
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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<CalendarType> {
|
||||
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<CalendarFieldsRecord> {
|
||||
// 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<CalendarFieldsRecord> = {
|
||||
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__<keyof CalendarFieldsRecord>(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<CalendarFieldsRecord> = {
|
||||
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<ISODateRecord> {
|
||||
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<CalendarType> {
|
||||
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<CalendarType> {
|
||||
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<ISODateRecord> {
|
||||
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<ISODateRecord> {
|
||||
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<ISODateRecord> {
|
||||
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<ISODateRecord> {
|
||||
mark_OtherCalendarNotImplemented();
|
||||
unreachable_OtherCalendarNotImplemented();
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso */
|
||||
export function CalendarDateToISO(
|
||||
calendar: CalendarType,
|
||||
fields: CalendarFieldsRecord,
|
||||
overflow: 'constrain' | 'reject',
|
||||
): PlainCompletion<ISODateRecord> {
|
||||
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<ISODateRecord> {
|
||||
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<void> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<TemporalInstantObject> {
|
||||
Assert(IsValidEpochNanoseconds(epochNanoseconds));
|
||||
if (newTarget === undefined) {
|
||||
newTarget = surroundingAgent.intrinsic('%Temporal.Instant%');
|
||||
}
|
||||
const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.Instant.prototype%', [
|
||||
'InitializedTemporalInstant',
|
||||
'EpochNanoseconds',
|
||||
])) as Mutable<TemporalInstantObject>;
|
||||
object.EpochNanoseconds = epochNanoseconds;
|
||||
return object;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant */
|
||||
export function* ToTemporalInstant(item: Value): ValueEvaluator<TemporalInstantObject> {
|
||||
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<bigint> {
|
||||
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<TemporalDurationObject> {
|
||||
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<TemporalInstantObject> {
|
||||
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));
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
@@ -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<ISODateTimeRecord> {
|
||||
let timeZone;
|
||||
if (temporalTimeZoneLike === Value.undefined) {
|
||||
timeZone = SystemTimeZoneIdentifier();
|
||||
} else {
|
||||
timeZone = Q(ToTemporalTimeZoneIdentifier(temporalTimeZoneLike));
|
||||
}
|
||||
const epochNs = SystemUTCEpochNanoseconds();
|
||||
return GetISODateTimeFor(timeZone, epochNs);
|
||||
}
|
||||
@@ -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<ISODateTimeRecord> {
|
||||
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<TemporalPlainDateTimeObject> {
|
||||
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<TemporalPlainDateTimeObject> {
|
||||
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<TemporalPlainDateTimeObject>;
|
||||
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<InternalDurationRecord> {
|
||||
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<number> {
|
||||
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<TemporalDurationObject> {
|
||||
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<TemporalPlainDateTimeObject> {
|
||||
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));
|
||||
}
|
||||
@@ -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<TemporalPlainDateObject> {
|
||||
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<TemporalPlainDateObject>;
|
||||
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<TemporalPlainDateObject> {
|
||||
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<ISODateRecord> {
|
||||
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<TemporalDurationObject> {
|
||||
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<TemporalPlainDateObject> {
|
||||
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));
|
||||
}
|
||||
@@ -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<TemporalPlainMonthDayObject> {
|
||||
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<TemporalPlainMonthDayObject> {
|
||||
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<TemporalPlainMonthDayObject>;
|
||||
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;
|
||||
}
|
||||
@@ -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<TemporalPlainTimeObject> {
|
||||
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<TimeRecord> {
|
||||
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<TimeRecord> {
|
||||
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<TemporalPlainTimeObject> {
|
||||
if (newTarget === undefined) {
|
||||
newTarget = surroundingAgent.intrinsic('%Temporal.PlainTime%');
|
||||
}
|
||||
const object = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%Temporal.PlainTime.prototype%', [
|
||||
'InitializedTemporalTime',
|
||||
'Time',
|
||||
])) as Mutable<TemporalPlainTimeObject>;
|
||||
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<TemporalTimeLike> {
|
||||
const result: Mutable<TemporalTimeLike> = {
|
||||
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<TemporalDurationObject> {
|
||||
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<TemporalPlainTimeObject> {
|
||||
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));
|
||||
}
|
||||
@@ -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<TemporalPlainYearMonthObject> {
|
||||
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<TemporalPlainYearMonthObject> {
|
||||
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<TemporalPlainYearMonthObject>;
|
||||
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<TemporalDurationObject> {
|
||||
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<TemporalPlainYearMonthObject> {
|
||||
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));
|
||||
}
|
||||
@@ -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<void> {
|
||||
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<TemporalOffsetOption> {
|
||||
// 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<ShowCalendarNameOption> {
|
||||
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<ShowTimeZoneNameOption> {
|
||||
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<DirectionOption> {
|
||||
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<void> {
|
||||
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<TimeUnit, TemporalUnit.Hour> | '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<TemporalUnit | 'unset' | 'auto'> {
|
||||
// 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<string>((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<TemporalUnit | 'auto'>): PlainCompletion<void> {
|
||||
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<boolean> {
|
||||
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<number> {
|
||||
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<number> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -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<TimeZoneIdentifier> {
|
||||
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<bigint> {
|
||||
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<bigint> {
|
||||
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<bigint[]> {
|
||||
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<bigint> {
|
||||
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;
|
||||
}
|
||||
@@ -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<bigint> {
|
||||
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<TemporalZonedDateTimeObject> {
|
||||
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<TemporalZonedDateTimeObject> {
|
||||
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<TemporalZonedDateTimeObject>;
|
||||
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<bigint> {
|
||||
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<InternalDurationRecord> {
|
||||
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<InternalDurationRecord> {
|
||||
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<number> {
|
||||
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<TemporalDurationObject> {
|
||||
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<TemporalZonedDateTimeObject> {
|
||||
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));
|
||||
}
|
||||
@@ -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<BooleanValue> {
|
||||
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<BooleanValue | UndefinedValue> {
|
||||
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<BooleanValue> {
|
||||
// 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);
|
||||
}
|
||||
@@ -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<PrimitiveValue> {
|
||||
// 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<PrimitiveValue> {
|
||||
// 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<NumberValue | BigIntValue> {
|
||||
// 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<NumberValue> {
|
||||
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<number> {
|
||||
// 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<NumberValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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<BigIntValue> {
|
||||
// 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<BigIntValue> {
|
||||
// 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<BigIntValue> {
|
||||
// 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<JSStringValue> {
|
||||
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<ObjectValue> {
|
||||
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<BooleanObject>;
|
||||
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<NumberObject>;
|
||||
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<SymbolObject>;
|
||||
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<BigIntObject>;
|
||||
obj.BigIntData = argument;
|
||||
return obj;
|
||||
}
|
||||
Assert(argument instanceof ObjectValue);
|
||||
return argument;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-topropertykey */
|
||||
export function* ToPropertyKey(argument: Value): ValueEvaluator<PropertyKeyValue> {
|
||||
// 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<NumberValue> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -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<ObjectInternalMethods<TypedArrayObject>>;
|
||||
|
||||
/** 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<BooleanValue> {
|
||||
// 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<unknown>();
|
||||
const weakrefs = new Set<WeakRefObject>();
|
||||
const fgs = new Set<FinalizationRegistryObject>();
|
||||
const weakmaps = new Set<WeakMapObject>();
|
||||
const weaksets = new Set<WeakSetObject>();
|
||||
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<void>;
|
||||
resolverCache?: Map<string, AbstractModuleRecord>;
|
||||
|
||||
randomSeed?(): string;
|
||||
attachingInspector?: unknown;
|
||||
attachingInspectorReportError?(realm: Realm, error: Value): void;
|
||||
/**
|
||||
* See https://tc39.es/ecma262/#sec-HostLoadImportedModule
|
||||
* In case of
|
||||
* <button type="button" onclick="import('./foo.mjs')">Click me</button>
|
||||
* 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<ArrayBufferLike> | 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<T>(cb: () => T, inspectorPreview?: boolean): T
|
||||
|
||||
scope<T>(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<ScriptRecord> {
|
||||
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<Value> | 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<void> => {
|
||||
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<SourceTextModuleRecord>
|
||||
|
||||
evaluateModule<T extends ModuleRecord>(sourceText: T, specifier: string): PlainCompletion<T>
|
||||
|
||||
evaluateModule(sourceText: string | ModuleRecord, specifier: string): PlainCompletion<ModuleRecord> {
|
||||
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<SourceTextModuleRecord> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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: <T>(init: NormalCompletionInit<T>) => NormalCompletionImpl<T>;
|
||||
let createBreakCompletion: (init: BreakCompletionInit) => BreakCompletion;
|
||||
let createContinueCompletion: (init: ContinueCompletionInit) => ContinueCompletion;
|
||||
let createReturnCompletion: (init: ReturnCompletionInit) => ReturnCompletion;
|
||||
let createThrowCompletion: (init: ThrowCompletionInit) => ThrowCompletion_;
|
||||
|
||||
type NormalCompletionInit<T> = Pick<NormalCompletion<T>, 'Type' | 'Value' | 'Target'>;
|
||||
|
||||
type BreakCompletionInit = Pick<BreakCompletion, 'Type' | 'Value' | 'Target'>;
|
||||
|
||||
type ContinueCompletionInit = Pick<ContinueCompletion, 'Type' | 'Value' | 'Target'>;
|
||||
|
||||
type ReturnCompletionInit = Pick<ReturnCompletion, 'Type' | 'Value' | 'Target'>;
|
||||
|
||||
type ThrowCompletionInit = Pick<ThrowCompletion, 'Type' | 'Value' | 'Target'>;
|
||||
|
||||
type AbruptCompletionInit =
|
||||
| BreakCompletionInit
|
||||
| ContinueCompletionInit
|
||||
| ReturnCompletionInit
|
||||
| ThrowCompletionInit;
|
||||
|
||||
type CompletionInit<T> =
|
||||
| NormalCompletionInit<T>
|
||||
| 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<const T> {
|
||||
declare readonly Type: 'normal' | 'break' | 'continue' | 'return' | 'throw';
|
||||
|
||||
readonly Value!: T | Value;
|
||||
|
||||
readonly Target!: JSStringValue | undefined;
|
||||
|
||||
constructor(init: CompletionInit<T>) {
|
||||
if (new.target === CompletionImpl) {
|
||||
switch (init.Type) {
|
||||
case 'normal':
|
||||
return createNormalCompletion(init);
|
||||
case 'break':
|
||||
return createBreakCompletion(init) as CompletionImpl<T>;
|
||||
case 'continue':
|
||||
return createContinueCompletion(init) as CompletionImpl<T>;
|
||||
case 'return':
|
||||
return createReturnCompletion(init) as CompletionImpl<T>;
|
||||
case 'throw':
|
||||
return createThrowCompletion(init) as CompletionImpl<T>;
|
||||
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<T> =
|
||||
| NormalCompletion<T>
|
||||
| AbruptCompletion<T>;
|
||||
|
||||
/**
|
||||
* A NON-SPEC shorthand to notate "returns either a normal completion containing an ECMAScript language value or a throw completion".
|
||||
*/
|
||||
// export type ValueEvaluator<T extends Value = Value> = T | NormalCompletion<T> | ThrowCompletion;
|
||||
export type ValueCompletion<T extends Value = Value> = T | NormalCompletion<T> | 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<T>.
|
||||
*/
|
||||
export type PlainCompletion<T> = T | NormalCompletion<T> | ThrowCompletion;
|
||||
export type YieldCompletion = NormalCompletion<Value> | ThrowCompletion | ReturnCompletion;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-completion-ao */
|
||||
export const Completion = CompletionImpl as {
|
||||
/** https://tc39.es/ecma262/#sec-completion-ao */
|
||||
<T extends Completion<unknown>>(completionRecord: T): T;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
||||
new <const T>(completion: { Type: 'normal', Value: T, Target: undefined }): NormalCompletion<T>;
|
||||
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<unknown>;
|
||||
};
|
||||
|
||||
@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<const T> extends CompletionImpl<T> {
|
||||
declare readonly Type: 'normal';
|
||||
|
||||
declare readonly Value: T;
|
||||
|
||||
declare readonly Target: undefined;
|
||||
|
||||
private constructor(init: NormalCompletionInit<T>) { // 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<T> = NormalCompletionImpl<T>;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-normalcompletion */
|
||||
export const NormalCompletion = NormalCompletionImpl as typeof NormalCompletionImpl & {
|
||||
/** https://tc39.es/ecma262/#sec-normalcompletion */
|
||||
<const T>(value: T): NormalCompletion<T>;
|
||||
};
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
||||
export type AbruptCompletion<T = unknown> =
|
||||
| ThrowCompletion
|
||||
| ReturnCompletion
|
||||
| BreakCompletion
|
||||
| ContinueCompletion;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
||||
export const AbruptCompletion = (() => {
|
||||
abstract class AbruptCompletion<const T> extends CompletionImpl<T | Value> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
declare readonly Type: 'throw';
|
||||
|
||||
declare readonly Value: Value;
|
||||
|
||||
declare readonly Target: undefined;
|
||||
|
||||
readonly stack = debugging ? new Error() : undefined;
|
||||
|
||||
private constructor(init: Pick<ThrowCompletion_, 'Type' | 'Value' | 'Target'>) { // 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<T extends Completion<unknown>, U> =
|
||||
T extends NormalCompletion<infer V> ? NormalCompletion<V extends undefined ? U : V> :
|
||||
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<C extends Completion<unknown>, const T>(completionRecord: C, value: T): UpdateEmpty<C, T>;
|
||||
export function UpdateEmpty<C extends Completion<unknown>, 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<unknown>); // NOTE: unsound cast
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-returnifabrupt */
|
||||
export type Q<T> =
|
||||
T extends NormalCompletion<infer V> ? V :
|
||||
T extends AbruptCompletion ? never :
|
||||
T;
|
||||
|
||||
/**
|
||||
* https://tc39.es/ecma262/#sec-returnifabrupt
|
||||
* https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ? OperationName()
|
||||
*/
|
||||
export function Q<const T>(_completion: T): Q<T> {
|
||||
/* node:coverage ignore next */
|
||||
throw new TypeError('Q requires build');
|
||||
}
|
||||
|
||||
function Q_runtime<const T>(completion: T): Q<T> {
|
||||
/* 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<T>;
|
||||
}
|
||||
throw c;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ! OperationName() */
|
||||
export function X<const T>(_completion: T | Evaluator<T>): Q<T> {
|
||||
/* node:coverage ignore next */
|
||||
throw new TypeError('X() requires build');
|
||||
}
|
||||
|
||||
export function unwrapCompletion<const T>(completion: T | Evaluator<T>): Q<T> {
|
||||
/* 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<T>;
|
||||
}
|
||||
/* node:coverage ignore next */
|
||||
throw new Assert.Error('Unexpected AbruptCompletion.', { cause: c });
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-ifabruptcloseiterator */
|
||||
export function IfAbruptCloseIterator<T>(_value: T, _iteratorRecord: IteratorRecord): Q<T> {
|
||||
/* node:coverage ignore next */
|
||||
throw new TypeError('IfAbruptCloseIterator() requires build');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-ifabruptcloseasynciterator */
|
||||
export function IfAbruptCloseAsyncIterator<T>(_value: T, _iteratorRecord: IteratorRecord): Q<T> {
|
||||
/* node:coverage ignore next */
|
||||
throw new TypeError('IfAbruptCloseAsyncIterator() requires build');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-ifabruptrejectpromise */
|
||||
export function IfAbruptRejectPromise<T>(_value: T, _capability: PromiseCapabilityRecord): Q<T> {
|
||||
/* 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<T>(callback: (q: typeof Q, x: typeof X) => Promise<T>): Promise<NormalCompletion<T> | ThrowCompletion>
|
||||
export function evalQ<T>(callback: (q: typeof Q, x: typeof X) => T): NormalCompletion<T> | ThrowCompletion
|
||||
export function evalQ<T>(callback: (q: typeof Q, x: typeof X) => T | Promise<T>): Promise<NormalCompletion<T> | ThrowCompletion> | NormalCompletion<T> | 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<T> = EnsureCompletionWorker<T, T>;
|
||||
|
||||
// Distribute over `T`s that are `Completion`s, but don't distribute over `T`s that aren't `Completion`s
|
||||
type EnsureCompletionWorker<T, _T> = T extends Completion<unknown> ? T : NormalCompletion<Exclude<_T, PlainCompletion<unknown>>>;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-implicit-normal-completion */
|
||||
export function EnsureCompletion(val: Value): NormalCompletion<Value>;
|
||||
export function EnsureCompletion<const T>(val: T): EnsureCompletion<T>;
|
||||
export function EnsureCompletion<const T>(val: T) {
|
||||
if (val instanceof Completion) {
|
||||
return val;
|
||||
}
|
||||
return NormalCompletion(val);
|
||||
}
|
||||
|
||||
export function ValueOfNormalCompletion<T>(value: NormalCompletion<T> | 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.
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/** https://tc39.es/ecma402/#sec-canonicalizeuvalue */
|
||||
export function CanonicalizeUValue(_ukey: string, uvalue: string): string {
|
||||
return uvalue;
|
||||
}
|
||||
@@ -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<Result> = Generator<EvaluatorYieldType, Result, EvaluatorNextType>;
|
||||
export type PlainEvaluator<V = void> = Evaluator<PlainCompletion<V>>;
|
||||
export type ValueEvaluator<V extends Value = Value> = Evaluator<ValueCompletion<V>>;
|
||||
export type ExpressionEvaluator = Evaluator<PlainCompletion<ReferenceRecord | Value>>;
|
||||
export type StatementEvaluator = Evaluator<PlainCompletion<void | Value> | AbruptCompletion>;
|
||||
export type ReferenceEvaluator = Evaluator<PlainCompletion<ReferenceRecord>>;
|
||||
export type YieldEvaluator = Evaluator<YieldCompletion | Value>;
|
||||
export type AsyncBuiltinSteps = () => Evaluator<Value | NormalCompletion<Value> | 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<unknown> {
|
||||
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<ParseNode> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<ObjectValue | SymbolValue>;
|
||||
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<const T extends keyof Intrinsics>(name: T): Intrinsics[T] {
|
||||
return this.currentRealmRecord.Intrinsics[name];
|
||||
}
|
||||
|
||||
// Generate a throw completion using message templates
|
||||
/** @deprecated Use Throw */
|
||||
Throw<K extends keyof typeof messages>(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<Value> | ThrowCompletion) => void;
|
||||
|
||||
// NON-SPEC
|
||||
/** This function will synchronously return a completion if this is a nested evaluation and debugger cannot be triggered. */
|
||||
evaluate<T extends Value>(evaluator: ValueEvaluator<T>, onFinished: (completion: NormalCompletion<T> | 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<Value> | ThrowCompletion) => void;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
resumeEvaluate(options?: ResumeEvaluateOptions): IteratorResult<void, ValueCompletion> {
|
||||
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<string, ScriptRecord | SourceTextModuleRecord | DynamicParsedCodeRecord>();
|
||||
|
||||
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<string, string>();
|
||||
|
||||
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<string, Breakpoint>();
|
||||
|
||||
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<ObjectValue>();
|
||||
|
||||
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<T>(cb: () => T): T;
|
||||
|
||||
debugger_scopePreview<T>(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;
|
||||
}
|
||||
@@ -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<BooleanValue>;
|
||||
|
||||
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<BooleanValue>;
|
||||
|
||||
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<DeclarativeEnvironmentBinding>();
|
||||
|
||||
/** 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<ModuleEnvironmentBinding>;
|
||||
|
||||
/** 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<BooleanValue> {
|
||||
// 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<BooleanValue> {
|
||||
// 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<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 = 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<BooleanValue> {
|
||||
// 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<BooleanValue> {
|
||||
// 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<BooleanValue> {
|
||||
// 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<ReferenceRecord> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -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__<EnvironmentRecord>(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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user