feat: add dependency

This commit is contained in:
2023-01-20 22:36:19 +08:00
parent 68e8d103b4
commit cf8e579f27
644 changed files with 150099 additions and 14 deletions

View File

@@ -9,9 +9,10 @@ edition = "2021"
crate-type = ['cdylib'] crate-type = ['cdylib']
[dependencies] [dependencies]
wit-bindgen-core = { path = '../wit-bindgen/crates/bindgen-core' } boa_engine = { version = "0.16.0", path = "external/boa/boa_engine"}
wit-bindgen-guest-rust = { path = '../wit-bindgen/crates/guest-rust' }
boa_engine = "0.16.0"
getrandom = { version = "0.2.8", features = ["js"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
[patch.crates-io]
iana-time-zone = { path = "external/iana-time-zone" }
getrandom = { path = "external/getrandom" }

View File

@@ -0,0 +1,3 @@
[profile.ci]
# Don't fail fast in CI to run the full test suite.
fail-fast = false

View File

@@ -0,0 +1,11 @@
root = true
[{Makefile,**.mk}]
# Use tabs for indentation (Makefiles require tabs)
indent_style = tab
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -0,0 +1,30 @@
# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto
#
# The above will handle all files NOT found below
#
# These files are text and should be normalized (Convert crlf => lf)
*.css eol=lf
*.htm eol=lf
*.html eol=lf
*.js eol=lf
*.json eol=lf
*.sh eol=lf
*.txt eol=lf
*.yml eol=lf
*.rs eol=lf
*.toml eol=lf
*.lock eol=lf
*.md eol=lf
*.svg eol=lf
# These files are binary and should be left untouched
# (binary is a macro for -text -diff)
*.gif binary
*.ico binary
*.jar binary
*.jpg binary
*.jpeg binary
*.png binary

View File

@@ -0,0 +1 @@
open_collective: boa

View File

@@ -0,0 +1,52 @@
---
name: "\U0001F41B Bug report"
about: Create a report to help us improve
title: ""
labels: bug
assignees: ""
---
<!--
Thank you for reporting a bug in Boa! This will make us improve the engine. But first, fill the following template so that we better understand what's happening. Feel free to add or remove sections as you feel appropriate.
-->
**Describe the bug**
A clear and concise description of what the bug is.
<!-- E.g.:
The variable statement is not working as expected, it always adds 10 when assigning a number to a variable"
-->
**To Reproduce**
Steps to reproduce the issue, or JavaScript code that causes this failure.
<!-- E.g.:
This JavaScript code reproduces the issue:
```javascript
var a = 10;
a;
```
-->
**Expected behavior**
Explain what you expected to happen, and what is happening instead.
<!-- E.g.:
Running this code, `a` should be set to `10` and printed, but `a` is instead set to `20`. The expected behaviour can be found in the [ECMAScript specification][spec].
[spec]: https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation
-->
**Build environment (please complete the following information):**
- OS: [e.g. Fedora Linux]
- Version: [e.g. 32]
- Target triple: [e.g. x86_64-unknown-linux-gnu]
- Rustc version: [e.g. rustc 1.43.0 (4fb7144ed 2020-04-20), running `rustc -V`]
**Additional context**
Add any other context about the problem here.
<!-- E.g.:
You can find more information in [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var).
-->

View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord channel
url: https://discord.gg/tUFFk9Y
about: Please ask and answer questions here.

View File

@@ -0,0 +1,13 @@
---
name: Custom
about: Open an issue in the repo that is neither a bug or a feature, such a new idea
title: ""
labels: ""
assignees: ""
---
<!--
Thank you for contributing to Boa! Please, let us know how can we help you.
-->
E.g.: I think we should improve the way the JavaScript interpreter works by...

View File

@@ -0,0 +1,43 @@
---
name: "\U0001F680 Feature request"
about: Suggest a new ECMAScript feature to be implemented, or a new capability of the engine.
title: ""
labels: enhancement
assignees: ""
---
<!--
Thank you for adding a feature request to Boa! As this is an experimental JavaScript engine, there will probably be many ECMAScript features left to implement. In order to understand the feature request as best as possible, please fill the following template. Feel free to add or remove sections as needed.
-->
**ECMASCript feature**
Explain the ECMAScript feature that you'd like to see implemented.
<!-- E.g.:
I would like to see `switch` statement parsing and execution implemented. [ECMAScript specification][spec].
[spec]: https://tc39.es/ecma262/#sec-switch-statement
-->
**Example code**
Give a code example that should work after the implementation of this feature.
<!-- E.g.:
This code should now work and give the expected result:
```javascript
let a = "hello";
let b;
switch (a) {
case 'hello':
b = 'world';
break;
case 'world':
b = 'hello';
break;
default:
b = 'hello world';
}
b;
```
The expected output is `world`.
-->

View File

@@ -0,0 +1,12 @@
<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->
This Pull Request fixes/closes #{issue_num}.
It changes the following:
-
-
-

View File

@@ -0,0 +1,10 @@
github_checks:
annotations: false
coverage:
status:
project:
default:
threshold: 5% # allow 5% coverage variance
patch: off

View File

@@ -0,0 +1,50 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
- package-ecosystem: cargo
directory: /
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_cli/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_engine/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_gc/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_interner/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_profiler/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_tester/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_unicode/
schedule:
interval: daily
- package-ecosystem: cargo
directory: /boa_wasm/
schedule:
interval: daily
- package-ecosystem: gitsubmodule
directory: /
schedule:
interval: weekly

View File

@@ -0,0 +1,16 @@
# .github/release.yml
changelog:
exclude:
authors:
- dependabot
categories:
- title: Feature Enhancements
labels:
- enhancement
- title: Bug Fixes
labels:
- bug
- title: Internal Improvements
labels:
- Internal

View File

@@ -0,0 +1,54 @@
name: Main workflows
on:
push:
branches:
- main
jobs:
benchmark:
if: ${{ github.actor != 'dependabot[bot]' }}
name: Upload docs and run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
target
!target/doc_upload
~/.cargo/git
~/.cargo/registry
key: ${{ runner.os }}-cargo-doc-bench-${{ hashFiles('**/Cargo.lock') }}
- name: Generate documentation
uses: actions-rs/cargo@v1
with:
command: doc
args: -v --document-private-items --all-features --workspace --no-deps --exclude boa_examples
- run: echo "<meta http-equiv=refresh content=0;url=boa_engine/index.html>" > target/doc/index.html
- run: |
if [ -d target/doc_upload ]; then rm -rf target/doc_upload; fi
mkdir target/doc_upload && mv target/doc target/doc_upload/doc
- name: Upload documentation
uses: crazy-max/ghaction-github-pages@v3.1.0
with:
target_branch: gh-pages
keep_history: true
build_dir: target/doc_upload
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run benchmark
run: cargo bench -p boa_engine -- --output-format bencher | tee output.txt
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1.15.0
with:
name: Boa Benchmarks
tool: "cargo"
output-file-path: output.txt
auto-push: true
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,34 @@
name: Benchmarks
on:
pull_request:
branches:
- main
jobs:
runBenchmark:
if: contains(github.event.pull_request.labels.*.name, 'run-benchmark')
name: run benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
target
~/.cargo/git
~/.cargo/registry
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: boa-dev/criterion-compare-action@v3.2.4
with:
token: ${{ secrets.GITHUB_TOKEN }}
branchName: ${{ github.base_ref }}
cwd: ./boa_engine

View File

@@ -0,0 +1,62 @@
name: Publish Release
on:
release:
types: [published]
jobs:
publish:
name: publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Install cargo-workspaces
uses: actions-rs/install@v0.1
with:
crate: cargo-workspaces
- name: Release
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
PATCH: ${{ github.run_number }}
shell: bash
run: |
git config --global user.email "runner@gha.local"
git config --global user.name "Github Action"
cargo workspaces publish --from-git --yes minor
doc-publish:
# needs: publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- uses: actions/setup-node@v3
with:
node-version: "16"
- run: npm ci
- name: Cache npm build
uses: actions/cache@v3
with:
path: |
node_modules
target
boa_wasm/pkg
~/.cargo/git
~/.cargo/registry
key: ${{ runner.os }}-npm-build-target-${{ hashFiles('**/package-lock.json') }}
- run: npm run build:prod
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
publish_dir: ./dist
destination_dir: playground
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,88 @@
name: Continuous integration
on:
pull_request:
branches:
- main
push:
branches:
- main
- staging # bors
- trying # bors
jobs:
coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- uses: Swatinem/rust-cache@v2
with:
key: tarpaulin
- name: Run cargo-tarpaulin
uses: actions-rs/tarpaulin@v0.1
with:
args: --features intl --ignore-tests --engine llvm
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
tests:
name: Build and Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- uses: Swatinem/rust-cache@v2
- name: Build tests
run: cargo test --no-run --profile ci
# this order is faster according to rust-analyzer
- name: Build
run: cargo build --all-targets --quiet --profile ci
- name: Install latest nextest
uses: taiki-e/install-action@nextest
- name: Test with nextest
run: cargo nextest run --profile ci --cargo-profile ci --features intl
misc:
name: Misc
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
with:
key: misc
- name: Format (rustfmt)
run: cargo fmt --all --check
- name: Lint (All features)
run: cargo clippy --all-features --all-targets
- name: Lint (No features)
run: cargo clippy --no-default-features --all-targets
- name: Generate documentation
run: cargo doc -v --document-private-items --all-features
- name: Build
run: cargo build --all-targets --quiet --profile ci
- run: cd boa_examples
- name: Build examples
run: cargo build --quiet --profile ci
- name: Run example classes
run: cargo run --bin classes --profile ci

View File

@@ -0,0 +1,12 @@
name: Security audit
on:
schedule:
- cron: "0 0 * * *"
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/audit-check@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,118 @@
name: EcmaScript official test suite (test262)
on:
push:
branches:
- main
tags:
- v*
pull_request:
branches:
- main
jobs:
run_test262:
name: Run the test262 test suite
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
with:
submodules: true
path: boa
- name: Install the Rust toolchain
uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
target
~/.cargo/git
~/.cargo/registry
key: ${{ runner.os }}-cargo-test262-${{ hashFiles('**/Cargo.lock') }}
# Run the test suite and upload the results
- name: Checkout GitHub pages
uses: actions/checkout@v3
with:
ref: gh-pages
path: gh-pages
- name: Run the test262 test suite
run: |
cd boa
mkdir ../results
cargo run --release --bin boa_tester -- run -v -o ../results/test262
cd ..
# Run the results comparison
- name: Compare results
if: github.event_name == 'pull_request'
id: compare-non-vm
shell: bash
run: |
cd boa
comment="$(./target/release/boa_tester compare ../gh-pages/test262/refs/heads/main/latest.json ../results/test262/pull/latest.json -m)"
echo "comment<<EOF" >> $GITHUB_OUTPUT
echo "$comment" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Get the PR number
if: github.event_name == 'pull_request'
id: pr-number
uses: kkak10/pr-number-action@v1.3
- name: Find Previous Comment
if: github.event_name == 'pull_request'
uses: peter-evans/find-comment@v2
id: previous-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr }}
body-includes: Test262 conformance changes
- name: Update comment
if: github.event_name == 'pull_request' && steps.previous-comment.outputs.comment-id
uses: peter-evans/create-or-update-comment@v2
continue-on-error: true
with:
comment-id: ${{ steps.previous-comment.outputs.comment-id }}
body: |
### Test262 conformance changes
${{ steps.compare-non-vm.outputs.comment }}
${{ steps.compare-vm.outputs.comment }}
edit-mode: replace
- name: Write a new comment
if: github.event_name == 'pull_request' && !steps.previous-comment.outputs.comment-id
uses: peter-evans/create-or-update-comment@v2
continue-on-error: true
with:
issue-number: ${{ steps.pr-number.outputs.pr }}
body: |
### Test262 conformance changes
${{ steps.compare-non-vm.outputs.comment }}
${{ steps.compare-vm.outputs.comment }}
# Commit changes to GitHub pages.
- name: Commit files
if: github.event_name == 'push'
run: |
cp -r ./results/test262/* ./gh-pages/test262/
cd gh-pages
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add test262
git commit -m "Add new test262 results" -a
cd ..
- name: Upload results
if: github.event_name == 'push'
uses: ad-m/github-push-action@v0.6.0
with:
directory: gh-pages
branch: gh-pages
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,49 @@
on:
pull_request:
branches:
- main
push:
branches:
- main
name: Webassembly demo
jobs:
check_style:
name: Check webassembly demo style
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check code formatting
run: npx prettier --check .
build:
name: Build webassembly demo
runs-on: ubuntu-latest
env:
WASM_PACK_PATH: ~/.cargo/bin/wasm-pack
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Cache npm build
uses: actions/cache@v3
with:
path: |
node_modules
target
~/.cargo/git
~/.cargo/registry
boa_wasm/pkg
key: ${{ runner.os }}-npm-build-target-${{ hashFiles('**/package-lock.json') }}
- uses: actions/setup-node@v3
with:
node-version: "16"
- run: npm ci
- run: npm run build

View File

@@ -0,0 +1,34 @@
# IDE
.idea/
*.iml
# Vim
*.*.swp
*.*.swo
# Build
target
dist
**/*.rs.bk
node_modules
.DS_Store
yarn-error.log
.vscode/settings.json
# tests/js/test.js is used for testing changes locally
tests/js/test.js
.boa_history
# Profiling
*.string_data
*.string_index
*.events
chrome_profiler.json
*.mm_profdata
# Logs
*.log
# Yarn
.yarn
.yarnrc.yml

View File

@@ -0,0 +1,3 @@
[submodule "test262"]
path = test262
url = https://github.com/tc39/test262.git

View File

@@ -0,0 +1,10 @@
# Ignore artifacts:
*.rs
target
node_modules
boa_engine/benches/bench_scripts/mini_js.js
boa_engine/benches/bench_scripts/clean_js.js
boa_wasm/pkg
dist
test262
tests/js/test.js

View File

@@ -0,0 +1,35 @@
{
// 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": "lldb",
"request": "launch",
"name": "Launch",
"windows": {
"program": "${workspaceFolder}/target/debug/boa.exe"
},
"program": "${workspaceFolder}/target/debug/boa",
"args": ["${workspaceFolder}/tests/js/test.js"],
"sourceLanguages": ["rust"]
},
{
"type": "lldb",
"request": "launch",
"name": "Launch (VM)",
"cargo": {
"args": [
"run",
"--manifest-path",
"./boa_cli/Cargo.toml",
"--features",
"vm"
]
},
"args": ["-t", "${workspaceFolder}/tests/js/test.js"],
"sourceLanguages": ["rust"]
}
]
}

View File

@@ -0,0 +1,90 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "process",
"label": "Cargo Run",
"command": "cargo",
"args": ["run", "--bin", "boa", "./tests/js/test.js"],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"clear": true
},
"options": {
"env": {
"RUST_BACKTRACE": "1"
}
},
"problemMatcher": []
},
{
"type": "process",
"label": "Cargo Run (Profiler)",
"command": "cargo",
"args": [
"run",
"--features",
"boa_engine/profiler",
"../tests/js/test.js"
],
"group": "build",
"options": {
"env": {
"RUST_BACKTRACE": "full"
},
"cwd": "${workspaceFolder}/boa_cli"
},
"presentation": {
"clear": true
},
"problemMatcher": []
},
{
"type": "process",
"label": "Run with VM trace",
"command": "cargo",
"args": ["run", "--bin", "boa", "--", "-t", "./tests/js/test.js"],
"group": "build",
"presentation": {
"clear": true
},
"problemMatcher": []
},
{
"type": "process",
"label": "Get AST",
"command": "cargo",
"args": ["run", "--bin", "boa", "--", "-a=Debug", "./tests/js/test.js"],
"group": "build",
"presentation": {
"clear": true
},
"problemMatcher": []
},
{
"type": "process",
"label": "Cargo Test",
"command": "cargo",
"args": ["test"],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"clear": true
}
},
{
"type": "process",
"label": "Cargo Test Build",
"command": "cargo",
"args": ["test", "--no-run"],
"group": "build"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders 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, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[discord](https://discord.gg/tUFFk9Y) by contacting anyone in the _@boa_dev_
group (check the yellow usernames).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[mozilla coc]: https://github.com/mozilla/diversity
[faq]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -0,0 +1,115 @@
# Contributing to Boa
Boa welcomes contribution from everyone. Here are the guidelines if you are
thinking of helping out:
## Contributions
Contributions to Boa or its dependencies should be made in the form of GitHub
pull requests. Each pull request will be reviewed by a core contributor
(someone with permission to land patches) and either landed in the main tree or
given feedback for changes that would be required. All contributions should
follow this format.
Should you wish to work on an issue, please claim it first by commenting on
the GitHub issue that you want to work on it. This is to prevent duplicated
efforts from contributors on the same issue.
Head over to [issues][issues] and check for "good first issue" labels to find
good tasks to start with. If you come across words or jargon that do not make
sense, please ask!
If you don't already have Rust installed [_rustup_][rustup] is the recommended
tool to use. It will install Rust and allow you to switch between _nightly_,
_stable_ and _beta_. You can also install additional components. In Linux, you
can run:
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
Then simply clone this project and `cargo build`.
### Running the compiler
You can execute a Boa console by running `cargo run`, and you can compile a list
of JavaScript files by running `cargo run -- file1.js file2.js` and so on.
### Debugging
Knowing how to debug the interpreter should help you resolve problems quite quickly.
See [Debugging](./docs/debugging.md).
### Web Assembly
If you want to develop on the web assembly side you can run `yarn serve` and then go
to <http://localhost:8080>.
### boa-unicode
Boa uses the library `boa-unicode` to query Unicode character properties and classes in lexer and parser. See [boa_unicode/README.md](./boa_unicode/README.md) for development and more information.
### Setup
#### VSCode Plugins
Either the [Rust (RLS)][rls_vscode] or the [Rust Analyzer][rust-analyzer_vscode]
extensions are preferred. RLS is easier to set up but some of the development is
moving towards Rust Analyzer. Both of these plugins will help you with your Rust
Development
#### Tasks
There are some pre-defined tasks in [tasks.json](.vscode/tasks.json)
- Build - shift+cmd/ctrl+b should build and run cargo. You should be able to make changes and run this task.
- Test - (there is no shortcut, you'll need to make one) - Runs `Cargo Test`.
I personally set a shortcut of shift+cmd+option+T (or shift+ctrl+alt+T)
If you don't want to install everything on your machine, you can use the Dockerfile.
Start VSCode in container mode (you may need the docker container plugin) and use the Dockerfile.
## Testing
Boa provides its own test suite, and can also run the official ECMAScript test suite. To run the Boa test
suite, you can just run the normal `cargo test`, and to run the full ECMAScript test suite, you can run it
with this command:
```shell
cargo run --release --bin boa_tester -- run -v 2> error.log
```
Note that this requires the `test262` submodule to be checked out, so you will need to run the following first:
```shell
git submodule init && git submodule update
```
This will run the test suite in verbose mode (you can remove the `-v` part to run it in non-verbose mode),
and output nice colorings in the terminal. It will also output any panic information into the `error.log` file.
You can get some more verbose information that tells you the exact name of each test that is being run, useful
for debugging purposes by setting up the verbose flag twice, for example `-vv`. If you want to know the output of
each test that is executed, you can use the triple verbose (`-vvv`) flag.
If you want to only run one sub-suite or even one test (to just check if you fixed/broke something specific),
you can do it with the `-s` parameter, and then passing the path to the sub-suite or test that you want to run. Note
that the `-s` parameter value should be a path relative to the `test262` directory. For example, to run the number
type tests, use `-s test/language/types/number`.
Finally, if you're using the verbose flag and running a sub suite with a small number of tests, then the output will
be more readable if you disable parallelism with the `-d` flag. All together it might look something like:
```shell
cargo run --release --bin boa_tester -- run -vv -d -s test/language/types/number 2> error.log
```
## Communication
We have a Discord server, feel free to ask questions here:
<https://discord.gg/tUFFk9Y>
[issues]: https://github.com/boa-dev/boa/issues
[rustup]: https://rustup.rs/
[rls_vscode]: https://marketplace.visualstudio.com/items?itemName=rust-lang.rust
[rust-analyzer_vscode]: https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer

View File

@@ -0,0 +1,64 @@
[workspace]
members = [
"boa_cli",
"boa_engine",
"boa_ast",
"boa_parser",
"boa_gc",
"boa_interner",
"boa_profiler",
"boa_tester",
"boa_unicode",
"boa_wasm",
"boa_examples",
"boa_macros",
"boa_icu_provider",
]
[workspace.package]
edition = "2021"
version = "0.16.0"
rust-version = "1.66"
authors = ["boa-dev"]
repository = "https://github.com/boa-dev/boa"
license = "Unlicense/MIT"
description = "Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language."
[workspace.dependencies]
boa_engine = { version = "0.16.0", path = "boa_engine" }
boa_interner = { version = "0.16.0", path = "boa_interner" }
boa_gc = { version = "0.16.0", path = "boa_gc" }
boa_profiler = { version = "0.16.0", path = "boa_profiler" }
boa_unicode = { version = "0.16.0", path = "boa_unicode" }
boa_macros = { version = "0.16.0", path = "boa_macros" }
boa_ast = { version = "0.16.0", path = "boa_ast" }
boa_parser = { version = "0.16.0", path = "boa_parser" }
boa_icu_provider = { version = "0.16.0", path = "boa_icu_provider" }
[workspace.metadata.workspaces]
allow_branch = "main"
# The ci profile, designed to reduce size of target directory
[profile.ci]
inherits = "dev"
debug = false
incremental = false
# The release profile, used for `cargo build --release`.
[profile.release]
# Enables "fat" LTO, for faster release builds
lto = "fat"
# Makes sure that all code is compiled together, for LTO
codegen-units = 1
# The test profile, used for `cargo test`.
[profile.test]
# Enables thin local LTO and some optimizations.
opt-level = 1
# The benchmark profile, used for `cargo bench`.
[profile.bench]
# Enables "fat" LTO, for faster benchmark builds
lto = "fat"
# Makes sure that all code is compiled together, for LTO
codegen-units = 1

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jason Williams
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.

View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org>

115
javascript-engine/external/boa/README.md vendored Normal file
View File

@@ -0,0 +1,115 @@
# Boa
<p align="center">
<img
alt="Boa Logo"
src="./assets/logo.svg"
width="30%"
/>
</p>
This is an experimental Javascript lexer, parser and interpreter written in Rust.
Currently, it has support for some of the language.
[![Build Status][build_badge]][build_link]
[![codecov](https://codecov.io/gh/boa-dev/boa/branch/main/graph/badge.svg)](https://codecov.io/gh/boa-dev/boa)
[![Crates.io](https://img.shields.io/crates/v/boa_engine.svg)](https://crates.io/crates/boa_engine)
[![Docs.rs](https://docs.rs/boa_engine/badge.svg)](https://docs.rs/boa_engine)
[![Discord](https://img.shields.io/discord/595323158140158003?logo=discord)](https://discord.gg/tUFFk9Y)
[build_badge]: https://github.com/boa-dev/boa/actions/workflows/rust.yml/badge.svg?event=push&branch=main
[build_link]: https://github.com/boa-dev/boa/actions/workflows/rust.yml?query=event%3Apush+branch%3Amain
## Live Demo (WASM)
<https://boa-dev.github.io/boa/playground/>
You can get more verbose errors when running from the command line.
## Development documentation
You can check the internal development docs at <https://boa-dev.github.io/boa/doc>.
## Conformance
To know how much of the _ECMAScript_ specification does Boa cover, you can check out results running the _ECMASCript Test262_ test suite [here](https://boa-dev.github.io/boa/test262/).
## Contributing
Please, check the [CONTRIBUTING.md](CONTRIBUTING.md) file to know how to
contribute in the project. You will need Rust installed and an editor. We have
some configurations ready for VSCode.
### Debugging
Check [debugging.md](./docs/debugging.md) for more info on debugging.
### Web Assembly
This interpreter can be exposed to JavaScript!
You can build the example locally with:
```shell
npm run build
```
In the console you can use `window.evaluate` to pass JavaScript in.
To develop on the web assembly side you can run:
```shell
npm run serve
```
then go to `http://localhost:8080`.
## Usage
- Clone this repo.
- Run with `cargo run -- test.js` where `test.js` is an existing JS file with any JS valid code.
- If any JS doesn't work then it's a bug. Please raise an [issue](https://github.com/boa-dev/boa/issues/)!
### Example
![Example](docs/img/latestDemo.gif)
## Command-line Options
```shell
USAGE:
boa [OPTIONS] [FILE]...
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-a, --dump-ast <FORMAT> Dump the abstract syntax tree (ast) to stdout with the given format [possible values: Debug, Json,
JsonPretty]
ARGS:
<FILE>... The JavaScript file(s) to be evaluated
```
## Roadmap
See [Milestones](https://github.com/boa-dev/boa/milestones).
## Benchmarks
See [Benchmarks](https://boa-dev.github.io/boa/dev/bench/).
## Profiling
See [Profiling](./docs/profiling.md).
## Changelog
See [CHANGELOG.md](./CHANGELOG.md).
## Communication
Feel free to contact us on [Discord](https://discord.gg/tUFFk9Y).
## License
This project is licensed under the [Unlicense](./LICENSE-UNLICENSE) or [MIT](./LICENSE-MIT) licenses, at your option.

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -0,0 +1,25 @@
[package]
name = "boa_ast"
description = "Abstract Syntax Tree definition for the Boa JavaScript engine."
keywords = ["javascript", "js", "syntax", "ast"]
categories = ["parser-implementations", "compilers"]
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[features]
serde = ["boa_interner/serde", "dep:serde"]
fuzz = ["arbitrary", "boa_interner/fuzz", "num-bigint/arbitrary"]
[dependencies]
boa_interner.workspace = true
boa_macros.workspace = true
rustc-hash = "1.1.0"
serde = { version = "1.0.152", features = ["derive"], optional = true }
bitflags = "1.3.2"
num-bigint = "0.4.3"
arbitrary = { version = "1", optional = true, features = ["derive"] }

View File

@@ -0,0 +1,97 @@
//! The [`Declaration`] Parse Node, as defined by the [spec].
//!
//! ECMAScript declarations include:
//! - [Lexical][lex] declarations (`let`, `const`).
//! - [Function][fun] declarations (`function`, `async function`).
//! - [Class][class] declarations.
//!
//! See [*Difference between statements and declarations*][diff] for an explanation on why `Declaration`s
//! and `Statement`s are distinct nodes.
//!
//! [spec]: https://tc39.es/ecma262/#prod-Declaration
//! [lex]: https://tc39.es/ecma262/#prod-LexicalDeclaration
//! [fun]: https://tc39.es/ecma262/#prod-HoistableDeclaration
//! [class]: https://tc39.es/ecma262/#prod-ClassDeclaration
//! [diff]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#difference_between_statements_and_declarations
use super::function::{AsyncFunction, AsyncGenerator, Class, Function, Generator};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
mod variable;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use variable::*;
/// The `Declaration` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Declaration {
/// See [`Function`]
Function(Function),
/// See [`Generator`]
Generator(Generator),
/// See [`AsyncFunction`]
AsyncFunction(AsyncFunction),
/// See [`AsyncGenerator`]
AsyncGenerator(AsyncGenerator),
/// See [`Class`]
Class(Class),
/// See [`LexicalDeclaration`]
Lexical(LexicalDeclaration),
}
impl ToIndentedString for Declaration {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
Self::Function(f) => f.to_indented_string(interner, indentation),
Self::Generator(g) => g.to_indented_string(interner, indentation),
Self::AsyncFunction(af) => af.to_indented_string(interner, indentation),
Self::AsyncGenerator(ag) => ag.to_indented_string(interner, indentation),
Self::Class(c) => c.to_indented_string(interner, indentation),
Self::Lexical(l) => {
let mut s = l.to_interned_string(interner);
s.push(';');
s
}
}
}
}
impl VisitWith for Declaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Function(f) => visitor.visit_function(f),
Self::Generator(g) => visitor.visit_generator(g),
Self::AsyncFunction(af) => visitor.visit_async_function(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::Class(c) => visitor.visit_class(c),
Self::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Function(f) => visitor.visit_function_mut(f),
Self::Generator(g) => visitor.visit_generator_mut(g),
Self::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::Class(c) => visitor.visit_class_mut(c),
Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}

View File

@@ -0,0 +1,385 @@
//! Variable related declarations.
use core::ops::ControlFlow;
use std::convert::TryFrom;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes,
pattern::Pattern,
Statement,
};
use boa_interner::{Interner, ToInternedString};
use super::Declaration;
/// A [`var`][var] statement, also called [`VariableStatement`][varstmt] in the spec.
///
/// The scope of a variable declared with `var` is its current execution context, which is either
/// the enclosing function or, for variables declared outside any function, global. If you
/// re-declare a ECMAScript variable, it will not lose its value.
///
/// Although a bit confusing, `VarDeclaration`s are not considered [`Declaration`]s by the spec.
/// This is partly because it has very different semantics from `let` and `const` declarations, but
/// also because a `var` statement can be labelled just like any other [`Statement`]:
///
/// ```javascript
/// label: var a = 5;
/// a;
/// ```
///
/// returns `5` as the value of the statement list, while:
///
/// ```javascript
/// label: let a = 5;
/// a;
/// ```
/// throws a `SyntaxError`.
///
/// `var` declarations, wherever they occur, are processed before any code is executed. This is
/// called <code>[hoisting]</code>.
///
/// [var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
/// [varstmt]: https://tc39.es/ecma262/#prod-VariableStatement
/// [hoisting]: https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct VarDeclaration(pub VariableList);
impl From<VarDeclaration> for Statement {
fn from(var: VarDeclaration) -> Self {
Self::Var(var)
}
}
impl ToInternedString for VarDeclaration {
fn to_interned_string(&self, interner: &Interner) -> String {
format!("var {}", self.0.to_interned_string(interner))
}
}
impl VisitWith for VarDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable_list(&self.0)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_list_mut(&mut self.0)
}
}
/// A **[lexical declaration]** defines variables that are scoped to the lexical environment of
/// the variable declaration.
///
/// [lexical declaration]: https://tc39.es/ecma262/#sec-let-and-const-declarations
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum LexicalDeclaration {
/// A <code>[const]</code> variable creates a constant whose scope can be either global or local
/// to the block in which it is declared.
///
/// An initializer for a constant is required. You must specify its value in the same statement
/// in which it's declared. (This makes sense, given that it can't be changed later)
///
/// [const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
Const(VariableList),
/// A <code>[let]</code> variable is limited to a scope of a block statement, or expression on
/// which it is used, unlike the `var` keyword, which defines a variable globally, or locally to
/// an entire function regardless of block scope.
///
/// Just like const, `let` does not create properties of the window object when declared
/// globally (in the top-most scope).
///
/// If a let declaration does not have an initializer, the variable is assigned the value `undefined`.
///
/// [let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Let(VariableList),
}
impl LexicalDeclaration {
/// Gets the inner variable list of the `LexicalDeclaration`
#[must_use]
pub const fn variable_list(&self) -> &VariableList {
match self {
Self::Const(list) | Self::Let(list) => list,
}
}
}
impl From<LexicalDeclaration> for Declaration {
fn from(lex: LexicalDeclaration) -> Self {
Self::Lexical(lex)
}
}
impl ToInternedString for LexicalDeclaration {
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {}",
match &self {
Self::Let(_) => "let",
Self::Const(_) => "const",
},
self.variable_list().to_interned_string(interner)
)
}
}
impl VisitWith for LexicalDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Const(vars) | Self::Let(vars) => visitor.visit_variable_list(vars),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Const(vars) | Self::Let(vars) => visitor.visit_variable_list_mut(vars),
}
}
}
/// List of variables in a variable declaration.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct VariableList {
list: Box<[Variable]>,
}
impl VariableList {
/// Creates a variable list if the provided list of [`Variable`] is not empty.
#[must_use]
pub fn new(list: Box<[Variable]>) -> Option<Self> {
if list.is_empty() {
return None;
}
Some(Self { list })
}
}
impl AsRef<[Variable]> for VariableList {
fn as_ref(&self) -> &[Variable] {
&self.list
}
}
impl ToInternedString for VariableList {
fn to_interned_string(&self, interner: &Interner) -> String {
join_nodes(interner, self.list.as_ref())
}
}
impl VisitWith for VariableList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for variable in self.list.iter() {
try_break!(visitor.visit_variable(variable));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for variable in self.list.iter_mut() {
try_break!(visitor.visit_variable_mut(variable));
}
ControlFlow::Continue(())
}
}
/// The error returned by the [`VariableList::try_from`] function.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromVariableListError(());
impl std::fmt::Display for TryFromVariableListError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"provided list of variables cannot be empty".fmt(f)
}
}
impl TryFrom<Box<[Variable]>> for VariableList {
type Error = TryFromVariableListError;
fn try_from(value: Box<[Variable]>) -> Result<Self, Self::Error> {
Self::new(value).ok_or(TryFromVariableListError(()))
}
}
impl TryFrom<Vec<Variable>> for VariableList {
type Error = TryFromVariableListError;
fn try_from(value: Vec<Variable>) -> Result<Self, Self::Error> {
Self::try_from(value.into_boxed_slice())
}
}
/// Variable represents a variable declaration of some kind.
///
/// For `let` and `const` declarations this type represents a [`LexicalBinding`][spec1]
///
/// For `var` declarations this type represents a [`VariableDeclaration`][spec2]
///
/// More information:
/// - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec3]
///
/// [spec1]: https://tc39.es/ecma262/#prod-LexicalBinding
/// [spec2]: https://tc39.es/ecma262/#prod-VariableDeclaration
/// [spec3]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Variable {
binding: Binding,
init: Option<Expression>,
}
impl ToInternedString for Variable {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = self.binding.to_interned_string(interner);
if let Some(ref init) = self.init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
}
impl Variable {
/// Creates a new variable declaration from a `BindingIdentifier`.
#[inline]
#[must_use]
pub const fn from_identifier(ident: Identifier, init: Option<Expression>) -> Self {
Self {
binding: Binding::Identifier(ident),
init,
}
}
/// Creates a new variable declaration from a `Pattern`.
#[inline]
#[must_use]
pub const fn from_pattern(pattern: Pattern, init: Option<Expression>) -> Self {
Self {
binding: Binding::Pattern(pattern),
init,
}
}
/// Gets the variable declaration binding.
#[must_use]
pub const fn binding(&self) -> &Binding {
&self.binding
}
/// Gets the initialization expression for the variable declaration, if any.
#[inline]
#[must_use]
pub const fn init(&self) -> Option<&Expression> {
self.init.as_ref()
}
}
impl VisitWith for Variable {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_binding(&self.binding));
if let Some(init) = &self.init {
try_break!(visitor.visit_expression(init));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_binding_mut(&mut self.binding));
if let Some(init) = &mut self.init {
try_break!(visitor.visit_expression_mut(init));
}
ControlFlow::Continue(())
}
}
/// Binding represents either an individual binding or a binding pattern.
///
/// More information:
/// - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Binding {
/// A single identifier binding.
Identifier(Identifier),
/// A pattern binding.
Pattern(Pattern),
}
impl From<Identifier> for Binding {
fn from(id: Identifier) -> Self {
Self::Identifier(id)
}
}
impl From<Pattern> for Binding {
fn from(pat: Pattern) -> Self {
Self::Pattern(pat)
}
}
impl ToInternedString for Binding {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Identifier(id) => id.to_interned_string(interner),
Self::Pattern(ref pattern) => pattern.to_interned_string(interner),
}
}
}
impl VisitWith for Binding {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Pattern(pattern) => visitor.visit_pattern(pattern),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Pattern(pattern) => visitor.visit_pattern_mut(pattern),
}
}
}

View File

@@ -0,0 +1,348 @@
//! Property access expressions, as defined by the [spec].
//!
//! [Property access expressions][access] provide two ways to access properties of an object: *dot notation*
//! and *bracket notation*.
//! - *Dot notation* is mostly used when the name of the property is static, and a valid Javascript
//! identifier e.g. `obj.prop`, `arr.$val`.
//! - *Bracket notation* is used when the name of the property is either variable, not a valid
//! identifier or a symbol e.g. `arr[var]`, `arr[5]`, `arr[Symbol.iterator]`.
//!
//! A property access expression can be represented by a [`SimplePropertyAccess`] (`x.y`), a
//! [`PrivatePropertyAccess`] (`x.#y`) or a [`SuperPropertyAccess`] (`super["y"]`), each of them with
//! slightly different semantics overall.
//!
//! [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors
//! [access]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors
use crate::expression::Expression;
use crate::function::PrivateName;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// A property access field.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyAccessField {
/// A constant property field, such as `x.prop`.
Const(Sym),
/// An expression property field, such as `x["val"]`.
Expr(Box<Expression>),
}
impl From<Sym> for PropertyAccessField {
#[inline]
fn from(id: Sym) -> Self {
Self::Const(id)
}
}
impl From<Expression> for PropertyAccessField {
#[inline]
fn from(expr: Expression) -> Self {
Self::Expr(Box::new(expr))
}
}
impl VisitWith for PropertyAccessField {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Const(sym) => visitor.visit_sym(sym),
Self::Expr(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Const(sym) => visitor.visit_sym_mut(sym),
Self::Expr(expr) => visitor.visit_expression_mut(&mut *expr),
}
}
}
/// A property access expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyAccess {
/// A simple property access (`x.prop`).
Simple(SimplePropertyAccess),
/// A property access of a private property (`x.#priv`).
Private(PrivatePropertyAccess),
/// A property access of a `super` reference. (`super["prop"]`).
Super(SuperPropertyAccess),
}
impl ToInternedString for PropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Simple(s) => s.to_interned_string(interner),
Self::Private(p) => p.to_interned_string(interner),
Self::Super(s) => s.to_interned_string(interner),
}
}
}
impl From<PropertyAccess> for Expression {
#[inline]
fn from(access: PropertyAccess) -> Self {
Self::PropertyAccess(access)
}
}
impl VisitWith for PropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Simple(spa) => visitor.visit_simple_property_access(spa),
Self::Private(ppa) => visitor.visit_private_property_access(ppa),
Self::Super(supa) => visitor.visit_super_property_access(supa),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Simple(spa) => visitor.visit_simple_property_access_mut(spa),
Self::Private(ppa) => visitor.visit_private_property_access_mut(ppa),
Self::Super(supa) => visitor.visit_super_property_access_mut(supa),
}
}
}
/// A simple property access, where the target object is an [`Expression`].
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct SimplePropertyAccess {
target: Box<Expression>,
field: PropertyAccessField,
}
impl SimplePropertyAccess {
/// Gets the target object of the property access.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Gets the accessed field of the target object.
#[inline]
#[must_use]
pub const fn field(&self) -> &PropertyAccessField {
&self.field
}
/// Creates a `PropertyAccess` AST Expression.
pub fn new<F>(target: Expression, field: F) -> Self
where
F: Into<PropertyAccessField>,
{
Self {
target: target.into(),
field: field.into(),
}
}
}
impl ToInternedString for SimplePropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let target = self.target.to_interned_string(interner);
match self.field {
PropertyAccessField::Const(sym) => format!("{target}.{}", interner.resolve_expect(sym)),
PropertyAccessField::Expr(ref expr) => {
format!("{target}[{}]", expr.to_interned_string(interner))
}
}
}
}
impl From<SimplePropertyAccess> for PropertyAccess {
#[inline]
fn from(access: SimplePropertyAccess) -> Self {
Self::Simple(access)
}
}
impl VisitWith for SimplePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_property_access_field(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_property_access_field_mut(&mut self.field)
}
}
/// An access expression to a class object's [private fields][mdn].
///
/// Private property accesses differ slightly from plain property accesses, since the accessed
/// property must be prefixed by `#`, and the bracket notation is not allowed. For example,
/// `this.#a` is a valid private property access.
///
/// This expression corresponds to the [`MemberExpression.PrivateIdentifier`][spec] production.
///
/// [spec]: https://tc39.es/ecma262/#prod-MemberExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct PrivatePropertyAccess {
target: Box<Expression>,
field: PrivateName,
}
impl PrivatePropertyAccess {
/// Creates a `GetPrivateField` AST Expression.
#[inline]
#[must_use]
pub fn new(value: Expression, field: PrivateName) -> Self {
Self {
target: value.into(),
field,
}
}
/// Gets the original object from where to get the field from.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Gets the name of the field to retrieve.
#[inline]
#[must_use]
pub const fn field(&self) -> PrivateName {
self.field
}
}
impl ToInternedString for PrivatePropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{}.#{}",
self.target.to_interned_string(interner),
interner.resolve_expect(self.field.description())
)
}
}
impl From<PrivatePropertyAccess> for PropertyAccess {
#[inline]
fn from(access: PrivatePropertyAccess) -> Self {
Self::Private(access)
}
}
impl VisitWith for PrivatePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_private_name(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_private_name_mut(&mut self.field)
}
}
/// A property access of an object's parent, as defined by the [spec].
///
/// A `SuperPropertyAccess` is much like a regular [`PropertyAccess`], but where its `target` object
/// is not a regular object, but a reference to the parent object of the current object ([`super`][mdn]).
///
/// [spec]: https://tc39.es/ecma262/#prod-SuperProperty
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct SuperPropertyAccess {
field: PropertyAccessField,
}
impl SuperPropertyAccess {
/// Creates a new property access field node.
#[must_use]
pub const fn new(field: PropertyAccessField) -> Self {
Self { field }
}
/// Gets the name of the field to retrieve.
#[inline]
#[must_use]
pub const fn field(&self) -> &PropertyAccessField {
&self.field
}
}
impl ToInternedString for SuperPropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match &self.field {
PropertyAccessField::Const(field) => {
format!("super.{}", interner.resolve_expect(*field))
}
PropertyAccessField::Expr(field) => {
format!("super[{}]", field.to_interned_string(interner))
}
}
}
}
impl From<SuperPropertyAccess> for PropertyAccess {
#[inline]
fn from(access: SuperPropertyAccess) -> Self {
Self::Super(access)
}
}
impl VisitWith for SuperPropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_property_access_field(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_property_access_field_mut(&mut self.field)
}
}

View File

@@ -0,0 +1,71 @@
//! Await expression Expression.
use core::ops::ControlFlow;
use super::Expression;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
/// An await expression is used within an async function to pause execution and wait for a
/// promise to resolve.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AwaitExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Await {
target: Box<Expression>,
}
impl Await {
/// Return the target expression that should be awaited.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
}
impl<T> From<T> for Await
where
T: Into<Box<Expression>>,
{
fn from(e: T) -> Self {
Self { target: e.into() }
}
}
impl ToInternedString for Await {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("await {}", self.target.to_indented_string(interner, 0))
}
}
impl From<Await> for Expression {
#[inline]
fn from(awaitexpr: Await) -> Self {
Self::Await(awaitexpr)
}
}
impl VisitWith for Await {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,164 @@
use crate::join_nodes;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// Calling the function actually performs the specified actions with the indicated parameters.
///
/// Defining a function does not execute it. Defining it simply names the function and
/// specifies what to do when the function is called. Functions must be in scope when they are
/// called, but the function declaration can be hoisted. The scope of a function is the
/// function in which it is declared (or the entire program, if it is declared at the top
/// level).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CallExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Call {
function: Box<Expression>,
args: Box<[Expression]>,
}
impl Call {
/// Creates a new `Call` AST Expression.
#[inline]
#[must_use]
pub fn new(function: Expression, args: Box<[Expression]>) -> Self {
Self {
function: function.into(),
args,
}
}
/// Gets the target function of this call expression.
#[inline]
#[must_use]
pub const fn function(&self) -> &Expression {
&self.function
}
/// Retrieves the arguments passed to the function.
#[inline]
#[must_use]
pub const fn args(&self) -> &[Expression] {
&self.args
}
}
impl ToInternedString for Call {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{}({})",
self.function.to_interned_string(interner),
join_nodes(interner, &self.args)
)
}
}
impl From<Call> for Expression {
#[inline]
fn from(call: Call) -> Self {
Self::Call(call)
}
}
impl VisitWith for Call {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.function));
for expr in self.args.iter() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.function));
for expr in self.args.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}
/// The `super` keyword is used to access and call functions on an object's parent.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-SuperCall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct SuperCall {
args: Box<[Expression]>,
}
impl SuperCall {
/// Creates a new `SuperCall` AST node.
pub fn new<A>(args: A) -> Self
where
A: Into<Box<[Expression]>>,
{
Self { args: args.into() }
}
/// Retrieves the arguments of the super call.
#[must_use]
pub const fn arguments(&self) -> &[Expression] {
&self.args
}
}
impl ToInternedString for SuperCall {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("super({})", join_nodes(interner, &self.args))
}
}
impl From<SuperCall> for Expression {
#[inline]
fn from(call: SuperCall) -> Self {
Self::SuperCall(call)
}
}
impl VisitWith for SuperCall {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.args.iter() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for expr in self.args.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,122 @@
//! Local identifier Expression.
use crate::{
visitor::{VisitWith, Visitor, VisitorMut},
ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// List of reserved keywords exclusive to strict mode.
pub const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [
Sym::IMPLEMENTS,
Sym::INTERFACE,
Sym::LET,
Sym::PACKAGE,
Sym::PRIVATE,
Sym::PROTECTED,
Sym::PUBLIC,
Sym::STATIC,
Sym::YIELD,
];
/// An `identifier` is a sequence of characters in the code that identifies a variable,
/// function, or property.
///
/// In ECMAScript, identifiers are case-sensitive and can contain Unicode letters, $, _, and
/// digits (0-9), but may not start with a digit.
///
/// An identifier differs from a string in that a string is data, while an identifier is part
/// of the code. In JavaScript, there is no way to convert identifiers to strings, but
/// sometimes it is possible to parse strings into identifiers.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-Identifier
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Identifier {
ident: Sym,
}
impl PartialEq<Sym> for Identifier {
#[inline]
fn eq(&self, other: &Sym) -> bool {
self.ident == *other
}
}
impl PartialEq<Identifier> for Sym {
#[inline]
fn eq(&self, other: &Identifier) -> bool {
*self == other.ident
}
}
impl Identifier {
/// Creates a new identifier AST Expression.
#[inline]
#[must_use]
pub const fn new(ident: Sym) -> Self {
Self { ident }
}
/// Retrieves the identifier's string symbol in the interner.
#[inline]
#[must_use]
pub const fn sym(self) -> Sym {
self.ident
}
}
impl ToInternedString for Identifier {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
interner.resolve_expect(self.ident).join(
String::from,
ToStringEscaped::to_string_escaped,
true,
)
}
}
impl From<Sym> for Identifier {
#[inline]
fn from(sym: Sym) -> Self {
Self { ident: sym }
}
}
impl From<Identifier> for Expression {
#[inline]
fn from(local: Identifier) -> Self {
Self::Identifier(local)
}
}
impl VisitWith for Identifier {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_sym(&self.ident)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_sym_mut(&mut self.ident)
}
}

View File

@@ -0,0 +1,223 @@
//! Array declaration Expression.
use crate::expression::operator::assign::AssignTarget;
use crate::expression::Expression;
use crate::pattern::{ArrayPattern, ArrayPatternElement, Pattern};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// An array is an ordered collection of data (either primitive or object depending upon the
/// language).
///
/// Arrays are used to store multiple values in a single variable.
/// This is compared to a variable that can store only one value.
///
/// Each item in an array has a number attached to it, called a numeric index, that allows you
/// to access it. In JavaScript, arrays start at index zero and can be manipulated with various
/// methods.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayLiteral {
arr: Box<[Option<Expression>]>,
has_trailing_comma_spread: bool,
}
impl ArrayLiteral {
/// Creates a new array literal.
pub fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self
where
A: Into<Box<[Option<Expression>]>>,
{
Self {
arr: array.into(),
has_trailing_comma_spread,
}
}
/// Indicates if a spread operator in the array literal has a trailing comma.
/// This is a syntax error in some cases.
#[must_use]
pub const fn has_trailing_comma_spread(&self) -> bool {
self.has_trailing_comma_spread
}
/// Converts this `ArrayLiteral` into an [`ArrayPattern`].
#[must_use]
pub fn to_pattern(&self, strict: bool) -> Option<ArrayPattern> {
if self.has_trailing_comma_spread() {
return None;
}
let mut bindings = Vec::new();
for (i, expr) in self.arr.iter().enumerate() {
let expr = if let Some(expr) = expr {
expr
} else {
bindings.push(ArrayPatternElement::Elision);
continue;
};
match expr {
Expression::Identifier(ident) => {
if strict && *ident == Sym::ARGUMENTS {
return None;
}
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: None,
});
}
Expression::Spread(spread) => {
match spread.target() {
Expression::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleNameRest { ident: *ident });
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccessRest {
access: access.clone(),
});
}
Expression::ArrayLiteral(array) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
Expression::ObjectLiteral(object) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
_ => return None,
}
if i + 1 != self.arr.len() {
return None;
}
}
Expression::Assign(assign) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
AssignTarget::Pattern(pattern) => match pattern {
Pattern::Object(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Object(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
Pattern::Array(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Array(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
},
},
Expression::ArrayLiteral(array) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::ObjectLiteral(object) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
_ => return None,
}
}
Some(ArrayPattern::new(bindings.into()))
}
}
impl AsRef<[Option<Expression>]> for ArrayLiteral {
#[inline]
fn as_ref(&self) -> &[Option<Expression>] {
&self.arr
}
}
impl<T> From<T> for ArrayLiteral
where
T: Into<Box<[Option<Expression>]>>,
{
fn from(decl: T) -> Self {
Self {
arr: decl.into(),
has_trailing_comma_spread: false,
}
}
}
impl ToInternedString for ArrayLiteral {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = String::from("[");
let mut first = true;
for e in &*self.arr {
if first {
first = false;
} else {
buf.push_str(", ");
}
if let Some(e) = e {
buf.push_str(&e.to_interned_string(interner));
}
}
buf.push(']');
buf
}
}
impl From<ArrayLiteral> for Expression {
#[inline]
fn from(arr: ArrayLiteral) -> Self {
Self::ArrayLiteral(arr)
}
}
impl VisitWith for ArrayLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.arr.iter().flatten() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for expr in self.arr.iter_mut().flatten() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,202 @@
//! This module contains all literal expressions, which represents the primitive values in ECMAScript.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
mod array;
mod object;
mod template;
pub use array::ArrayLiteral;
use core::ops::ControlFlow;
pub use object::ObjectLiteral;
pub use template::{TemplateElement, TemplateLiteral};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use num_bigint::BigInt;
use super::Expression;
/// Literals represent values in ECMAScript.
///
/// These are fixed values **not variables** that you literally provide in your script.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
/// A string literal is zero or more characters enclosed in double (`"`) or single (`'`) quotation marks.
///
/// A string must be delimited by quotation marks of the same type (that is, either both single quotation marks, or both double quotation marks).
/// You can call any of the String object's methods on a string literal value.
/// ECMAScript automatically converts the string literal to a temporary String object,
/// calls the method, then discards the temporary String object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-string-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#String_literals
String(Sym),
/// A floating-point number literal.
///
/// The exponent part is an "`e`" or "`E`" followed by an integer, which can be signed (preceded by "`+`" or "`-`").
/// A floating-point literal must have at least one digit, and either a decimal point or "`e`" (or "`E`").
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-number-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Floating-point_literals
Num(f64),
/// Integer types can be expressed in decimal (base 10), hexadecimal (base 16), octal (base 8) and binary (base 2).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-number-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Numeric_literals
Int(i32),
/// BigInt provides a way to represent whole numbers larger than the largest number ECMAScript
/// can reliably represent with the `Number` primitive.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-bigint-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Numeric_literals
BigInt(Box<BigInt>),
/// The Boolean type has two literal values: `true` and `false`.
///
/// The Boolean object is a wrapper around the primitive Boolean data type.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-boolean-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Boolean_literals
Bool(bool),
/// In JavaScript, `null` is marked as one of the primitive values, cause it's behaviour is seemingly primitive.
///
/// In computer science, a null value represents a reference that points,
/// generally intentionally, to a nonexistent or invalid object or address.
/// The meaning of a null reference varies among language implementations.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-null-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/null
Null,
}
impl From<Sym> for Literal {
#[inline]
fn from(string: Sym) -> Self {
Self::String(string)
}
}
impl From<f64> for Literal {
#[inline]
fn from(num: f64) -> Self {
Self::Num(num)
}
}
impl From<i32> for Literal {
#[inline]
fn from(i: i32) -> Self {
Self::Int(i)
}
}
impl From<BigInt> for Literal {
#[inline]
fn from(i: BigInt) -> Self {
Self::BigInt(Box::new(i))
}
}
impl From<Box<BigInt>> for Literal {
#[inline]
fn from(i: Box<BigInt>) -> Self {
Self::BigInt(i)
}
}
impl From<bool> for Literal {
#[inline]
fn from(b: bool) -> Self {
Self::Bool(b)
}
}
impl From<Literal> for Expression {
#[inline]
fn from(lit: Literal) -> Self {
Self::Literal(lit)
}
}
impl ToInternedString for Literal {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match *self {
Self::String(st) => {
format!("\"{}\"", interner.resolve_expect(st))
}
Self::Num(num) => num.to_string(),
Self::Int(num) => num.to_string(),
Self::BigInt(ref num) => num.to_string(),
Self::Bool(v) => v.to_string(),
Self::Null => "null".to_owned(),
}
}
}
impl VisitWith for Literal {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Self::String(sym) = self {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Self::String(sym) = self {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,326 @@
//! Object Expression.
use crate::{
block_to_string,
expression::{operator::assign::AssignTarget, Expression, RESERVED_IDENTIFIERS_STRICT},
function::Function,
join_nodes,
pattern::{ObjectPattern, ObjectPatternElement},
property::{MethodDefinition, PropertyDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// Objects in ECMAScript may be defined as an unordered collection of related data, of
/// primitive or reference types, in the form of “key: value” pairs.
///
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
/// notation.
///
/// An object initializer is an expression that describes the initialization of an
/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
/// Values of object properties can either contain [`primitive`][primitive] data types or other
/// objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectLiteral {
properties: Box<[PropertyDefinition]>,
}
impl ObjectLiteral {
/// Gets the object literal properties
#[inline]
#[must_use]
pub const fn properties(&self) -> &[PropertyDefinition] {
&self.properties
}
/// Converts the object literal into an [`ObjectPattern`].
#[must_use]
pub fn to_pattern(&self, strict: bool) -> Option<ObjectPattern> {
let mut bindings = Vec::new();
let mut excluded_keys = Vec::new();
for (i, property) in self.properties.iter().enumerate() {
match property {
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => {
return None
}
PropertyDefinition::IdentifierReference(ident) => {
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: None,
});
}
PropertyDefinition::Property(name, expr) => match (name, expr) {
(PropertyName::Literal(name), Expression::Identifier(ident))
if *name == *ident =>
{
if strict && *name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ObjectLiteral(object)) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ArrayLiteral(array)) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(_, Expression::Assign(assign)) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
if let Some(name) = name.literal() {
if name == *ident {
if strict && name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name) {
return None;
}
excluded_keys.push(*ident);
}
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(name),
default_init: Some(assign.rhs().clone()),
});
} else {
return None;
}
}
AssignTarget::Pattern(pattern) => {
bindings.push(ObjectPatternElement::Pattern {
name: name.clone(),
pattern: pattern.clone(),
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: Some(assign.rhs().clone()),
});
}
},
(_, Expression::PropertyAccess(access)) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: None,
});
}
(PropertyName::Computed(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Computed(name.clone()),
default_init: None,
});
}
_ => return None,
},
PropertyDefinition::SpreadObject(spread) => {
match spread {
Expression::Identifier(ident) => {
bindings.push(ObjectPatternElement::RestProperty {
ident: *ident,
excluded_keys: excluded_keys.clone(),
});
}
Expression::PropertyAccess(access) => {
bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess {
access: access.clone(),
excluded_keys: excluded_keys.clone(),
});
}
_ => return None,
}
if i + 1 != self.properties.len() {
return None;
}
}
PropertyDefinition::MethodDefinition(_, _) => return None,
PropertyDefinition::CoverInitializedName(ident, expr) => {
if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) {
return None;
}
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: Some(expr.clone()),
});
}
}
}
Some(ObjectPattern::new(bindings.into()))
}
}
impl ToIndentedString for ObjectLiteral {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let mut buf = "{\n".to_owned();
let indentation = " ".repeat(indent_n + 1);
for property in self.properties().iter() {
buf.push_str(&match property {
PropertyDefinition::IdentifierReference(ident) => {
format!("{indentation}{},\n", interner.resolve_expect(ident.sym()))
}
PropertyDefinition::Property(key, value) => {
let value = if let Expression::Function(f) = value {
Function::new(None, f.parameters().clone(), f.body().clone()).into()
} else {
value.clone()
};
format!(
"{indentation}{}: {},\n",
key.to_interned_string(interner),
value.to_no_indent_string(interner, indent_n + 1)
)
}
PropertyDefinition::SpreadObject(key) => {
format!("{indentation}...{},\n", key.to_interned_string(interner))
}
PropertyDefinition::MethodDefinition(key, method) => {
format!(
"{indentation}{}{}({}) {},\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
key.to_interned_string(interner),
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::Generator(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::Async(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
},
)
}
PropertyDefinition::CoverInitializedName(ident, expr) => {
format!(
"{indentation}{} = {},\n",
interner.resolve_expect(ident.sym()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
});
}
buf.push_str(&format!("{}}}", " ".repeat(indent_n)));
buf
}
}
impl<T> From<T> for ObjectLiteral
where
T: Into<Box<[PropertyDefinition]>>,
{
fn from(props: T) -> Self {
Self {
properties: props.into(),
}
}
}
impl From<ObjectLiteral> for Expression {
#[inline]
fn from(obj: ObjectLiteral) -> Self {
Self::ObjectLiteral(obj)
}
}
impl VisitWith for ObjectLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for pd in self.properties.iter() {
try_break!(visitor.visit_property_definition(pd));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for pd in self.properties.iter_mut() {
try_break!(visitor.visit_property_definition_mut(pd));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,133 @@
//! Template literal Expression.
use core::ops::ControlFlow;
use std::borrow::Cow;
use boa_interner::{Interner, Sym, ToInternedString};
use crate::{
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
ToStringEscaped,
};
/// Template literals are string literals allowing embedded expressions.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct TemplateLiteral {
elements: Box<[TemplateElement]>,
}
impl From<TemplateLiteral> for Expression {
#[inline]
fn from(tem: TemplateLiteral) -> Self {
Self::TemplateLiteral(tem)
}
}
/// An element found within a [`TemplateLiteral`].
///
/// The [spec] doesn't define an element akin to `TemplateElement`. However, the AST defines this
/// node as the equivalent of the components found in a template literal.
///
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum TemplateElement {
/// A simple string.
String(Sym),
/// An expression that is evaluated and replaced by its string representation.
Expr(Expression),
}
impl TemplateLiteral {
/// Creates a new `TemplateLiteral` from a list of [`TemplateElement`]s.
#[inline]
#[must_use]
pub fn new(elements: Box<[TemplateElement]>) -> Self {
Self { elements }
}
/// Gets the element list of this `TemplateLiteral`.
#[must_use]
pub const fn elements(&self) -> &[TemplateElement] {
&self.elements
}
}
impl ToInternedString for TemplateLiteral {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = "`".to_owned();
for elt in self.elements.iter() {
match elt {
TemplateElement::String(s) => buf.push_str(&interner.resolve_expect(*s).join(
Cow::Borrowed,
|utf16| Cow::Owned(utf16.to_string_escaped()),
true,
)),
TemplateElement::Expr(n) => {
buf.push_str(&format!("${{{}}}", n.to_interned_string(interner)));
}
}
}
buf.push('`');
buf
}
}
impl VisitWith for TemplateLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for element in self.elements.iter() {
try_break!(visitor.visit_template_element(element));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for element in self.elements.iter_mut() {
try_break!(visitor.visit_template_element_mut(element));
}
ControlFlow::Continue(())
}
}
impl VisitWith for TemplateElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::String(sym) => visitor.visit_sym(sym),
Self::Expr(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::String(sym) => visitor.visit_sym_mut(sym),
Self::Expr(expr) => visitor.visit_expression_mut(expr),
}
}
}

View File

@@ -0,0 +1,332 @@
//! The [`Expression`] Parse Node, as defined by the [spec].
//!
//! ECMAScript expressions include:
//! - [Primary][primary] expressions (`this`, function expressions, literals).
//! - [Left hand side][lhs] expressions (accessors, `new` operator, `super`).
//! - [operator] expressions.
//!
//! [spec]: https://tc39.es/ecma262/#prod-Expression
//! [primary]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#primary_expressions
//! [lhs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#left-hand-side_expressions
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use self::{
access::PropertyAccess,
literal::{ArrayLiteral, Literal, ObjectLiteral, TemplateLiteral},
operator::{Assign, Binary, Conditional, Unary},
};
use super::{
function::{ArrowFunction, AsyncFunction, AsyncGenerator, Class, Function, Generator},
function::{AsyncArrowFunction, FormalParameterList},
Statement,
};
mod r#await;
mod call;
mod identifier;
mod new;
mod optional;
mod spread;
mod tagged_template;
mod r#yield;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use call::{Call, SuperCall};
pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT};
pub use new::New;
pub use optional::{Optional, OptionalOperation, OptionalOperationKind};
pub use r#await::Await;
pub use r#yield::Yield;
pub use spread::Spread;
pub use tagged_template::TaggedTemplate;
pub mod access;
pub mod literal;
pub mod operator;
/// The `Expression` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
/// The ECMAScript `this` keyword refers to the object it belongs to.
///
/// A property of an execution context (global, function or eval) that,
/// in nonstrict mode, is always a reference to an object and in strict
/// mode can be any value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-this-keyword
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
This,
/// See [`Identifier`].
Identifier(Identifier),
/// See [`Literal`].
Literal(Literal),
/// See [`ArrayLiteral`].
ArrayLiteral(ArrayLiteral),
/// See [`ObjectLiteral`].
ObjectLiteral(ObjectLiteral),
/// See [`Spread`],
Spread(Spread),
/// See [`Function`].
Function(Function),
/// See [`ArrowFunction`].
ArrowFunction(ArrowFunction),
/// See [`AsyncArrowFunction`].
AsyncArrowFunction(AsyncArrowFunction),
/// See [`Generator`].
Generator(Generator),
/// See [`AsyncFunction`].
AsyncFunction(AsyncFunction),
/// See [`AsyncGenerator`].
AsyncGenerator(AsyncGenerator),
/// See [`Class`].
Class(Box<Class>),
// TODO: Extract regexp literal Expression
// RegExpLiteral,
/// See [`TemplateLiteral`].
TemplateLiteral(TemplateLiteral),
/// See [`PropertyAccess`].
PropertyAccess(PropertyAccess),
/// See [`New`].
New(New),
/// See [`Call`].
Call(Call),
/// See [`SuperCall`].
SuperCall(SuperCall),
/// See [`Optional`].
Optional(Optional),
// TODO: Import calls
/// See [`TaggedTemplate`].
TaggedTemplate(TaggedTemplate),
/// The `new.target` pseudo-property expression.
NewTarget,
// TODO: import.meta
/// See [`Assign`].
Assign(Assign),
/// See [`Unary`].
Unary(Unary),
/// See [`Binary`].
Binary(Binary),
/// See [`Conditional`].
Conditional(Conditional),
/// See [`Await`].
Await(Await),
/// See [`Yield`].
Yield(Yield),
/// A FormalParameterList.
///
/// This is only used in the parser itself.
/// It is not a valid expression node.
#[doc(hidden)]
FormalParameterList(FormalParameterList),
}
impl Expression {
/// Implements the display formatting with indentation.
///
/// This will not prefix the value with any indentation. If you want to prefix this with proper
/// indents, use [`to_indented_string()`](Self::to_indented_string).
pub(crate) fn to_no_indent_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
Self::This => "this".to_owned(),
Self::Identifier(id) => id.to_interned_string(interner),
Self::Literal(lit) => lit.to_interned_string(interner),
Self::ArrayLiteral(arr) => arr.to_interned_string(interner),
Self::ObjectLiteral(o) => o.to_indented_string(interner, indentation),
Self::Spread(sp) => sp.to_interned_string(interner),
Self::Function(f) => f.to_indented_string(interner, indentation),
Self::AsyncArrowFunction(f) => f.to_indented_string(interner, indentation),
Self::ArrowFunction(arrf) => arrf.to_indented_string(interner, indentation),
Self::Class(cl) => cl.to_indented_string(interner, indentation),
Self::Generator(gen) => gen.to_indented_string(interner, indentation),
Self::AsyncFunction(asf) => asf.to_indented_string(interner, indentation),
Self::AsyncGenerator(asgen) => asgen.to_indented_string(interner, indentation),
Self::TemplateLiteral(tem) => tem.to_interned_string(interner),
Self::PropertyAccess(prop) => prop.to_interned_string(interner),
Self::New(new) => new.to_interned_string(interner),
Self::Call(call) => call.to_interned_string(interner),
Self::SuperCall(supc) => supc.to_interned_string(interner),
Self::Optional(opt) => opt.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(),
Self::TaggedTemplate(tag) => tag.to_interned_string(interner),
Self::Assign(assign) => assign.to_interned_string(interner),
Self::Unary(unary) => unary.to_interned_string(interner),
Self::Binary(bin) => bin.to_interned_string(interner),
Self::Conditional(cond) => cond.to_interned_string(interner),
Self::Await(aw) => aw.to_interned_string(interner),
Self::Yield(yi) => yi.to_interned_string(interner),
Self::FormalParameterList(_) => unreachable!(),
}
}
/// Returns if the expression is a function definition according to the spec.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-isfunctiondefinition
#[must_use]
#[inline]
pub const fn is_function_definition(&self) -> bool {
matches!(
self,
Self::ArrowFunction(_)
| Self::AsyncArrowFunction(_)
| Self::Function(_)
| Self::Generator(_)
| Self::AsyncGenerator(_)
| Self::AsyncFunction(_)
| Self::Class(_)
)
}
/// Returns if the expression is a function definition without a name.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isanonymousfunctiondefinition
#[must_use]
#[inline]
pub const fn is_anonymous_function_definition(&self) -> bool {
match self {
Self::ArrowFunction(f) => f.name().is_none(),
Self::AsyncArrowFunction(f) => f.name().is_none(),
Self::Function(f) => f.name().is_none(),
Self::Generator(f) => f.name().is_none(),
Self::AsyncGenerator(f) => f.name().is_none(),
Self::AsyncFunction(f) => f.name().is_none(),
Self::Class(f) => f.name().is_none(),
_ => false,
}
}
}
impl From<Expression> for Statement {
#[inline]
fn from(expr: Expression) -> Self {
Self::Expression(expr)
}
}
impl ToIndentedString for Expression {
#[inline]
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
self.to_no_indent_string(interner, indentation)
}
}
impl VisitWith for Expression {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Literal(lit) => visitor.visit_literal(lit),
Self::ArrayLiteral(arlit) => visitor.visit_array_literal(arlit),
Self::ObjectLiteral(olit) => visitor.visit_object_literal(olit),
Self::Spread(sp) => visitor.visit_spread(sp),
Self::Function(f) => visitor.visit_function(f),
Self::ArrowFunction(af) => visitor.visit_arrow_function(af),
Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function(af),
Self::Generator(g) => visitor.visit_generator(g),
Self::AsyncFunction(af) => visitor.visit_async_function(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::Class(c) => visitor.visit_class(c),
Self::TemplateLiteral(tlit) => visitor.visit_template_literal(tlit),
Self::PropertyAccess(pa) => visitor.visit_property_access(pa),
Self::New(n) => visitor.visit_new(n),
Self::Call(c) => visitor.visit_call(c),
Self::SuperCall(sc) => visitor.visit_super_call(sc),
Self::Optional(opt) => visitor.visit_optional(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt),
Self::Assign(a) => visitor.visit_assign(a),
Self::Unary(u) => visitor.visit_unary(u),
Self::Binary(b) => visitor.visit_binary(b),
Self::Conditional(c) => visitor.visit_conditional(c),
Self::Await(a) => visitor.visit_await(a),
Self::Yield(y) => visitor.visit_yield(y),
Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list(fpl),
Self::This | Self::NewTarget => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Literal(lit) => visitor.visit_literal_mut(lit),
Self::ArrayLiteral(arlit) => visitor.visit_array_literal_mut(arlit),
Self::ObjectLiteral(olit) => visitor.visit_object_literal_mut(olit),
Self::Spread(sp) => visitor.visit_spread_mut(sp),
Self::Function(f) => visitor.visit_function_mut(f),
Self::ArrowFunction(af) => visitor.visit_arrow_function_mut(af),
Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function_mut(af),
Self::Generator(g) => visitor.visit_generator_mut(g),
Self::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::Class(c) => visitor.visit_class_mut(c),
Self::TemplateLiteral(tlit) => visitor.visit_template_literal_mut(tlit),
Self::PropertyAccess(pa) => visitor.visit_property_access_mut(pa),
Self::New(n) => visitor.visit_new_mut(n),
Self::Call(c) => visitor.visit_call_mut(c),
Self::SuperCall(sc) => visitor.visit_super_call_mut(sc),
Self::Optional(opt) => visitor.visit_optional_mut(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt),
Self::Assign(a) => visitor.visit_assign_mut(a),
Self::Unary(u) => visitor.visit_unary_mut(u),
Self::Binary(b) => visitor.visit_binary_mut(b),
Self::Conditional(c) => visitor.visit_conditional_mut(c),
Self::Await(a) => visitor.visit_await_mut(a),
Self::Yield(y) => visitor.visit_yield_mut(y),
Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list_mut(fpl),
Self::This | Self::NewTarget => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::expression::Call;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// The `new` operator lets developers create an instance of a user-defined object type or of
/// one of the built-in object types that has a constructor function.
///
/// The new keyword does the following things:
/// - Creates a blank, plain JavaScript object;
/// - Links (sets the constructor of) this object to another object;
/// - Passes the newly created object from Step 1 as the this context;
/// - Returns this if the function doesn't return its own object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-NewExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct New {
call: Call,
}
impl New {
/// Gets the constructor of the new expression.
#[inline]
#[must_use]
pub const fn constructor(&self) -> &Expression {
self.call.function()
}
/// Retrieves the arguments passed to the constructor.
#[inline]
#[must_use]
pub const fn arguments(&self) -> &[Expression] {
self.call.args()
}
/// Returns the inner call expression.
#[must_use]
pub const fn call(&self) -> &Call {
&self.call
}
}
impl From<Call> for New {
#[inline]
fn from(call: Call) -> Self {
Self { call }
}
}
impl ToInternedString for New {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("new {}", self.call.to_interned_string(interner))
}
}
impl From<New> for Expression {
#[inline]
fn from(new: New) -> Self {
Self::New(new)
}
}
impl VisitWith for New {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_call(&self.call)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_call_mut(&mut self.call)
}
}

View File

@@ -0,0 +1,191 @@
//! Assignment expression nodes, as defined by the [spec].
//!
//! An [assignment operator][mdn] assigns a value to its left operand based on the value of its right
//! operand. Almost any [`LeftHandSideExpression`][lhs] Parse Node can be the target of a simple
//! assignment expression (`=`). However, the compound assignment operations such as `%=` or `??=`
//! only allow ["simple"][simple] left hand side expressions as an assignment target.
//!
//! [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
//! [lhs]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
//! [simple]: https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::{
expression::{access::PropertyAccess, identifier::Identifier, Expression},
pattern::Pattern,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
/// An assignment operator expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Assign {
op: AssignOp,
lhs: Box<AssignTarget>,
rhs: Box<Expression>,
}
impl Assign {
/// Creates an `Assign` AST Expression.
#[inline]
#[must_use]
pub fn new(op: AssignOp, lhs: AssignTarget, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
/// Gets the operator of the assignment operation.
#[inline]
#[must_use]
pub const fn op(&self) -> AssignOp {
self.op
}
/// Gets the left hand side of the assignment operation.
#[inline]
#[must_use]
pub const fn lhs(&self) -> &AssignTarget {
&self.lhs
}
/// Gets the right hand side of the assignment operation.
#[inline]
#[must_use]
pub const fn rhs(&self) -> &Expression {
&self.rhs
}
}
impl ToInternedString for Assign {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {} {}",
self.lhs.to_interned_string(interner),
self.op,
self.rhs.to_interned_string(interner)
)
}
}
impl From<Assign> for Expression {
#[inline]
fn from(op: Assign) -> Self {
Self::Assign(op)
}
}
impl VisitWith for Assign {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_assign_target(&self.lhs));
visitor.visit_expression(&self.rhs)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_assign_target_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}
/// The valid left-hand-side expressions of an assignment operator. Also called
/// [`LeftHandSideExpression`][spec] in the spec.
///
/// [spec]: hhttps://tc39.es/ecma262/#prod-LeftHandSideExpression
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum AssignTarget {
/// A simple identifier, such as `a`.
Identifier(Identifier),
/// A property access, such as `a.prop`.
Access(PropertyAccess),
/// A pattern assignment, such as `{a, b, ...c}`.
Pattern(Pattern),
}
impl AssignTarget {
/// Converts the left-hand-side Expression of an assignment expression into an [`AssignTarget`].
/// Returns `None` if the given Expression is an invalid left-hand-side for a assignment expression.
#[must_use]
pub fn from_expression(
expression: &Expression,
strict: bool,
destructure: bool,
) -> Option<Self> {
match expression {
Expression::Identifier(id) => Some(Self::Identifier(*id)),
Expression::PropertyAccess(access) => Some(Self::Access(access.clone())),
Expression::ObjectLiteral(object) if destructure => {
let pattern = object.to_pattern(strict)?;
Some(Self::Pattern(pattern.into()))
}
Expression::ArrayLiteral(array) if destructure => {
let pattern = array.to_pattern(strict)?;
Some(Self::Pattern(pattern.into()))
}
_ => None,
}
}
}
impl ToInternedString for AssignTarget {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Identifier(id) => id.to_interned_string(interner),
Self::Access(access) => access.to_interned_string(interner),
Self::Pattern(pattern) => pattern.to_interned_string(interner),
}
}
}
impl From<Identifier> for AssignTarget {
#[inline]
fn from(target: Identifier) -> Self {
Self::Identifier(target)
}
}
impl VisitWith for AssignTarget {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Access(pa) => visitor.visit_property_access(pa),
Self::Pattern(pat) => visitor.visit_pattern(pat),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Access(pa) => visitor.visit_property_access_mut(pa),
Self::Pattern(pat) => visitor.visit_pattern_mut(pat),
}
}
}

View File

@@ -0,0 +1,244 @@
/// An assignment operator assigns a value to its left operand based on the value of its right operand.
///
/// The simple assignment operator is equal (`=`), which assigns the value of its right operand to its
/// left operand. That is, `x = y` assigns the value of `y to x`.
///
/// There are also compound assignment operators that are shorthand for the operations
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignOp {
/// The assignment operator assigns the value of the right operand to the left operand.
///
/// Syntax: `x = y`
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment
Assign,
/// The addition assignment operator adds the value of the right operand to a variable and assigns the result to the variable.
///
/// Syntax: `x += y`
///
/// The types of the two operands determine the behavior of the addition assignment operator. Addition or concatenation is possible.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Addition_assignment
Add,
/// The subtraction assignment operator subtracts the value of the right operand from a variable and assigns the result to the variable.
///
/// Syntax: `x -= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Subtraction_assignment
Sub,
/// The multiplication assignment operator multiplies a variable by the value of the right operand and assigns the result to the variable.
///
/// Syntax: `x *= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Multiplication_assignment
Mul,
/// The division assignment operator divides a variable by the value of the right operand and assigns the result to the variable.
///
/// Syntax: `x /= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Division_assignment
Div,
/// The remainder assignment operator divides a variable by the value of the right operand and assigns the remainder to the variable.
///
/// Syntax: `x %= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Remainder_assignment
Mod,
/// The exponentiation assignment operator raises the value of a variable to the power of the right operand.
///
/// Syntax: `x ** y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Exponentiation_assignment
Exp,
/// The bitwise AND assignment operator uses the binary representation of both operands, does a bitwise AND operation on
/// them and assigns the result to the variable.
///
/// Syntax: `x &= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Bitwise_AND_assignment
And,
/// The bitwise OR assignment operator uses the binary representation of both operands, does a bitwise OR operation on
/// them and assigns the result to the variable.
///
/// Syntax: `x |= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Bitwise_OR_assignment
Or,
/// The bitwise XOR assignment operator uses the binary representation of both operands, does a bitwise XOR operation on
/// them and assigns the result to the variable.
///
/// Syntax: `x ^= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Bitwise_XOR_assignment
Xor,
/// The left shift assignment operator moves the specified amount of bits to the left and assigns the result to the variable.
///
/// Syntax: `x <<= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Left_shift_assignment
Shl,
/// The right shift assignment operator moves the specified amount of bits to the right and assigns the result to the variable.
///
/// Syntax: `x >>= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Right_shift_assignment
Shr,
/// The unsigned right shift assignment operator moves the specified amount of bits to the right and assigns the result to the variable.
///
/// Syntax: `x >>>= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift_assignment
Ushr,
/// The logical and assignment operator only assigns if the target variable is truthy.
///
/// Syntax: `x &&= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND_assignment
BoolAnd,
/// The logical or assignment operator only assigns if the target variable is falsy.
///
/// Syntax: `x ||= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment
BoolOr,
/// The logical nullish assignment operator only assigns if the target variable is nullish (null or undefined).
///
/// Syntax: `x ??= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment
Coalesce,
}
impl AssignOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Assign => "=",
Self::Add => "+=",
Self::Sub => "-=",
Self::Mul => "*=",
Self::Exp => "**=",
Self::Div => "/=",
Self::Mod => "%=",
Self::And => "&=",
Self::Or => "|=",
Self::Xor => "^=",
Self::Shl => "<<=",
Self::Shr => ">>=",
Self::Ushr => ">>>=",
Self::BoolAnd => "&&=",
Self::BoolOr => "||=",
Self::Coalesce => "??=",
}
}
}
impl std::fmt::Display for AssignOp {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -0,0 +1,111 @@
//! Binary expression nodes.
//!
//! A Binary expression comprises any operation between two expressions (excluding assignments),
//! such as:
//! - [Logic operations][logic] (`||`, `&&`).
//! - [Relational math][relat] (`==`, `<`).
//! - [Bit manipulation][bit] (`^`, `|`).
//! - [Arithmetic][arith] (`+`, `%`).
//! - The [comma operator][comma] (`,`)
//!
//! [logic]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_logical_operators
//! [relat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#relational_operators
//! [bit]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_bitwise_operators
//! [arith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#arithmetic_operators
//! [comma]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::{
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
/// Binary operations require two operands, one before the operator and one after the operator.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Binary {
op: BinaryOp,
lhs: Box<Expression>,
rhs: Box<Expression>,
}
impl Binary {
/// Creates a `BinOp` AST Expression.
#[inline]
#[must_use]
pub fn new(op: BinaryOp, lhs: Expression, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
/// Gets the binary operation of the Expression.
#[inline]
#[must_use]
pub const fn op(&self) -> BinaryOp {
self.op
}
/// Gets the left hand side of the binary operation.
#[inline]
#[must_use]
pub const fn lhs(&self) -> &Expression {
&self.lhs
}
/// Gets the right hand side of the binary operation.
#[inline]
#[must_use]
pub const fn rhs(&self) -> &Expression {
&self.rhs
}
}
impl ToInternedString for Binary {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {} {}",
self.lhs.to_interned_string(interner),
self.op,
self.rhs.to_interned_string(interner)
)
}
}
impl From<Binary> for Expression {
#[inline]
fn from(op: Binary) -> Self {
Self::Binary(op)
}
}
impl VisitWith for Binary {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.lhs));
visitor.visit_expression(&self.rhs)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}

View File

@@ -0,0 +1,578 @@
//! This module implements various structure for logic handling.
use std::fmt::{Display, Formatter, Result};
/// This represents a binary operation between two values.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp {
/// Numeric operation.
///
/// see: [`NumOp`](enum.NumOp.html)
Arithmetic(ArithmeticOp),
/// Bitwise operation.
///
/// see: [`BitOp`](enum.BitOp.html).
Bitwise(BitwiseOp),
/// Comparative operation.
///
/// see: [`CompOp`](enum.CompOp.html).
Relational(RelationalOp),
/// Logical operation.
///
/// see: [`LogOp`](enum.LogOp.html).
Logical(LogicalOp),
/// Comma operation.
Comma,
}
impl From<ArithmeticOp> for BinaryOp {
#[inline]
fn from(op: ArithmeticOp) -> Self {
Self::Arithmetic(op)
}
}
impl From<BitwiseOp> for BinaryOp {
#[inline]
fn from(op: BitwiseOp) -> Self {
Self::Bitwise(op)
}
}
impl From<RelationalOp> for BinaryOp {
#[inline]
fn from(op: RelationalOp) -> Self {
Self::Relational(op)
}
}
impl From<LogicalOp> for BinaryOp {
#[inline]
fn from(op: LogicalOp) -> Self {
Self::Logical(op)
}
}
impl BinaryOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Arithmetic(ref op) => op.as_str(),
Self::Bitwise(ref op) => op.as_str(),
Self::Relational(ref op) => op.as_str(),
Self::Logical(ref op) => op.as_str(),
Self::Comma => ",",
}
}
}
impl Display for BinaryOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// Arithmetic operators take numerical values (either literals or variables)
/// as their operands and return a single numerical value.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Arithmetic
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ArithmeticOp {
/// The addition operator produces the sum of numeric operands or string concatenation.
///
/// Syntax: `x + y`
///
/// More information:
/// - [ECMAScript reference][spec].
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-addition-operator-plus
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Addition
Add,
/// The subtraction operator subtracts the two operands, producing their difference.
///
/// Syntax: `x - y`
///
/// More information:
/// - [ECMAScript reference][spec].
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-subtraction-operator-minus
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Subtraction
Sub,
/// The division operator produces the quotient of its operands where the left operand
/// is the dividend and the right operand is the divisor.
///
/// Syntax: `x / y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MultiplicativeOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Division
Div,
/// The multiplication operator produces the product of the operands.
///
/// Syntax: `x * y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MultiplicativeExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Multiplication
Mul,
/// The exponentiation operator returns the result of raising the first operand to
/// the power of the second operand.
///
/// Syntax: `x ** y`
///
/// The exponentiation operator is right-associative. a ** b ** c is equal to a ** (b ** c).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-exp-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation
Exp,
/// The remainder operator returns the remainder left over when one operand is divided by a second operand.
///
/// Syntax: `x % y`
///
/// The remainder operator always takes the sign of the dividend.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MultiplicativeOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder
Mod,
}
impl ArithmeticOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::Sub => "-",
Self::Div => "/",
Self::Mul => "*",
Self::Exp => "**",
Self::Mod => "%",
}
}
}
impl Display for ArithmeticOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// A bitwise operator is an operator used to perform bitwise operations
/// on bit patterns or binary numerals that involve the manipulation of individual bits.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BitwiseOp {
/// Performs the AND operation on each pair of bits. a AND b yields 1 only if both a and b are 1.
///
/// Syntax: `x & y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BitwiseANDExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_AND
And,
/// Performs the OR operation on each pair of bits. a OR b yields 1 if either a or b is 1.
///
/// Syntax: `x | y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BitwiseORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_OR
Or,
/// Performs the XOR operation on each pair of bits. a XOR b yields 1 if a and b are different.
///
/// Syntax: `x ^ y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BitwiseXORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_XOR
Xor,
/// This operator shifts the first operand the specified number of bits to the left.
///
/// Syntax: `x << y`
///
/// Excess bits shifted off to the left are discarded. Zero bits are shifted in from the right.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-left-shift-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Left_shift
Shl,
/// This operator shifts the first operand the specified number of bits to the right.
///
/// Syntax: `x >> y`
///
/// Excess bits shifted off to the right are discarded. Copies of the leftmost bit
/// are shifted in from the left. Since the new leftmost bit has the same value as
/// the previous leftmost bit, the sign bit (the leftmost bit) does not change.
/// Hence the name "sign-propagating".
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-signed-right-shift-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Right_shift
Shr,
/// This operator shifts the first operand the specified number of bits to the right.
///
/// Syntax: `x >>> y`
///
/// Excess bits shifted off to the right are discarded. Zero bits are shifted in
/// from the left. The sign bit becomes 0, so the result is always non-negative.
/// Unlike the other bitwise operators, zero-fill right shift returns an unsigned 32-bit integer.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-unsigned-right-shift-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Unsigned_right_shift
UShr,
}
impl BitwiseOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::And => "&",
Self::Or => "|",
Self::Xor => "^",
Self::Shl => "<<",
Self::Shr => ">>",
Self::UShr => ">>>",
}
}
}
impl Display for BitwiseOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// A relational operator compares its operands and returns a logical value based on whether the relation is true.
///
/// The operands can be numerical, string, logical, or object values. Strings are compared based on standard
/// lexicographical ordering, using Unicode values. In most cases, if the two operands are not of the same type,
/// JavaScript attempts to convert them to an appropriate type for the comparison. This behavior generally results in
/// comparing the operands numerically. The sole exceptions to type conversion within comparisons involve the `===` and `!==`
/// operators, which perform strict equality and inequality comparisons. These operators do not attempt to convert the operands
/// to compatible types before checking equality.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: tc39.es/ecma262/#sec-testing-and-comparison-operations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Comparison
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RelationalOp {
/// The equality operator converts the operands if they are not of the same type, then applies
/// strict comparison.
///
/// Syntax: `y == y`
///
/// If both operands are objects, then JavaScript compares internal references which are equal
/// when operands refer to the same object in memory.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-abstract-equality-comparison
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Equality
Equal,
/// The inequality operator returns `true` if the operands are not equal.
///
/// Syntax: `x != y`
///
/// If the two operands are not of the same type, JavaScript attempts to convert the operands
/// to an appropriate type for the comparison. If both operands are objects, then JavaScript
/// compares internal references which are not equal when operands refer to different objects
/// in memory.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-EqualityExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Inequality
NotEqual,
/// The identity operator returns `true` if the operands are strictly equal **with no type
/// conversion**.
///
/// Syntax: `x === y`
///
/// Returns `true` if the operands are equal and of the same type.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-strict-equality-comparison
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Identity
StrictEqual,
/// The non-identity operator returns `true` if the operands **are not equal and/or not of the
/// same type**.
///
/// Syntax: `x !== y`
///
/// Returns `true` if the operands are of the same type but not equal, or are of different type.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-EqualityExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Nonidentity>
StrictNotEqual,
/// The greater than operator returns `true` if the left operand is greater than the right
/// operand.
///
/// Syntax: `x > y`
///
/// Returns `true` if the left operand is greater than the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_operator
GreaterThan,
/// The greater than or equal operator returns `true` if the left operand is greater than or
/// equal to the right operand.
///
/// Syntax: `x >= y`
///
/// Returns `true` if the left operand is greater than the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_operator
GreaterThanOrEqual,
/// The less than operator returns `true` if the left operand is less than the right operand.
///
/// Syntax: `x < y`
///
/// Returns `true` if the left operand is less than the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_operator
LessThan,
/// The less than or equal operator returns `true` if the left operand is less than or equal to
/// the right operand.
///
/// Syntax: `x <= y`
///
/// Returns `true` if the left operand is less than or equal to the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_or_equal_operator
LessThanOrEqual,
/// The `in` operator returns `true` if the specified property is in the specified object or
/// its prototype chain.
///
/// Syntax: `prop in object`
///
/// Returns `true` the specified property is in the specified object or its prototype chain.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in
In,
/// The `instanceof` operator returns `true` if the specified object is an instance of the
/// right hand side object.
///
/// Syntax: `obj instanceof Object`
///
/// Returns `true` the `prototype` property of the right hand side constructor appears anywhere
/// in the prototype chain of the object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
InstanceOf,
}
impl RelationalOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Equal => "==",
Self::NotEqual => "!=",
Self::StrictEqual => "===",
Self::StrictNotEqual => "!==",
Self::GreaterThan => ">",
Self::GreaterThanOrEqual => ">=",
Self::LessThan => "<",
Self::LessThanOrEqual => "<=",
Self::In => "in",
Self::InstanceOf => "instanceof",
}
}
}
impl Display for RelationalOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// Logical operators are typically used with Boolean (logical) values; when they are, they return a Boolean value.
///
/// However, the `&&` and `||` operators actually return the value of one of the specified operands,
/// so if these operators are used with non-Boolean values, they may return a non-Boolean value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-binary-logical-operators
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Logical
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogicalOp {
/// The logical AND operator returns the value of the first operand if it can be coerced into `false`;
/// otherwise, it returns the second operand.
///
/// Syntax: `x && y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-LogicalANDExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_AND
And,
/// The logical OR operator returns the value the first operand if it can be coerced into `true`;
/// otherwise, it returns the second operand.
///
/// Syntax: `x || y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-LogicalORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR
Or,
/// The nullish coalescing operator is a logical operator that returns the second operand
/// when its first operand is null or undefined, and otherwise returns its first operand.
///
/// Syntax: `x ?? y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CoalesceExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
Coalesce,
}
impl LogicalOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::And => "&&",
Self::Or => "||",
Self::Coalesce => "??",
}
}
}
impl Display for LogicalOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -0,0 +1,103 @@
use crate::{
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `conditional` (ternary) operation is the only ECMAScript operation that takes three
/// operands.
///
/// This operation takes three operands: a condition followed by a question mark (`?`),
/// then an expression to execute `if` the condition is truthy followed by a colon (`:`),
/// and finally the expression to execute if the condition is `false`.
/// This operator is frequently used as a shortcut for the `if` statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ConditionalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Conditional {
condition: Box<Expression>,
if_true: Box<Expression>,
if_false: Box<Expression>,
}
impl Conditional {
/// Gets the condition of the `Conditional` expression.
#[inline]
#[must_use]
pub const fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the expression returned if `condition` is truthy.
#[inline]
#[must_use]
pub const fn if_true(&self) -> &Expression {
&self.if_true
}
/// Gets the expression returned if `condition` is falsy.
#[inline]
#[must_use]
pub const fn if_false(&self) -> &Expression {
&self.if_false
}
/// Creates a `Conditional` AST Expression.
#[inline]
#[must_use]
pub fn new(condition: Expression, if_true: Expression, if_false: Expression) -> Self {
Self {
condition: Box::new(condition),
if_true: Box::new(if_true),
if_false: Box::new(if_false),
}
}
}
impl ToInternedString for Conditional {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} ? {} : {}",
self.condition().to_interned_string(interner),
self.if_true().to_interned_string(interner),
self.if_false().to_interned_string(interner)
)
}
}
impl From<Conditional> for Expression {
#[inline]
fn from(cond_op: Conditional) -> Self {
Self::Conditional(cond_op)
}
}
impl VisitWith for Conditional {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
try_break!(visitor.visit_expression(&self.if_true));
visitor.visit_expression(&self.if_false)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
try_break!(visitor.visit_expression_mut(&mut self.if_true));
visitor.visit_expression_mut(&mut self.if_false)
}
}

View File

@@ -0,0 +1,20 @@
//! Operator expression nodes.
//!
//! An [operator][op] expression is an expression that takes an operator (such as `+`, `typeof`, `+=`)
//! and one or more expressions and returns an expression as a result.
//! An operator expression can be any of the following:
//!
//! - A [`Unary`] expression.
//! - An [`Assign`] expression.
//! - A [`Binary`] expression.
//! - A [`Conditional`] expression.
//!
//! [op]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators
mod conditional;
pub mod assign;
pub mod binary;
pub mod unary;
pub use self::{assign::Assign, binary::Binary, conditional::Conditional, unary::Unary};

View File

@@ -0,0 +1,102 @@
//! Unary expression nodes.
//!
//! A Binary expression comprises any operation applied to a single expression. Some examples include:
//!
//! - [Increment and decrement operations][inc] (`++`, `--`).
//! - The [`delete`][del] operator.
//! - The [bitwise NOT][not] operator (`~`).
//!
//! The full list of valid unary operators is defined in [`UnaryOp`].
//!
//! [inc]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#increment_and_decrement
//! [del]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
//! [not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::expression::Expression;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
/// A unary expression is an operation with only one operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Unary {
op: UnaryOp,
target: Box<Expression>,
}
impl Unary {
/// Creates a new `UnaryOp` AST Expression.
#[inline]
#[must_use]
pub fn new(op: UnaryOp, target: Expression) -> Self {
Self {
op,
target: Box::new(target),
}
}
/// Gets the unary operation of the Expression.
#[inline]
#[must_use]
pub const fn op(&self) -> UnaryOp {
self.op
}
/// Gets the target of this unary operator.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
self.target.as_ref()
}
}
impl ToInternedString for Unary {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let space = match self.op {
UnaryOp::TypeOf | UnaryOp::Delete | UnaryOp::Void => " ",
_ => "",
};
format!(
"{}{space}{}",
self.op,
self.target.to_interned_string(interner)
)
}
}
impl From<Unary> for Expression {
#[inline]
fn from(op: Unary) -> Self {
Self::Unary(op)
}
}
impl VisitWith for Unary {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,216 @@
/// A unary operator is one that takes a single operand/argument and performs an operation.
///
/// A unary operation is an operation with only one operand. This operand comes either
/// before or after the operator. Unary operators are more efficient than standard JavaScript
/// function calls.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryOp {
/// The increment operator increments (adds one to) its operand and returns a value.
///
/// Syntax: `++x`
///
/// This operator increments and returns the value after incrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-postfix-increment-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment
IncrementPost,
/// The increment operator increments (adds one to) its operand and returns a value.
///
/// Syntax: `x++`
///
/// This operator increments and returns the value before incrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-prefix-increment-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment
IncrementPre,
/// The decrement operator decrements (subtracts one from) its operand and returns a value.
///
/// Syntax: `--x`
///
/// This operator decrements and returns the value before decrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-postfix-decrement-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement
DecrementPost,
/// The decrement operator decrements (subtracts one from) its operand and returns a value.
///
/// Syntax: `x--`
///
/// This operator decrements the operand and returns the value after decrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-prefix-decrement-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement
DecrementPre,
/// The unary negation operator precedes its operand and negates it.
///
/// Syntax: `-x`
///
/// Converts non-numbers data types to numbers like unary plus,
/// however, it performs an additional operation, negation.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-unary-minus-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_negation
Minus,
/// The unary plus operator attempts to convert the operand into a number, if it isn't already.
///
/// Syntax: `+x`
///
/// Although unary negation (`-`) also can convert non-numbers, unary plus is the fastest and preferred
/// way of converting something into a number, because it does not perform any other operations on the number.
/// It can convert `string` representations of integers and floats, as well as the non-string values `true`, `false`, and `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-unary-plus-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus
Plus,
/// Returns `false` if its single operand can be converted to `true`; otherwise, returns `true`.
///
/// Syntax: `!x`
///
/// Boolean values simply get inverted: `!true === false` and `!false === true`.
/// Non-boolean values get converted to boolean values first, then are negated.
/// This means that it is possible to use a couple of NOT operators in series to explicitly
/// force the conversion of any value to the corresponding boolean primitive.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-logical-not-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_NOT
Not,
/// Performs the NOT operator on each bit.
///
/// Syntax: `~x`
///
/// NOT `a` yields the inverted value (or one's complement) of `a`.
/// Bitwise NOTing any number x yields -(x + 1). For example, ~-5 yields 4.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bitwise-not-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT
Tilde,
/// The `typeof` operator returns a string indicating the type of the unevaluated operand.
///
/// Syntax: `typeof x` or `typeof(x)`
///
/// The `typeof` is a JavaScript keyword that will return the type of a variable when you call it.
/// You can use this to validate function parameters or check if variables are defined.
/// There are other uses as well.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
TypeOf,
/// The JavaScript `delete` operator removes a property from an object.
///
/// Syntax: `delete x`
///
/// Unlike what common belief suggests, the delete operator has nothing to do with
/// directly freeing memory. Memory management is done indirectly via breaking references.
/// If no more references to the same property are held, it is eventually released automatically.
///
/// The `delete` operator returns `true` for all cases except when the property is an
/// [own](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty)
/// [non-configurable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_delete)
/// property, in which case, `false` is returned in non-strict mode.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-delete-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
Delete,
/// The `void` operator evaluates the given `expression` and then returns `undefined`.
///
/// Syntax: `void x`
///
/// This operator allows evaluating expressions that produce a value into places where an
/// expression that evaluates to `undefined` is desired.
/// The `void` operator is often used merely to obtain the `undefined` primitive value, usually using `void(0)`
/// (which is equivalent to `void 0`). In these cases, the global variable undefined can be used.
///
/// When using an [immediately-invoked function expression](https://developer.mozilla.org/en-US/docs/Glossary/IIFE),
/// `void` can be used to force the function keyword to be treated as an expression instead of a declaration.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-void-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
Void,
}
impl UnaryOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::IncrementPost | Self::IncrementPre => "++",
Self::DecrementPost | Self::DecrementPre => "--",
Self::Plus => "+",
Self::Minus => "-",
Self::Not => "!",
Self::Tilde => "~",
Self::Delete => "delete",
Self::TypeOf => "typeof",
Self::Void => "void",
}
}
}
impl std::fmt::Display for UnaryOp {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -0,0 +1,249 @@
use super::{access::PropertyAccessField, Expression};
use crate::{
function::PrivateName,
join_nodes, try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// List of valid operations in an [`Optional`] chain.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum OptionalOperationKind {
/// A property access (`a?.prop`).
SimplePropertyAccess {
/// The field accessed.
field: PropertyAccessField,
},
/// A private property access (`a?.#prop`).
PrivatePropertyAccess {
/// The private property accessed.
field: PrivateName,
},
/// A function call (`a?.(arg)`).
Call {
/// The args passed to the function call.
args: Box<[Expression]>,
},
}
impl VisitWith for OptionalOperationKind {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::SimplePropertyAccess { field } => visitor.visit_property_access_field(field),
Self::PrivatePropertyAccess { field } => visitor.visit_private_name(field),
Self::Call { args } => {
for arg in args.iter() {
try_break!(visitor.visit_expression(arg));
}
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::SimplePropertyAccess { field } => visitor.visit_property_access_field_mut(field),
Self::PrivatePropertyAccess { field } => visitor.visit_private_name_mut(field),
Self::Call { args } => {
for arg in args.iter_mut() {
try_break!(visitor.visit_expression_mut(arg));
}
ControlFlow::Continue(())
}
}
}
}
/// Operation within an [`Optional`] chain.
///
/// An operation within an `Optional` chain can be either shorted or non-shorted. A shorted operation
/// (`?.item`) will force the expression to return `undefined` if the target is `undefined` or `null`.
/// In contrast, a non-shorted operation (`.prop`) will try to access the property, even if the target
/// is `undefined` or `null`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct OptionalOperation {
kind: OptionalOperationKind,
shorted: bool,
}
impl OptionalOperation {
/// Creates a new `OptionalOperation`.
#[inline]
#[must_use]
pub const fn new(kind: OptionalOperationKind, shorted: bool) -> Self {
Self { kind, shorted }
}
/// Gets the kind of operation.
#[inline]
#[must_use]
pub const fn kind(&self) -> &OptionalOperationKind {
&self.kind
}
/// Returns `true` if the operation short-circuits the [`Optional`] chain when the target is
/// `undefined` or `null`.
#[inline]
#[must_use]
pub const fn shorted(&self) -> bool {
self.shorted
}
}
impl ToInternedString for OptionalOperation {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = if self.shorted {
String::from("?.")
} else {
if let OptionalOperationKind::SimplePropertyAccess {
field: PropertyAccessField::Const(name),
} = &self.kind
{
return format!(".{}", interner.resolve_expect(*name));
}
if let OptionalOperationKind::PrivatePropertyAccess { field } = &self.kind {
return format!(".#{}", interner.resolve_expect(field.description()));
}
String::new()
};
buf.push_str(&match &self.kind {
OptionalOperationKind::SimplePropertyAccess { field } => match field {
PropertyAccessField::Const(name) => interner.resolve_expect(*name).to_string(),
PropertyAccessField::Expr(expr) => {
format!("[{}]", expr.to_interned_string(interner))
}
},
OptionalOperationKind::PrivatePropertyAccess { field } => {
format!("#{}", interner.resolve_expect(field.description()))
}
OptionalOperationKind::Call { args } => format!("({})", join_nodes(interner, args)),
});
buf
}
}
impl VisitWith for OptionalOperation {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_optional_operation_kind(&self.kind)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_optional_operation_kind_mut(&mut self.kind)
}
}
/// An optional chain expression, as defined by the [spec].
///
/// [Optional chaining][mdn] allows for short-circuiting property accesses and function calls, which
/// will return `undefined` instead of returning an error if the access target or the call is
/// either `undefined` or `null`.
///
/// An example of optional chaining:
///
/// ```Javascript
/// const adventurer = {
/// name: 'Alice',
/// cat: {
/// name: 'Dinah'
/// }
/// };
///
/// console.log(adventurer.cat?.name); // Dinah
/// console.log(adventurer.dog?.name); // undefined
/// ```
///
/// [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-OptionalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Optional {
target: Box<Expression>,
chain: Box<[OptionalOperation]>,
}
impl VisitWith for Optional {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
for op in self.chain.iter() {
try_break!(visitor.visit_optional_operation(op));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
for op in self.chain.iter_mut() {
try_break!(visitor.visit_optional_operation_mut(op));
}
ControlFlow::Continue(())
}
}
impl Optional {
/// Creates a new `Optional` expression.
#[inline]
#[must_use]
pub fn new(target: Expression, chain: Box<[OptionalOperation]>) -> Self {
Self {
target: Box::new(target),
chain,
}
}
/// Gets the target of this `Optional` expression.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
self.target.as_ref()
}
/// Gets the chain of accesses and calls that will be applied to the target at runtime.
#[inline]
#[must_use]
pub fn chain(&self) -> &[OptionalOperation] {
self.chain.as_ref()
}
}
impl From<Optional> for Expression {
fn from(opt: Optional) -> Self {
Self::Optional(opt)
}
}
impl ToInternedString for Optional {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = self.target.to_interned_string(interner);
for item in &*self.chain {
buf.push_str(&item.to_interned_string(interner));
}
buf
}
}

View File

@@ -0,0 +1,78 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use super::Expression;
/// The `spread` operator allows an iterable such as an array expression or string to be
/// expanded.
///
/// Syntax: `...x`
///
/// It expands array expressions or strings in places where zero or more arguments (for
/// function calls) or elements (for array literals)
/// are expected, or an object expression to be expanded in places where zero or more key-value
/// pairs (for object literals) are expected.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-SpreadElement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Spread {
target: Box<Expression>,
}
impl Spread {
/// Gets the target expression to be expanded by the spread operator.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Creates a `Spread` AST Expression.
#[inline]
#[must_use]
pub fn new(target: Expression) -> Self {
Self {
target: Box::new(target),
}
}
}
impl ToInternedString for Spread {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("...{}", self.target().to_interned_string(interner))
}
}
impl From<Spread> for Expression {
#[inline]
fn from(spread: Spread) -> Self {
Self::Spread(spread)
}
}
impl VisitWith for Spread {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,132 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use super::Expression;
/// A [`TaggedTemplate`][moz] expression, as defined by the [spec].
///
/// `TaggedTemplate`s are a type of template literals that are parsed by a custom function to generate
/// arbitrary objects from the inner strings and expressions.
///
/// [moz]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
/// [spec]: https://tc39.es/ecma262/#sec-tagged-templates
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct TaggedTemplate {
tag: Box<Expression>,
raws: Box<[Sym]>,
cookeds: Box<[Option<Sym>]>,
exprs: Box<[Expression]>,
}
impl TaggedTemplate {
/// Creates a new tagged template with a tag, the list of raw strings, the cooked strings and
/// the expressions.
#[inline]
#[must_use]
pub fn new(
tag: Expression,
raws: Box<[Sym]>,
cookeds: Box<[Option<Sym>]>,
exprs: Box<[Expression]>,
) -> Self {
Self {
tag: tag.into(),
raws,
cookeds,
exprs,
}
}
/// Gets the tag function of the template.
#[inline]
#[must_use]
pub const fn tag(&self) -> &Expression {
&self.tag
}
/// Gets the inner raw strings of the template.
#[inline]
#[must_use]
pub const fn raws(&self) -> &[Sym] {
&self.raws
}
/// Gets the cooked strings of the template.
#[inline]
#[must_use]
pub const fn cookeds(&self) -> &[Option<Sym>] {
&self.cookeds
}
/// Gets the interpolated expressions of the template.
#[inline]
#[must_use]
pub const fn exprs(&self) -> &[Expression] {
&self.exprs
}
}
impl ToInternedString for TaggedTemplate {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = format!("{}`", self.tag.to_interned_string(interner));
for (&raw, expr) in self.raws.iter().zip(self.exprs.iter()) {
buf.push_str(&format!(
"{}${{{}}}",
interner.resolve_expect(raw),
expr.to_interned_string(interner)
));
}
buf.push('`');
buf
}
}
impl From<TaggedTemplate> for Expression {
#[inline]
fn from(template: TaggedTemplate) -> Self {
Self::TaggedTemplate(template)
}
}
impl VisitWith for TaggedTemplate {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.tag));
for raw in self.raws.iter() {
try_break!(visitor.visit_sym(raw));
}
for cooked in self.cookeds.iter().flatten() {
try_break!(visitor.visit_sym(cooked));
}
for expr in self.exprs.iter() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.tag));
for raw in self.raws.iter_mut() {
try_break!(visitor.visit_sym_mut(raw));
}
for cooked in self.cookeds.iter_mut().flatten() {
try_break!(visitor.visit_sym_mut(cooked));
}
for expr in self.exprs.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,90 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use super::Expression;
/// The `yield` keyword is used to pause and resume a generator function
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Yield {
target: Option<Box<Expression>>,
delegate: bool,
}
impl Yield {
/// Gets the target expression of this `Yield` statement.
#[inline]
pub fn target(&self) -> Option<&Expression> {
self.target.as_ref().map(Box::as_ref)
}
/// Returns `true` if this `Yield` statement delegates to another generator or iterable object.
#[inline]
#[must_use]
pub const fn delegate(&self) -> bool {
self.delegate
}
/// Creates a `Yield` AST Expression.
#[inline]
#[must_use]
pub fn new(expr: Option<Expression>, delegate: bool) -> Self {
Self {
target: expr.map(Box::new),
delegate,
}
}
}
impl From<Yield> for Expression {
#[inline]
fn from(r#yield: Yield) -> Self {
Self::Yield(r#yield)
}
}
impl ToInternedString for Yield {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let y = if self.delegate { "yield*" } else { "yield" };
if let Some(ex) = self.target() {
format!("{y} {}", ex.to_interned_string(interner))
} else {
y.to_owned()
}
}
}
impl VisitWith for Yield {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(expr) = &self.target {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(expr) = &mut self.target {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,118 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
/// An arrow function expression, as defined by the [spec].
///
/// An [arrow function][mdn] expression is a syntactically compact alternative to a regular function
/// expression. Arrow function expressions are ill suited as methods, and they cannot be used as
/// constructors. Arrow functions cannot be used as constructors and will throw an error when
/// used with new.
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrowFunction {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
}
impl ArrowFunction {
/// Creates a new `ArrowFunctionDecl` AST Expression.
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
params: FormalParameterList,
body: StatementList,
) -> Self {
Self {
name,
parameters: params,
body,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Sets the name of the function declaration.
#[inline]
pub fn set_name(&mut self, name: Option<Identifier>) {
self.name = name;
}
/// Gets the list of parameters of the arrow function.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the arrow function.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
}
impl ToIndentedString for ArrowFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!("({}", join_nodes(interner, self.parameters.as_ref()));
if self.body().statements().is_empty() {
buf.push_str(") => {}");
} else {
buf.push_str(&format!(
") => {{\n{}{}}}",
self.body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
));
}
buf
}
}
impl From<ArrowFunction> for Expression {
fn from(decl: ArrowFunction) -> Self {
Self::ArrowFunction(decl)
}
}
impl VisitWith for ArrowFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,118 @@
use std::ops::ControlFlow;
use super::FormalParameterList;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
/// An async arrow function expression, as defined by the [spec].
///
/// An [async arrow function][mdn] expression is a syntactically compact alternative to a regular function
/// expression. Arrow function expressions are ill suited as methods, and they cannot be used as
/// constructors. Arrow functions cannot be used as constructors and will throw an error when
/// used with new.
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncArrowFunction
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncArrowFunction {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
}
impl AsyncArrowFunction {
/// Creates a new `AsyncArrowFunction` AST Expression.
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
) -> Self {
Self {
name,
parameters,
body,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Sets the name of the function declaration.
#[inline]
pub fn set_name(&mut self, name: Option<Identifier>) {
self.name = name;
}
/// Gets the list of parameters of the arrow function.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the arrow function.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
}
impl ToIndentedString for AsyncArrowFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!("async ({}", join_nodes(interner, self.parameters.as_ref()));
if self.body().statements().is_empty() {
buf.push_str(") => {}");
} else {
buf.push_str(&format!(
") => {{\n{}{}}}",
self.body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
));
}
buf
}
}
impl From<AsyncArrowFunction> for Expression {
fn from(decl: AsyncArrowFunction) -> Self {
Self::AsyncArrowFunction(decl)
}
}
impl VisitWith for AsyncArrowFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,138 @@
//! Async Function Expression.
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
/// An async function definition, as defined by the [spec].
///
/// An [async function][mdn] is a function where await expressions are allowed within it.
/// The async and await keywords enable asynchronous programming on Javascript without the use
/// of promise chains.
///
/// [spec]: https://tc39.es/ecma262/#sec-async-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncFunction {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl AsyncFunction {
/// Creates a new function expression
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the function declaration.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for AsyncFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "async function".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}",
join_nodes(interner, self.parameters.as_ref())
));
if self.body().statements().is_empty() {
buf.push_str(") {}");
} else {
buf.push_str(&format!(
") {{\n{}{}}}",
self.body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
));
}
buf
}
}
impl From<AsyncFunction> for Expression {
#[inline]
fn from(expr: AsyncFunction) -> Self {
Self::AsyncFunction(expr)
}
}
impl From<AsyncFunction> for Declaration {
#[inline]
fn from(f: AsyncFunction) -> Self {
Self::AsyncFunction(f)
}
}
impl VisitWith for AsyncFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,130 @@
//! Async Generator Expression
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
/// An async generator definition, as defined by the [spec].
///
/// An [async generator][mdn] combines async functions with generators, making it possible to use
/// `await` and `yield` expressions within the definition of the function.
///
/// [spec]: https://tc39.es/ecma262/#sec-async-generator-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncGenerator {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl AsyncGenerator {
/// Creates a new async generator expression
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the async generator expression
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the async generator expression
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the async generator expression
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for AsyncGenerator {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "async function*".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
buf
}
}
impl From<AsyncGenerator> for Expression {
#[inline]
fn from(expr: AsyncGenerator) -> Self {
Self::AsyncGenerator(expr)
}
}
impl From<AsyncGenerator> for Declaration {
#[inline]
fn from(f: AsyncGenerator) -> Self {
Self::AsyncGenerator(f)
}
}
impl VisitWith for AsyncGenerator {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,579 @@
use super::Function;
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes,
property::{MethodDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Declaration, StatementList, ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use std::borrow::Cow;
use std::hash::Hash;
/// A class declaration, as defined by the [spec].
///
/// A [class][mdn] declaration defines a class with the specified methods, fields, and optional constructor.
/// Classes can be used to create objects, which can also be created through literals (using `{}`).
///
/// [spec]: https://tc39.es/ecma262/#sec-class-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Class {
name: Option<Identifier>,
super_ref: Option<Expression>,
pub(crate) constructor: Option<Function>,
pub(crate) elements: Box<[ClassElement]>,
has_binding_identifier: bool,
}
impl Class {
/// Creates a new class declaration.
#[inline]
#[must_use]
pub fn new(
name: Option<Identifier>,
super_ref: Option<Expression>,
constructor: Option<Function>,
elements: Box<[ClassElement]>,
has_binding_identifier: bool,
) -> Self {
Self {
name,
super_ref,
constructor,
elements,
has_binding_identifier,
}
}
/// Returns the name of the class.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Returns the super class ref of the class.
#[inline]
#[must_use]
pub const fn super_ref(&self) -> Option<&Expression> {
self.super_ref.as_ref()
}
/// Returns the constructor of the class.
#[inline]
#[must_use]
pub const fn constructor(&self) -> Option<&Function> {
self.constructor.as_ref()
}
/// Gets the list of all fields defined on the class.
#[inline]
#[must_use]
pub const fn elements(&self) -> &[ClassElement] {
&self.elements
}
/// Returns whether the class has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for Class {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let class_name = self.name.map_or(Cow::Borrowed(""), |s| {
interner.resolve_expect(s.sym()).join(
Cow::Borrowed,
|utf16| Cow::Owned(utf16.to_string_escaped()),
true,
)
});
if self.elements.is_empty() && self.constructor().is_none() {
return format!(
"class {class_name}{} {{}}",
self.super_ref
.as_ref()
.map_or_else(String::new, |sup| format!(
" extends {}",
sup.to_interned_string(interner)
))
);
}
let indentation = " ".repeat(indent_n + 1);
let mut buf = format!(
"class {class_name}{} {{\n",
self.super_ref
.as_ref()
.map_or_else(String::new, |sup| format!(
"extends {}",
sup.to_interned_string(interner)
))
);
if let Some(expr) = &self.constructor {
buf.push_str(&format!(
"{indentation}constructor({}) {}\n",
join_nodes(interner, expr.parameters().as_ref()),
block_to_string(expr.body(), interner, indent_n + 1)
));
}
for element in self.elements.iter() {
buf.push_str(&match element {
ClassElement::MethodDefinition(name, method) => {
format!(
"{indentation}{}{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
name.to_interned_string(interner),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::StaticMethodDefinition(name, method) => {
format!(
"{indentation}static {}{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
name.to_interned_string(interner),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::FieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}{} = {};\n",
name.to_interned_string(interner),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!("{indentation}{};\n", name.to_interned_string(interner),)
}
},
ClassElement::StaticFieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}static {} = {};\n",
name.to_interned_string(interner),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}static {};\n",
name.to_interned_string(interner),
)
}
},
ClassElement::PrivateMethodDefinition(name, method) => {
format!(
"{indentation}{}#{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
interner.resolve_expect(name.description()),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::PrivateStaticMethodDefinition(name, method) => {
format!(
"{indentation}static {}#{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
interner.resolve_expect(name.description()),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::PrivateFieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}#{} = {};\n",
interner.resolve_expect(name.description()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}#{};\n",
interner.resolve_expect(name.description()),
)
}
},
ClassElement::PrivateStaticFieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}static #{} = {};\n",
interner.resolve_expect(name.description()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}static #{};\n",
interner.resolve_expect(name.description()),
)
}
},
ClassElement::StaticBlock(statement_list) => {
format!(
"{indentation}static {}\n",
block_to_string(statement_list, interner, indent_n + 1)
)
}
});
}
buf.push('}');
buf
}
}
impl From<Class> for Expression {
fn from(expr: Class) -> Self {
Self::Class(Box::new(expr))
}
}
impl From<Class> for Declaration {
fn from(f: Class) -> Self {
Self::Class(f)
}
}
impl VisitWith for Class {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
if let Some(expr) = &self.super_ref {
try_break!(visitor.visit_expression(expr));
}
if let Some(func) = &self.constructor {
try_break!(visitor.visit_function(func));
}
for elem in self.elements.iter() {
try_break!(visitor.visit_class_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
if let Some(expr) = &mut self.super_ref {
try_break!(visitor.visit_expression_mut(expr));
}
if let Some(func) = &mut self.constructor {
try_break!(visitor.visit_function_mut(func));
}
for elem in self.elements.iter_mut() {
try_break!(visitor.visit_class_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// An element that can be within a [`Class`], as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ClassElement {
/// A method definition, including `get` and `set` accessors.
MethodDefinition(PropertyName, MethodDefinition),
/// A static method definition, accessible from the class constructor object.
StaticMethodDefinition(PropertyName, MethodDefinition),
/// A field definition.
FieldDefinition(PropertyName, Option<Expression>),
/// A static field definition, accessible from the class constructor object
StaticFieldDefinition(PropertyName, Option<Expression>),
/// A private method definition, only accessible inside the class declaration.
PrivateMethodDefinition(PrivateName, MethodDefinition),
/// A private static method definition, only accessible from static methods and fields inside
/// the class declaration.
PrivateStaticMethodDefinition(PrivateName, MethodDefinition),
/// A private field definition, only accessible inside the class declaration.
PrivateFieldDefinition(PrivateName, Option<Expression>),
/// A private static field definition, only accessible from static methods and fields inside the
/// class declaration.
PrivateStaticFieldDefinition(PrivateName, Option<Expression>),
/// A static block, where a class can have initialization logic for its static fields.
StaticBlock(StatementList),
}
impl VisitWith for ClassElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::MethodDefinition(pn, md) | Self::StaticMethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_method_definition(md)
}
Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => {
try_break!(visitor.visit_property_name(pn));
if let Some(expr) = maybe_expr {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PrivateMethodDefinition(name, md)
| Self::PrivateStaticMethodDefinition(name, md) => {
try_break!(visitor.visit_private_name(name));
visitor.visit_method_definition(md)
}
Self::PrivateFieldDefinition(name, maybe_expr)
| Self::PrivateStaticFieldDefinition(name, maybe_expr) => {
try_break!(visitor.visit_private_name(name));
if let Some(expr) = maybe_expr {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::StaticBlock(sl) => visitor.visit_statement_list(sl),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::MethodDefinition(pn, md) | Self::StaticMethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_method_definition_mut(md)
}
Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => {
try_break!(visitor.visit_property_name_mut(pn));
if let Some(expr) = maybe_expr {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PrivateMethodDefinition(name, md)
| Self::PrivateStaticMethodDefinition(name, md) => {
try_break!(visitor.visit_private_name_mut(name));
visitor.visit_method_definition_mut(md)
}
Self::PrivateFieldDefinition(name, maybe_expr)
| Self::PrivateStaticFieldDefinition(name, maybe_expr) => {
try_break!(visitor.visit_private_name_mut(name));
if let Some(expr) = maybe_expr {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::StaticBlock(sl) => visitor.visit_statement_list_mut(sl),
}
}
}
/// A private name as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-private-names
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PrivateName {
/// The `[[Description]]` internal slot of the private name.
description: Sym,
/// The indices of the private name are included to ensure that private names are unique.
pub(crate) indices: (usize, usize),
}
impl PrivateName {
/// Create a new private name.
#[inline]
#[must_use]
pub const fn new(description: Sym) -> Self {
Self {
description,
indices: (0, 0),
}
}
/// Get the description of the private name.
#[inline]
#[must_use]
pub const fn description(&self) -> Sym {
self.description
}
}
impl VisitWith for PrivateName {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_sym(&self.description)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_sym_mut(&mut self.description)
}
}

View File

@@ -0,0 +1,132 @@
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use core::ops::ControlFlow;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString};
use super::FormalParameterList;
/// A generator definition, as defined by the [spec].
///
/// [Generators][mdn] are "resumable functions", which can be suspended during execution and
/// resumed at any later time. The main feature of a generator are `yield` expressions, which
/// specifies the value returned when a generator is suspended, and the point from which
/// the execution will resume.
///
/// [spec]: https://tc39.es/ecma262/#sec-generator-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Generator {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl Generator {
/// Creates a new generator expression
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the generator declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the generator declaration.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the generator declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for Generator {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "function*".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
buf
}
}
impl From<Generator> for Expression {
#[inline]
fn from(expr: Generator) -> Self {
Self::Generator(expr)
}
}
impl From<Generator> for Declaration {
#[inline]
fn from(f: Generator) -> Self {
Self::Generator(f)
}
}
impl VisitWith for Generator {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,184 @@
//! Functions and classes nodes, as defined by the [spec].
//!
//! [Functions][func] are mainly subprograms that can be called by external code to execute a sequence of
//! statements (the *body* of the function). Javascript functions fall in several categories:
//!
//! - [`Function`]s.
//! - [`ArrowFunction`]s.
//! - [`AsyncArrowFunction`]s.
//! - [`Generator`]s.
//! - [`AsyncFunction`]s.
//! - [`AsyncGenerator`]s.
//!
//! All of them can be declared in either [declaration][decl] form or [expression][expr] form,
//! except from `ArrowFunction`s and `AsyncArrowFunction`s, which can only be declared in expression form.
//!
//! This module also contains [`Class`]es, which are templates for creating objects. Classes
//! can also be declared in either declaration or expression form.
//!
//! [spec]: https://tc39.es/ecma262/#sec-ecmascript-language-functions-and-classes
//! [func]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
//! [decl]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
//! [expr]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function
mod arrow_function;
mod async_arrow_function;
mod async_function;
mod async_generator;
mod class;
mod generator;
mod parameters;
pub use arrow_function::ArrowFunction;
pub use async_arrow_function::AsyncArrowFunction;
pub use async_function::AsyncFunction;
pub use async_generator::AsyncGenerator;
pub use class::{Class, ClassElement, PrivateName};
use core::ops::ControlFlow;
pub use generator::Generator;
pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{block_to_string, join_nodes, StatementList};
use boa_interner::{Interner, ToIndentedString};
use super::expression::{Expression, Identifier};
use super::Declaration;
/// A function definition, as defined by the [spec].
///
/// By default, functions return `undefined`. To return any other value, the function must have
/// a return statement that specifies the value to return.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Function {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl Function {
/// Creates a new function expression.
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier: false,
}
}
/// Creates a new function expression with an expression binding identifier.
#[inline]
#[must_use]
pub const fn new_with_binding_identifier(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the function declaration.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for Function {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "function".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
buf
}
}
impl From<Function> for Expression {
#[inline]
fn from(expr: Function) -> Self {
Self::Function(expr)
}
}
impl From<Function> for Declaration {
#[inline]
fn from(f: Function) -> Self {
Self::Function(f)
}
}
impl VisitWith for Function {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,287 @@
use crate::{
declaration::{Binding, Variable},
expression::Expression,
operations::bound_names,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use bitflags::bitflags;
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use rustc_hash::FxHashSet;
/// A list of `FormalParameter`s that describes the parameters of a function, as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FormalParameterList {
parameters: Box<[FormalParameter]>,
flags: FormalParameterListFlags,
length: u32,
}
impl FormalParameterList {
/// Creates a new empty formal parameter list.
#[must_use]
pub fn new() -> Self {
Self {
parameters: Box::new([]),
flags: FormalParameterListFlags::default(),
length: 0,
}
}
/// Creates a `FormalParameterList` from a list of [`FormalParameter`]s.
#[must_use]
pub fn from_parameters(parameters: Vec<FormalParameter>) -> Self {
let mut flags = FormalParameterListFlags::default();
let mut length = 0;
let mut names = FxHashSet::default();
for parameter in &parameters {
let parameter_names = bound_names(parameter);
for name in parameter_names {
if name == Sym::ARGUMENTS {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
if names.contains(&name) {
flags |= FormalParameterListFlags::HAS_DUPLICATES;
} else {
names.insert(name);
}
}
if parameter.is_rest_param() {
flags |= FormalParameterListFlags::HAS_REST_PARAMETER;
}
if parameter.init().is_some() {
flags |= FormalParameterListFlags::HAS_EXPRESSIONS;
}
if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier()
{
flags.remove(FormalParameterListFlags::IS_SIMPLE);
}
if !(flags.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
|| parameter.is_rest_param()
|| parameter.init().is_some())
{
length += 1;
}
}
Self {
parameters: parameters.into(),
flags,
length,
}
}
/// Returns the length of the parameter list.
/// Note that this is not equal to the length of the parameters slice.
#[must_use]
pub const fn length(&self) -> u32 {
self.length
}
/// Returns the parameter list flags.
#[must_use]
pub const fn flags(&self) -> FormalParameterListFlags {
self.flags
}
/// Indicates if the parameter list is simple.
#[must_use]
pub const fn is_simple(&self) -> bool {
self.flags.contains(FormalParameterListFlags::IS_SIMPLE)
}
/// Indicates if the parameter list has duplicate parameters.
#[must_use]
pub const fn has_duplicates(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_DUPLICATES)
}
/// Indicates if the parameter list has a rest parameter.
#[must_use]
pub const fn has_rest_parameter(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_REST_PARAMETER)
}
/// Indicates if the parameter list has expressions in it's parameters.
#[must_use]
pub const fn has_expressions(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
}
/// Indicates if the parameter list has parameters named 'arguments'.
#[must_use]
pub const fn has_arguments(&self) -> bool {
self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS)
}
}
impl From<Vec<FormalParameter>> for FormalParameterList {
fn from(parameters: Vec<FormalParameter>) -> Self {
Self::from_parameters(parameters)
}
}
impl From<FormalParameter> for FormalParameterList {
fn from(parameter: FormalParameter) -> Self {
Self::from_parameters(vec![parameter])
}
}
impl AsRef<[FormalParameter]> for FormalParameterList {
fn as_ref(&self) -> &[FormalParameter] {
&self.parameters
}
}
impl VisitWith for FormalParameterList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for parameter in self.parameters.iter() {
try_break!(visitor.visit_formal_parameter(parameter));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for parameter in self.parameters.iter_mut() {
try_break!(visitor.visit_formal_parameter_mut(parameter));
}
// TODO recompute flags
ControlFlow::Continue(())
}
}
#[cfg(feature = "fuzz")]
impl<'a> arbitrary::Arbitrary<'a> for FormalParameterList {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let params: Vec<FormalParameter> = u.arbitrary()?;
Ok(Self::from(params))
}
}
bitflags! {
/// Flags for a [`FormalParameterList`].
#[allow(clippy::unsafe_derive_deserialize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FormalParameterListFlags: u8 {
/// Has only identifier parameters with no initialization expressions.
const IS_SIMPLE = 0b0000_0001;
/// Has any duplicate parameters.
const HAS_DUPLICATES = 0b0000_0010;
/// Has a rest parameter.
const HAS_REST_PARAMETER = 0b0000_0100;
/// Has any initialization expression.
const HAS_EXPRESSIONS = 0b0000_1000;
/// Has an argument with the name `arguments`.
const HAS_ARGUMENTS = 0b0001_0000;
}
}
impl Default for FormalParameterListFlags {
fn default() -> Self {
Self::empty().union(Self::IS_SIMPLE)
}
}
/// "Formal parameter" is a fancy way of saying "function parameter".
///
/// In the declaration of a function, the parameters must be identifiers,
/// not any value like numbers, strings, or objects.
/// ```text
/// function foo(formalParameter1, formalParameter2) {
/// }
/// ```
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_formal_parameter
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct FormalParameter {
variable: Variable,
is_rest_param: bool,
}
impl FormalParameter {
/// Creates a new formal parameter.
pub fn new<D>(variable: D, is_rest_param: bool) -> Self
where
D: Into<Variable>,
{
Self {
variable: variable.into(),
is_rest_param,
}
}
/// Gets the variable of the formal parameter
#[must_use]
pub const fn variable(&self) -> &Variable {
&self.variable
}
/// Gets the initialization node of the formal parameter, if any.
#[must_use]
pub const fn init(&self) -> Option<&Expression> {
self.variable.init()
}
/// Returns `true` if the parameter is a rest parameter.
#[must_use]
pub const fn is_rest_param(&self) -> bool {
self.is_rest_param
}
/// Returns `true` if the parameter is an identifier.
#[must_use]
pub const fn is_identifier(&self) -> bool {
matches!(&self.variable.binding(), Binding::Identifier(_))
}
}
impl ToInternedString for FormalParameter {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = if self.is_rest_param {
"...".to_owned()
} else {
String::new()
};
buf.push_str(&self.variable.to_interned_string(interner));
buf
}
}
impl VisitWith for FormalParameter {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable(&self.variable)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_mut(&mut self.variable)
}
}

View File

@@ -0,0 +1,625 @@
//! The `Keyword` AST node, which represents reserved words of the ECMAScript language.
//!
//! The [specification][spec] defines keywords as tokens that match an `IdentifierName`, but also
//! have special meaning in ECMAScript. In ECMAScript, you cannot use these reserved words as variables,
//! labels, or function names.
//!
//! The [MDN documentation][mdn] contains a more extensive explanation about keywords.
//!
//! [spec]: https://tc39.es/ecma262/#sec-keywords-and-reserved-words
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
use crate::expression::operator::binary::{BinaryOp, RelationalOp};
use boa_interner::{Interner, Sym};
use boa_macros::utf16;
use std::{convert::TryFrom, error, fmt, str::FromStr};
/// List of keywords recognized by the JavaScript grammar.
///
/// See the [module-level documentation][self] for more details.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Keyword {
/// The `await` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AwaitExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
Await,
/// The `async` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Async,
/// The `break` keyword.
///
/// More information:
/// - [break `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BreakStatement
/// [node]: ../node/enum.Node.html#variant.Break
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
Break,
/// The `case` keyword.
///
/// More information:
/// - [switch `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CaseClause
/// [node]: ../node/enum.Node.html#variant.Switch
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
Case,
/// The `catch` keyword.
///
/// More information:
/// - [try `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-Catch
/// [node]: ../node/enum.Node.html#variant.Try
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Catch,
/// The `class` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class
Class,
/// The `continue` keyword.
///
/// More information:
/// - [continue `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement
/// [node]: ../node/enum.Node.html#variant.Continue
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
Continue,
/// The `const` keyword.
///
/// More information:
/// - [const `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
/// [node]: ../node/enum.Node.html#variant.ConstDecl
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
Const,
/// The `debugger` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-debugger-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger
Debugger,
/// The `default` keyword.
///
/// More information:
/// - [switch `Node` documentation][node]
/// - [ECMAScript reference default clause][spec-clause]
/// - [ECMAScript reference default export][spec-export]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Switch
/// [spec-clause]: https://tc39.es/ecma262/#prod-DefaultClause
/// [spec-export]: https://tc39.es/ecma262/#prod-ImportedDefaultBinding
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/default
Default,
/// The `delete` keyword.
///
/// More information:
/// - [delete `UnaryOp` documentation][unary]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-delete-operator
/// [unary]: ../op/enum.UnaryOp.html#variant.Delete
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
Delete,
/// The `do` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
Do,
/// The `else` keyword.
///
/// More information:
/// - [if `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.If
/// [spec]: https://tc39.es/ecma262/#prod-IfStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
Else,
/// The `enum` keyword.
///
/// Future reserved keyword.
Enum,
/// The `export` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-exports
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
Export,
/// The `extends` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassHeritage
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends
Extends,
/// The `false` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BooleanLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
False,
/// The `finally` keyword.
///
/// More information:
/// - [try `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Try
/// [spec]: https://tc39.es/ecma262/#prod-Finally
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Finally,
/// The `for` keyword.
///
/// More information:
/// - [for loop `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.ForLoop
/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
For,
/// The `function` keyword.
///
/// More information:
/// - [function `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.FunctionDecl
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
Function,
/// The `if` keyword.
///
/// More information:
/// - [if `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.If
/// [spec]: https://tc39.es/ecma262/#prod-IfStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
If,
/// The `in` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in
In,
/// The `instanceof` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-instanceofoperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
InstanceOf,
/// The `import` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-imports
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Import,
/// The `let` keyword.
///
/// More information:
/// - [let `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.LetDecl
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Let,
/// The `new` keyword.
///
/// More information:
/// - [new `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.New
/// [spec]: https://tc39.es/ecma262/#prod-NewExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
New,
/// The `null` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-NullLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null
Null,
/// The `of` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
Of,
/// The `return` keyword
///
/// More information:
/// - [return `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Return
/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
Return,
/// The `super` keyword
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-super-keyword
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
Super,
/// The `switch` keyword.
///
/// More information:
/// - [switch `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Switch
/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
Switch,
/// The `this` keyword.
///
/// More information:
/// - [this `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.This
/// [spec]: https://tc39.es/ecma262/#sec-this-keyword
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
This,
/// The `throw` keyword.
///
/// More information:
/// - [throw `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Throw
/// [spec]: https://tc39.es/ecma262/#sec-throw-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
Throw,
/// The `true` keyword
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BooleanLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
True,
/// The `try` keyword.
///
/// More information:
/// - [try `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Try
/// [spec]: https://tc39.es/ecma262/#prod-TryStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Try,
/// The `typeof` keyword.
///
/// More information:
/// - [typeof `UnaryOp` documentation][unary]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [unary]: ../op/enum.UnaryOp.html#variant.TypeOf
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
TypeOf,
/// The `var` keyword.
///
/// More information:
/// - [var `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.VarDecl
/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
Var,
/// The `void` keyword.
///
/// More information:
/// - [void `UnaryOp` documentation][unary]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [unary]: ../op/enum.UnaryOp.html#variant.Void
/// [spec]: https://tc39.es/ecma262/#sec-void-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
Void,
/// The `while` keyword.
///
/// More information:
/// - [while `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.While
/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while
While,
/// The `with` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-WithStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
With,
/// The 'yield' keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
Yield,
}
impl Keyword {
/// Gets the keyword as a binary operation, if this keyword is the `in` or the `instanceof`
/// keywords.
#[must_use]
pub const fn as_binary_op(self) -> Option<BinaryOp> {
match self {
Self::In => Some(BinaryOp::Relational(RelationalOp::In)),
Self::InstanceOf => Some(BinaryOp::Relational(RelationalOp::InstanceOf)),
_ => None,
}
}
/// Gets the keyword as a tuple of strings.
#[must_use]
pub const fn as_str(self) -> (&'static str, &'static [u16]) {
match self {
Self::Await => ("await", utf16!("await")),
Self::Async => ("async", utf16!("async")),
Self::Break => ("break", utf16!("break")),
Self::Case => ("case", utf16!("case")),
Self::Catch => ("catch", utf16!("catch")),
Self::Class => ("class", utf16!("class")),
Self::Continue => ("continue", utf16!("continue")),
Self::Const => ("const", utf16!("const")),
Self::Debugger => ("debugger", utf16!("debugger")),
Self::Default => ("default", utf16!("default")),
Self::Delete => ("delete", utf16!("delete")),
Self::Do => ("do", utf16!("do")),
Self::Else => ("else", utf16!("else")),
Self::Enum => ("enum", utf16!("enum")),
Self::Extends => ("extends", utf16!("extends")),
Self::Export => ("export", utf16!("export")),
Self::False => ("false", utf16!("false")),
Self::Finally => ("finally", utf16!("finally")),
Self::For => ("for", utf16!("for")),
Self::Function => ("function", utf16!("function")),
Self::If => ("if", utf16!("if")),
Self::In => ("in", utf16!("in")),
Self::InstanceOf => ("instanceof", utf16!("instanceof")),
Self::Import => ("import", utf16!("import")),
Self::Let => ("let", utf16!("let")),
Self::New => ("new", utf16!("new")),
Self::Null => ("null", utf16!("null")),
Self::Of => ("of", utf16!("of")),
Self::Return => ("return", utf16!("return")),
Self::Super => ("super", utf16!("super")),
Self::Switch => ("switch", utf16!("switch")),
Self::This => ("this", utf16!("this")),
Self::Throw => ("throw", utf16!("throw")),
Self::True => ("true", utf16!("true")),
Self::Try => ("try", utf16!("try")),
Self::TypeOf => ("typeof", utf16!("typeof")),
Self::Var => ("var", utf16!("var")),
Self::Void => ("void", utf16!("void")),
Self::While => ("while", utf16!("while")),
Self::With => ("with", utf16!("with")),
Self::Yield => ("yield", utf16!("yield")),
}
}
// TODO: promote all keywords to statics inside Interner
/// Converts the keyword to a symbol in the given interner.
pub fn to_sym(self, interner: &mut Interner) -> Sym {
let (utf8, utf16) = self.as_str();
interner.get_or_intern_static(utf8, utf16)
}
}
// TODO: Should use a proper Error
impl TryFrom<Keyword> for BinaryOp {
type Error = String;
fn try_from(value: Keyword) -> Result<Self, Self::Error> {
value
.as_binary_op()
.ok_or_else(|| format!("No binary operation for {value}"))
}
}
/// The error type which is returned from parsing a [`str`] into a [`Keyword`].
#[derive(Debug, Clone, Copy)]
pub struct KeywordError;
impl fmt::Display for KeywordError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid token")
}
}
// This is important for other errors to wrap this one.
impl error::Error for KeywordError {
fn description(&self) -> &str {
"invalid token"
}
}
impl FromStr for Keyword {
type Err = KeywordError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"await" => Ok(Self::Await),
"async" => Ok(Self::Async),
"break" => Ok(Self::Break),
"case" => Ok(Self::Case),
"catch" => Ok(Self::Catch),
"class" => Ok(Self::Class),
"continue" => Ok(Self::Continue),
"const" => Ok(Self::Const),
"debugger" => Ok(Self::Debugger),
"default" => Ok(Self::Default),
"delete" => Ok(Self::Delete),
"do" => Ok(Self::Do),
"else" => Ok(Self::Else),
"enum" => Ok(Self::Enum),
"extends" => Ok(Self::Extends),
"export" => Ok(Self::Export),
"false" => Ok(Self::False),
"finally" => Ok(Self::Finally),
"for" => Ok(Self::For),
"function" => Ok(Self::Function),
"if" => Ok(Self::If),
"in" => Ok(Self::In),
"instanceof" => Ok(Self::InstanceOf),
"import" => Ok(Self::Import),
"let" => Ok(Self::Let),
"new" => Ok(Self::New),
"null" => Ok(Self::Null),
"of" => Ok(Self::Of),
"return" => Ok(Self::Return),
"super" => Ok(Self::Super),
"switch" => Ok(Self::Switch),
"this" => Ok(Self::This),
"throw" => Ok(Self::Throw),
"true" => Ok(Self::True),
"try" => Ok(Self::Try),
"typeof" => Ok(Self::TypeOf),
"var" => Ok(Self::Var),
"void" => Ok(Self::Void),
"while" => Ok(Self::While),
"with" => Ok(Self::With),
"yield" => Ok(Self::Yield),
_ => Err(KeywordError),
}
}
}
impl fmt::Display for Keyword {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str().0, f)
}
}

View File

@@ -0,0 +1,174 @@
//! Boa's **`boa_ast`** crate implements an ECMAScript abstract syntax tree.
//!
//! # Crate Overview
//! **`boa_ast`** contains representations of [**Parse Nodes**][grammar] as defined by the ECMAScript
//! spec. Some `Parse Node`s are not represented by Boa's AST, because a lot of grammar productions
//! are only used to throw [**Early Errors**][early], and don't influence the evaluation of the AST
//! itself.
//!
//! Boa's AST is mainly split in three main components: [`Declaration`]s, [`Expression`]s and
//! [`Statement`]s, with [`StatementList`] being the primordial Parse Node that combines
//! all of them to create a proper AST.
//!
//! # About Boa
//! Boa is an open-source, experimental ECMAScript Engine written in Rust for lexing, parsing and executing ECMAScript/JavaScript. Currently, Boa
//! supports some of the [language][boa-conformance]. More information can be viewed at [Boa's website][boa-web].
//!
//! Try out the most recent release with Boa's live demo [playground][boa-playground].
//!
//! # Boa Crates
//! - **`boa_ast`** - Boa's ECMAScript Abstract Syntax Tree.
//! - **`boa_engine`** - Boa's implementation of ECMAScript builtin objects and execution.
//! - **`boa_gc`** - Boa's garbage collector.
//! - **`boa_interner`** - Boa's string interner.
//! - **`boa_parser`** - Boa's lexer and parser.
//! - **`boa_profiler`** - Boa's code profiler.
//! - **`boa_unicode`** - Boa's Unicode identifier.
//! - **`boa_icu_provider`** - Boa's ICU4X data provider.
//!
//! [grammar]: https://tc39.es/ecma262/#sec-syntactic-grammar
//! [early]: https://tc39.es/ecma262/#sec-static-semantic-rules
//! [boa-conformance]: https://boa-dev.github.io/boa/test262/
//! [boa-web]: https://boa-dev.github.io/
//! [boa-playground]: https://boa-dev.github.io/boa/playground/
#![doc(
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg"
)]
#![cfg_attr(not(test), forbid(clippy::unwrap_used))]
#![warn(missing_docs, clippy::dbg_macro)]
#![deny(
// rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html
warnings,
future_incompatible,
let_underscore,
nonstandard_style,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
unused,
// rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
macro_use_extern_crate,
meta_variable_misuse,
missing_abi,
missing_copy_implementations,
missing_debug_implementations,
non_ascii_idents,
noop_method_call,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_crate_dependencies,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_tuple_struct_fields,
variant_size_differences,
// rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::private_doc_tests,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
// clippy categories https://doc.rust-lang.org/clippy/
clippy::all,
clippy::correctness,
clippy::suspicious,
clippy::style,
clippy::complexity,
clippy::perf,
clippy::pedantic,
clippy::nursery,
)]
#![allow(
clippy::module_name_repetitions,
clippy::too_many_lines,
clippy::option_if_let_else,
clippy::use_self
)]
mod position;
mod punctuator;
mod statement_list;
pub mod declaration;
pub mod expression;
pub mod function;
pub mod keyword;
pub mod operations;
pub mod pattern;
pub mod property;
pub mod statement;
pub mod visitor;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
pub use self::{
declaration::Declaration,
expression::Expression,
keyword::Keyword,
position::{Position, Span},
punctuator::Punctuator,
statement::Statement,
statement_list::{StatementList, StatementListItem},
};
/// Utility to join multiple Nodes into a single string.
fn join_nodes<N>(interner: &Interner, nodes: &[N]) -> String
where
N: ToInternedString,
{
let mut first = true;
let mut buf = String::new();
for e in nodes {
if first {
first = false;
} else {
buf.push_str(", ");
}
buf.push_str(&e.to_interned_string(interner));
}
buf
}
/// Displays the body of a block or statement list.
///
/// This includes the curly braces at the start and end. This will not indent the first brace,
/// but will indent the last brace.
fn block_to_string(body: &StatementList, interner: &Interner, indentation: usize) -> String {
if body.statements().is_empty() {
"{}".to_owned()
} else {
format!(
"{{\n{}{}}}",
body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
)
}
}
/// Utility trait that adds a `UTF-16` escaped representation to every [`[u16]`][slice].
trait ToStringEscaped {
/// Decodes `self` as an `UTF-16` encoded string, escaping any unpaired surrogates by its
/// codepoint value.
fn to_string_escaped(&self) -> String;
}
impl ToStringEscaped for [u16] {
fn to_string_escaped(&self) -> String {
char::decode_utf16(self.iter().copied())
.map(|r| match r {
Ok(c) => String::from(c),
Err(e) => format!("\\u{:04X}", e.unpaired_surrogate()),
})
.collect()
}
}

View File

@@ -0,0 +1,769 @@
//! Definitions of various **Syntax-Directed Operations** used in the [spec].
//!
//! [spec]: https://tc39.es/ecma262/#sec-syntax-directed-operations
use core::ops::ControlFlow;
use std::convert::Infallible;
use boa_interner::Sym;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
declaration::VarDeclaration,
expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield},
function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
Function, Generator, PrivateName,
},
property::{MethodDefinition, PropertyDefinition},
statement::LabelledItem,
try_break,
visitor::{NodeRef, VisitWith, Visitor, VisitorMut},
Declaration, Expression, Statement, StatementList, StatementListItem,
};
/// Represents all the possible symbols searched for by the [`Contains`][contains] operation.
///
/// [contains]: https://tc39.es/ecma262/#sec-syntax-directed-operations-contains
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContainsSymbol {
/// A node with the `super` keyword (`super(args)` or `super.prop`).
Super,
/// A super property access (`super.prop`).
SuperProperty,
/// A super constructor call (`super(args)`).
SuperCall,
/// A yield expression (`yield 5`).
YieldExpression,
/// An await expression (`await 4`).
AwaitExpression,
/// The new target expression (`new.target`).
NewTarget,
/// The body of a class definition.
ClassBody,
/// The super class of a class definition.
ClassHeritage,
/// A this expression (`this`).
This,
/// A method definition.
MethodDefinition,
/// The BindingIdentifier "eval" or "arguments".
EvalOrArguments,
}
/// Returns `true` if the node contains the given symbol.
///
/// This is equivalent to the [`Contains`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
#[must_use]
pub fn contains<N>(node: &N, symbol: ContainsSymbol) -> bool
where
N: VisitWith,
{
/// Visitor used by the function to search for a specific symbol in a node.
#[derive(Debug, Clone, Copy)]
struct ContainsVisitor(ContainsSymbol);
impl<'ast> Visitor<'ast> for ContainsVisitor {
type BreakTy = ();
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::EvalOrArguments
&& (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS)
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
}
fn visit_function(&mut self, _: &'ast Function) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
if !node.elements().is_empty() && self.0 == ContainsSymbol::ClassBody {
return ControlFlow::Break(());
}
if node.super_ref().is_some() && self.0 == ContainsSymbol::ClassHeritage {
return ControlFlow::Break(());
}
node.visit_with(self)
}
// `ComputedPropertyContains`: https://tc39.es/ecma262/#sec-static-semantics-computedpropertycontains
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
match node {
ClassElement::MethodDefinition(name, _)
| ClassElement::StaticMethodDefinition(name, _)
| ClassElement::FieldDefinition(name, _)
| ClassElement::StaticFieldDefinition(name, _) => name.visit_with(self),
_ => ControlFlow::Continue(()),
}
}
fn visit_property_definition(
&mut self,
node: &'ast PropertyDefinition,
) -> ControlFlow<Self::BreakTy> {
if let PropertyDefinition::MethodDefinition(name, _) = node {
if self.0 == ContainsSymbol::MethodDefinition {
return ControlFlow::Break(());
}
return name.visit_with(self);
}
node.visit_with(self)
}
fn visit_arrow_function(
&mut self,
node: &'ast ArrowFunction,
) -> ControlFlow<Self::BreakTy> {
if ![
ContainsSymbol::NewTarget,
ContainsSymbol::SuperProperty,
ContainsSymbol::SuperCall,
ContainsSymbol::Super,
ContainsSymbol::This,
]
.contains(&self.0)
{
return ControlFlow::Continue(());
}
node.visit_with(self)
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
if ![
ContainsSymbol::NewTarget,
ContainsSymbol::SuperProperty,
ContainsSymbol::SuperCall,
ContainsSymbol::Super,
ContainsSymbol::This,
]
.contains(&self.0)
{
return ControlFlow::Continue(());
}
node.visit_with(self)
}
fn visit_super_property_access(
&mut self,
node: &'ast SuperPropertyAccess,
) -> ControlFlow<Self::BreakTy> {
if [ContainsSymbol::SuperProperty, ContainsSymbol::Super].contains(&self.0) {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_super_call(&mut self, node: &'ast SuperCall) -> ControlFlow<Self::BreakTy> {
if [ContainsSymbol::SuperCall, ContainsSymbol::Super].contains(&self.0) {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_yield(&mut self, node: &'ast Yield) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::YieldExpression {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_await(&mut self, node: &'ast Await) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::AwaitExpression {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_expression(&mut self, node: &'ast Expression) -> ControlFlow<Self::BreakTy> {
if node == &Expression::This && self.0 == ContainsSymbol::This {
return ControlFlow::Break(());
}
if node == &Expression::NewTarget && self.0 == ContainsSymbol::NewTarget {
return ControlFlow::Break(());
}
node.visit_with(self)
}
}
node.visit_with(&mut ContainsVisitor(symbol)).is_break()
}
/// Returns true if the node contains an identifier reference with name `arguments`.
///
/// This is equivalent to the [`ContainsArguments`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments
#[must_use]
pub fn contains_arguments<N>(node: &N) -> bool
where
N: VisitWith,
{
/// Visitor used by the function to search for an identifier with the name `arguments`.
#[derive(Debug, Clone, Copy)]
struct ContainsArgsVisitor;
impl<'ast> Visitor<'ast> for ContainsArgsVisitor {
type BreakTy = ();
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if node.sym() == Sym::ARGUMENTS {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}
fn visit_function(&mut self, _: &'ast Function) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
match node {
ClassElement::MethodDefinition(name, _)
| ClassElement::StaticMethodDefinition(name, _) => return name.visit_with(self),
_ => {}
}
node.visit_with(self)
}
fn visit_property_definition(
&mut self,
node: &'ast PropertyDefinition,
) -> ControlFlow<Self::BreakTy> {
if let PropertyDefinition::MethodDefinition(name, _) = node {
name.visit_with(self)
} else {
node.visit_with(self)
}
}
}
node.visit_with(&mut ContainsArgsVisitor).is_break()
}
/// Returns `true` if `method` has a super call in its parameters or body.
///
/// This is equivalent to the [`HasDirectSuper`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper
#[must_use]
#[inline]
pub fn has_direct_super(method: &MethodDefinition) -> bool {
match method {
MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => {
contains(f, ContainsSymbol::SuperCall)
}
MethodDefinition::Generator(f) => contains(f, ContainsSymbol::SuperCall),
MethodDefinition::AsyncGenerator(f) => contains(f, ContainsSymbol::SuperCall),
MethodDefinition::Async(f) => contains(f, ContainsSymbol::SuperCall),
}
}
/// A container that [`BoundNamesVisitor`] can use to push the found identifiers.
trait IdentList {
fn add(&mut self, value: Identifier, function: bool);
}
impl IdentList for Vec<Identifier> {
fn add(&mut self, value: Identifier, _function: bool) {
self.push(value);
}
}
impl IdentList for Vec<(Identifier, bool)> {
fn add(&mut self, value: Identifier, function: bool) {
self.push((value, function));
}
}
impl IdentList for FxHashSet<Identifier> {
fn add(&mut self, value: Identifier, _function: bool) {
self.insert(value);
}
}
/// The [`Visitor`] used to obtain the bound names of a node.
#[derive(Debug)]
struct BoundNamesVisitor<'a, T: IdentList>(&'a mut T);
impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> {
type BreakTy = Infallible;
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
self.0.add(*node, false);
ControlFlow::Continue(())
}
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
// TODO: add "*default" for module default functions without name
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, true);
}
ControlFlow::Continue(())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
}
/// Returns a list with the bound names of an AST node, which may contain duplicates.
///
/// This is equivalent to the [`BoundNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames
#[must_use]
pub fn bound_names<'a, N>(node: &'a N) -> Vec<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = Vec::new();
BoundNamesVisitor(&mut names).visit(node.into());
names
}
/// The [`Visitor`] used to obtain the lexically declared names of a node.
#[derive(Debug)]
struct LexicallyDeclaredNamesVisitor<'a, T: IdentList>(&'a mut T);
impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> {
type BreakTy = Infallible;
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
if let Statement::Labelled(labelled) = node {
return self.visit_labelled(labelled);
}
ControlFlow::Continue(())
}
fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_declaration(node)
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f),
LabelledItem::Statement(_) => ControlFlow::Continue(()),
}
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_lexicals(stmts, self.0);
}
ControlFlow::Continue(())
}
// TODO: ScriptBody : StatementList
// 1. Return TopLevelLexicallyDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_lexically_declared_names` directly.
}
/// Returns a list with the lexical bindings of a node, which may contain duplicates.
///
/// This is equivalent to the [`LexicallyDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames
#[must_use]
pub fn lexically_declared_names<'a, N>(node: &'a N) -> Vec<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = Vec::new();
LexicallyDeclaredNamesVisitor(&mut names).visit(node.into());
names
}
/// Returns a list with the lexical bindings of a node, which may contain duplicates.
///
/// If a declared name originates from a function declaration it is flagged as `true` in the returned
/// list. (See [B.3.2.4 Changes to Block Static Semantics: Early Errors])
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames
/// [changes]: https://tc39.es/ecma262/#sec-block-duplicates-allowed-static-semantics
#[must_use]
pub fn lexically_declared_names_legacy<'a, N>(node: &'a N) -> Vec<(Identifier, bool)>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = Vec::new();
LexicallyDeclaredNamesVisitor(&mut names).visit(node.into());
names
}
/// The [`Visitor`] used to obtain the var declared names of a node.
#[derive(Debug)]
struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet<Identifier>);
impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
type BreakTy = Infallible;
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_declaration(&mut self, _: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_var_declaration(node)
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(_) => ControlFlow::Continue(()),
LabelledItem::Statement(stmt) => stmt.visit_with(self),
}
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_vars(stmts, self.0);
}
node.visit_with(self)
}
// TODO: ScriptBody : StatementList
// 1. Return TopLevelVarDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly.
}
/// Returns a set with the var bindings of a node, with no duplicates.
///
/// This is equivalent to the [`VarDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-vardeclarednames
#[must_use]
pub fn var_declared_names<'a, N>(node: &'a N) -> FxHashSet<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = FxHashSet::default();
VarDeclaredNamesVisitor(&mut names).visit(node.into());
names
}
/// Utility function that collects the top level lexicals of a statement list into `names`.
fn top_level_lexicals<T: IdentList>(stmts: &StatementList, names: &mut T) {
for stmt in stmts.statements() {
if let StatementListItem::Declaration(decl) = stmt {
match decl {
// Note
// At the top level of a function, or script, function declarations are treated like
// var declarations rather than like lexical declarations.
Declaration::Function(_)
| Declaration::Generator(_)
| Declaration::AsyncFunction(_)
| Declaration::AsyncGenerator(_) => {}
Declaration::Class(class) => {
BoundNamesVisitor(names).visit_class(class);
}
Declaration::Lexical(decl) => {
BoundNamesVisitor(names).visit_lexical_declaration(decl);
}
}
}
}
}
/// Returns a list with the lexical bindings of a top-level statement list, which may contain duplicates.
///
/// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames
#[must_use]
#[inline]
pub fn top_level_lexically_declared_names(stmts: &StatementList) -> Vec<Identifier> {
let mut names = Vec::new();
top_level_lexicals(stmts, &mut names);
names
}
/// Utility function that collects the top level vars of a statement list into `names`.
fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet<Identifier>) {
for stmt in stmts.statements() {
match stmt {
StatementListItem::Declaration(decl) => {
match decl {
// Note
// At the top level of a function, or script, function declarations are treated like
// var declarations rather than like lexical declarations.
Declaration::Function(f) => {
BoundNamesVisitor(names).visit_function(f);
}
Declaration::Generator(f) => {
BoundNamesVisitor(names).visit_generator(f);
}
Declaration::AsyncFunction(f) => {
BoundNamesVisitor(names).visit_async_function(f);
}
Declaration::AsyncGenerator(f) => {
BoundNamesVisitor(names).visit_async_generator(f);
}
Declaration::Class(_) | Declaration::Lexical(_) => {}
}
}
StatementListItem::Statement(stmt) => {
let mut stmt = Some(stmt);
while let Some(Statement::Labelled(labelled)) = stmt {
match labelled.item() {
LabelledItem::Function(f) => {
BoundNamesVisitor(names).visit_function(f);
stmt = None;
}
LabelledItem::Statement(s) => stmt = Some(s),
}
}
if let Some(stmt) = stmt {
VarDeclaredNamesVisitor(names).visit(stmt);
}
}
}
}
}
/// Returns a list with the var bindings of a top-level statement list, with no duplicates.
///
/// This is equivalent to the [`TopLevelVarDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames
#[must_use]
#[inline]
pub fn top_level_var_declared_names(stmts: &StatementList) -> FxHashSet<Identifier> {
let mut names = FxHashSet::default();
top_level_vars(stmts, &mut names);
names
}
/// Resolves the private names of a class and all of the contained classes and private identifiers.
pub fn class_private_name_resolver(node: &mut Class, top_level_class_index: usize) -> bool {
/// Visitor used by the function to search for an identifier with the name `arguments`.
#[derive(Debug, Clone)]
struct ClassPrivateNameResolver {
private_environments_stack: Vec<FxHashMap<Sym, PrivateName>>,
top_level_class_index: usize,
}
impl<'ast> VisitorMut<'ast> for ClassPrivateNameResolver {
type BreakTy = ();
#[inline]
fn visit_class_mut(&mut self, node: &'ast mut Class) -> ControlFlow<Self::BreakTy> {
let mut names = FxHashMap::default();
for element in node.elements.iter_mut() {
match element {
ClassElement::PrivateMethodDefinition(name, _)
| ClassElement::PrivateStaticMethodDefinition(name, _)
| ClassElement::PrivateFieldDefinition(name, _)
| ClassElement::PrivateStaticFieldDefinition(name, _) => {
name.indices = (
self.top_level_class_index,
self.private_environments_stack.len(),
);
names.insert(name.description(), *name);
}
_ => {}
}
}
self.private_environments_stack.push(names);
for element in node.elements.iter_mut() {
match element {
ClassElement::MethodDefinition(name, method)
| ClassElement::StaticMethodDefinition(name, method) => {
try_break!(self.visit_property_name_mut(name));
try_break!(self.visit_method_definition_mut(method));
}
ClassElement::PrivateMethodDefinition(_, method)
| ClassElement::PrivateStaticMethodDefinition(_, method) => {
try_break!(self.visit_method_definition_mut(method));
}
ClassElement::FieldDefinition(name, expression)
| ClassElement::StaticFieldDefinition(name, expression) => {
try_break!(self.visit_property_name_mut(name));
if let Some(expression) = expression {
try_break!(self.visit_expression_mut(expression));
}
}
ClassElement::PrivateFieldDefinition(_, expression)
| ClassElement::PrivateStaticFieldDefinition(_, expression) => {
if let Some(expression) = expression {
try_break!(self.visit_expression_mut(expression));
}
}
ClassElement::StaticBlock(statement_list) => {
try_break!(self.visit_statement_list_mut(statement_list));
}
}
}
if let Some(function) = &mut node.constructor {
try_break!(self.visit_function_mut(function));
}
self.private_environments_stack.pop();
ControlFlow::Continue(())
}
#[inline]
fn visit_private_name_mut(
&mut self,
node: &'ast mut PrivateName,
) -> ControlFlow<Self::BreakTy> {
let mut found = false;
for environment in self.private_environments_stack.iter().rev() {
if let Some(n) = environment.get(&node.description()) {
found = true;
node.indices = n.indices;
break;
}
}
if found {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
}
}
let mut visitor = ClassPrivateNameResolver {
private_environments_stack: Vec::new(),
top_level_class_index,
};
visitor.visit_class_mut(node).is_continue()
}

View File

@@ -0,0 +1,787 @@
//! A pattern binding or assignment node.
//!
//! A [`Pattern`] Corresponds to the [`BindingPattern`][spec1] and the [`AssignmentPattern`][spec2]
//! nodes, each of which is used in different situations and have slightly different grammars.
//! For example, a variable declaration combined with a destructuring expression is a `BindingPattern`:
//!
//! ```Javascript
//! const obj = { a: 1, b: 2 };
//! const { a, b } = obj; // BindingPattern
//! ```
//!
//! On the other hand, a simple destructuring expression with already declared variables is called
//! an `AssignmentPattern`:
//!
//! ```Javascript
//! let a = 1;
//! let b = 3;
//! [a, b] = [b, a]; // AssignmentPattern
//! ```
//!
//! [spec1]: https://tc39.es/ecma262/#prod-BindingPattern
//! [spec2]: https://tc39.es/ecma262/#prod-AssignmentPattern
//! [destr]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
use crate::{
expression::{access::PropertyAccess, Identifier},
property::PropertyName,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Expression,
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// An object or array pattern binding or assignment.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
/// An object pattern (`let {a, b, c} = object`).
Object(ObjectPattern),
/// An array pattern (`[a, b, c] = array`).
Array(ArrayPattern),
}
impl From<ObjectPattern> for Pattern {
fn from(obj: ObjectPattern) -> Self {
Self::Object(obj)
}
}
impl From<ArrayPattern> for Pattern {
fn from(obj: ArrayPattern) -> Self {
Self::Array(obj)
}
}
impl From<Vec<ObjectPatternElement>> for Pattern {
fn from(elements: Vec<ObjectPatternElement>) -> Self {
ObjectPattern::new(elements.into()).into()
}
}
impl From<Vec<ArrayPatternElement>> for Pattern {
fn from(elements: Vec<ArrayPatternElement>) -> Self {
ArrayPattern::new(elements.into()).into()
}
}
impl ToInternedString for Pattern {
fn to_interned_string(&self, interner: &Interner) -> String {
match &self {
Self::Object(o) => o.to_interned_string(interner),
Self::Array(a) => a.to_interned_string(interner),
}
}
}
impl VisitWith for Pattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Object(op) => visitor.visit_object_pattern(op),
Self::Array(ap) => visitor.visit_array_pattern(ap),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Object(op) => visitor.visit_object_pattern_mut(op),
Self::Array(ap) => visitor.visit_array_pattern_mut(ap),
}
}
}
/// An object binding or assignment pattern.
///
/// Corresponds to the [`ObjectBindingPattern`][spec1] and the [`ObjectAssignmentPattern`][spec2]
/// Parse Nodes.
///
/// For more information on what is a valid binding in an object pattern, see [`ObjectPatternElement`].
///
/// [spec1]: https://tc39.es/ecma262/#prod-ObjectBindingPattern
/// [spec2]: https://tc39.es/ecma262/#prod-ObjectAssignmentPattern
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectPattern(Box<[ObjectPatternElement]>);
impl From<Vec<ObjectPatternElement>> for ObjectPattern {
fn from(elements: Vec<ObjectPatternElement>) -> Self {
Self(elements.into())
}
}
impl ToInternedString for ObjectPattern {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = "{".to_owned();
for (i, binding) in self.0.iter().enumerate() {
let binding = binding.to_interned_string(interner);
let str = if i == self.0.len() - 1 {
format!("{binding} ")
} else {
format!("{binding},")
};
buf.push_str(&str);
}
if self.0.is_empty() {
buf.push(' ');
}
buf.push('}');
buf
}
}
impl ObjectPattern {
/// Creates a new object binding pattern.
#[inline]
#[must_use]
pub fn new(bindings: Box<[ObjectPatternElement]>) -> Self {
Self(bindings)
}
/// Gets the bindings for the object binding pattern.
#[inline]
#[must_use]
pub const fn bindings(&self) -> &[ObjectPatternElement] {
&self.0
}
/// Returns true if the object binding pattern has a rest element.
#[inline]
#[must_use]
pub const fn has_rest(&self) -> bool {
matches!(
self.0.last(),
Some(ObjectPatternElement::RestProperty { .. })
)
}
}
impl VisitWith for ObjectPattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for elem in self.0.iter() {
try_break!(visitor.visit_object_pattern_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for elem in self.0.iter_mut() {
try_break!(visitor.visit_object_pattern_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// An array binding or assignment pattern.
///
/// Corresponds to the [`ArrayBindingPattern`][spec1] and the [`ArrayAssignmentPattern`][spec2]
/// Parse Nodes.
///
/// For more information on what is a valid binding in an array pattern, see [`ArrayPatternElement`].
///
/// [spec1]: https://tc39.es/ecma262/#prod-ArrayBindingPattern
/// [spec2]: https://tc39.es/ecma262/#prod-ArrayAssignmentPattern
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayPattern(Box<[ArrayPatternElement]>);
impl From<Vec<ArrayPatternElement>> for ArrayPattern {
fn from(elements: Vec<ArrayPatternElement>) -> Self {
Self(elements.into())
}
}
impl ToInternedString for ArrayPattern {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = "[".to_owned();
for (i, binding) in self.0.iter().enumerate() {
if i == self.0.len() - 1 {
match binding {
ArrayPatternElement::Elision => {
buf.push_str(&format!("{}, ", binding.to_interned_string(interner)));
}
_ => buf.push_str(&format!("{} ", binding.to_interned_string(interner))),
}
} else {
buf.push_str(&format!("{},", binding.to_interned_string(interner)));
}
}
buf.push(']');
buf
}
}
impl ArrayPattern {
/// Creates a new array binding pattern.
#[inline]
#[must_use]
pub fn new(bindings: Box<[ArrayPatternElement]>) -> Self {
Self(bindings)
}
/// Gets the bindings for the array binding pattern.
#[inline]
#[must_use]
pub const fn bindings(&self) -> &[ArrayPatternElement] {
&self.0
}
}
impl VisitWith for ArrayPattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for elem in self.0.iter() {
try_break!(visitor.visit_array_pattern_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for elem in self.0.iter_mut() {
try_break!(visitor.visit_array_pattern_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// The different types of bindings that an [`ObjectPattern`] may contain.
///
/// Corresponds to the [`BindingProperty`][spec1] and the [`AssignmentProperty`][spec2] nodes.
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingProperty
/// [spec2]: https://tc39.es/ecma262/#prod-AssignmentProperty
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ObjectPatternElement {
/// SingleName represents one of the following properties:
///
/// - `SingleName` with an identifier and an optional default initializer.
/// - `BindingProperty` with an property name and a `SingleNameBinding` as the `BindingElement`.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - SingleNameBinding][spec1]
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingProperty][spec2]
///
/// [spec1]: https://tc39.es/ecma262/#prod-SingleNameBinding
/// [spec2]: https://tc39.es/ecma262/#prod-BindingProperty
SingleName {
/// The identifier name of the property to be destructured.
name: PropertyName,
/// The variable name where the property value will be stored.
ident: Identifier,
/// An optional default value for the variable, in case the property doesn't exist.
default_init: Option<Expression>,
},
/// RestProperty represents a `BindingRestProperty` with an identifier.
///
/// It also includes a list of the property keys that should be excluded from the rest,
/// because they where already assigned.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestProperty][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestProperty
RestProperty {
/// The variable name where the unassigned properties will be stored.
ident: Identifier,
/// A list of the excluded property keys that were already destructured.
excluded_keys: Vec<Identifier>,
},
/// AssignmentGetField represents an AssignmentProperty with an expression field member expression AssignmentElement.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentProperty
AssignmentPropertyAccess {
/// The identifier name of the property to be destructured.
name: PropertyName,
/// The property access where the property value will be destructured.
access: PropertyAccess,
/// An optional default value for the variable, in case the property doesn't exist.
default_init: Option<Expression>,
},
/// AssignmentRestProperty represents a rest property with a DestructuringAssignmentTarget.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentRestProperty
AssignmentRestPropertyAccess {
/// The property access where the unassigned properties will be stored.
access: PropertyAccess,
/// A list of the excluded property keys that were already destructured.
excluded_keys: Vec<Identifier>,
},
/// Pattern represents a property with a `Pattern` as the element.
///
/// Additionally to the identifier of the new property and the nested pattern,
/// this may also include an optional default initializer.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingProperty][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingProperty
Pattern {
/// The identifier name of the property to be destructured.
name: PropertyName,
/// The pattern where the property value will be destructured.
pattern: Pattern,
/// An optional default value for the variable, in case the property doesn't exist.
default_init: Option<Expression>,
},
}
impl ToInternedString for ObjectPatternElement {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::SingleName {
ident,
name,
default_init,
} => {
let mut buf = match name {
PropertyName::Literal(name) if name == ident => {
format!(" {}", interner.resolve_expect(ident.sym()))
}
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
interner.resolve_expect(ident.sym())
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
interner.resolve_expect(ident.sym())
)
}
};
if let Some(ref init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::RestProperty {
ident,
excluded_keys: _,
} => {
format!(" ... {}", interner.resolve_expect(ident.sym()))
}
Self::AssignmentRestPropertyAccess { access, .. } => {
format!(" ... {}", access.to_interned_string(interner))
}
Self::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
let mut buf = match name {
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
access.to_interned_string(interner)
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
access.to_interned_string(interner)
)
}
};
if let Some(init) = &default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::Pattern {
name,
pattern,
default_init,
} => {
let mut buf = match name {
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
pattern.to_interned_string(interner),
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
pattern.to_interned_string(interner),
)
}
};
if let Some(ref init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
}
}
}
impl VisitWith for ObjectPatternElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::SingleName {
name,
ident,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_identifier(ident));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::RestProperty { ident, .. } => visitor.visit_identifier(ident),
Self::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_property_access(access));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::AssignmentRestPropertyAccess { access, .. } => {
visitor.visit_property_access(access)
}
Self::Pattern {
name,
pattern,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_pattern(pattern));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::SingleName {
name,
ident,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_identifier_mut(ident));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::RestProperty { ident, .. } => visitor.visit_identifier_mut(ident),
Self::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_property_access_mut(access));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::AssignmentRestPropertyAccess { access, .. } => {
visitor.visit_property_access_mut(access)
}
Self::Pattern {
name,
pattern,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_pattern_mut(pattern));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}
}
}
/// The different types of bindings that an array binding pattern may contain.
///
/// Corresponds to the [`BindingElement`][spec1] and the [`AssignmentElement`][spec2] nodes.
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingElement
/// [spec2]: https://tc39.es/ecma262/#prod-AssignmentElement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ArrayPatternElement {
/// Elision represents the elision of an item in the array binding pattern.
///
/// An `Elision` may occur at multiple points in the pattern and may be multiple elisions.
/// This variant strictly represents one elision. If there are multiple, this should be used multiple times.
///
/// More information:
/// - [ECMAScript reference: 13.2.4 Array Initializer - Elision][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-Elision
Elision,
/// SingleName represents a `SingleName` with an identifier and an optional default initializer.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - SingleNameBinding][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-SingleNameBinding
SingleName {
/// The variable name where the index element will be stored.
ident: Identifier,
/// An optional default value for the variable, in case the index element doesn't exist.
default_init: Option<Expression>,
},
/// PropertyAccess represents a binding with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
PropertyAccess {
/// The property access where the index element will be stored.
access: PropertyAccess,
},
/// Pattern represents a `Pattern` in an `Element` of an array pattern.
///
/// The pattern and the optional default initializer are both stored in the DeclarationPattern.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingElement][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingElement
Pattern {
/// The pattern where the index element will be stored.
pattern: Pattern,
/// An optional default value for the pattern, in case the index element doesn't exist.
default_init: Option<Expression>,
},
/// SingleNameRest represents a `BindingIdentifier` in a `BindingRestElement` of an array pattern.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestElement][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement
SingleNameRest {
/// The variable where the unassigned index elements will be stored.
ident: Identifier,
},
/// PropertyAccess represents a rest (spread operator) with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
PropertyAccessRest {
/// The property access where the unassigned index elements will be stored.
access: PropertyAccess,
},
/// PatternRest represents a `Pattern` in a `RestElement` of an array pattern.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestElement][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement
PatternRest {
/// The pattern where the unassigned index elements will be stored.
pattern: Pattern,
},
}
impl ToInternedString for ArrayPatternElement {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Elision => " ".to_owned(),
Self::SingleName {
ident,
default_init,
} => {
let mut buf = format!(" {}", interner.resolve_expect(ident.sym()));
if let Some(ref init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::PropertyAccess { access } => {
format!(" {}", access.to_interned_string(interner))
}
Self::Pattern {
pattern,
default_init,
} => {
let mut buf = format!(" {}", pattern.to_interned_string(interner));
if let Some(init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::SingleNameRest { ident } => {
format!(" ... {}", interner.resolve_expect(ident.sym()))
}
Self::PropertyAccessRest { access } => {
format!(" ... {}", access.to_interned_string(interner))
}
Self::PatternRest { pattern } => {
format!(" ... {}", pattern.to_interned_string(interner))
}
}
}
}
impl VisitWith for ArrayPatternElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::SingleName {
ident,
default_init,
} => {
try_break!(visitor.visit_identifier(ident));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PropertyAccess { access } | Self::PropertyAccessRest { access } => {
visitor.visit_property_access(access)
}
Self::Pattern {
pattern,
default_init,
} => {
try_break!(visitor.visit_pattern(pattern));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::SingleNameRest { ident } => visitor.visit_identifier(ident),
Self::PatternRest { pattern } => visitor.visit_pattern(pattern),
Self::Elision => {
// special case to be handled by user
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::SingleName {
ident,
default_init,
} => {
try_break!(visitor.visit_identifier_mut(ident));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PropertyAccess { access } | Self::PropertyAccessRest { access } => {
visitor.visit_property_access_mut(access)
}
Self::Pattern {
pattern,
default_init,
} => {
try_break!(visitor.visit_pattern_mut(pattern));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::SingleNameRest { ident } => visitor.visit_identifier_mut(ident),
Self::PatternRest { pattern } => visitor.visit_pattern_mut(pattern),
Self::Elision => {
// special case to be handled by user
ControlFlow::Continue(())
}
}
}
}

View File

@@ -0,0 +1,302 @@
use std::{cmp::Ordering, fmt, num::NonZeroU32};
/// A position in the ECMAScript source code.
///
/// Stores both the column number and the line number.
///
/// ## Similar Implementations
/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216)
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position {
/// Line number.
line_number: NonZeroU32,
/// Column number.
column_number: NonZeroU32,
}
impl Position {
/// Creates a new `Position`.
#[inline]
#[track_caller]
#[must_use]
pub fn new(line_number: u32, column_number: u32) -> Self {
Self {
line_number: NonZeroU32::new(line_number).expect("line number cannot be 0"),
column_number: NonZeroU32::new(column_number).expect("column number cannot be 0"),
}
}
/// Gets the line number of the position.
#[inline]
#[must_use]
pub const fn line_number(self) -> u32 {
self.line_number.get()
}
/// Gets the column number of the position.
#[inline]
#[must_use]
pub const fn column_number(self) -> u32 {
self.column_number.get()
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line_number, self.column_number)
}
}
/// A span in the ECMAScript source code.
///
/// Stores a start position and an end position.
///
/// Note that spans are of the form [start, end) i.e. that the start position is inclusive
/// and the end position is exclusive.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
start: Position,
end: Position,
}
impl Span {
/// Creates a new `Span`.
///
/// # Panics
///
/// Panics if the start position is bigger than the end position.
#[inline]
#[track_caller]
#[must_use]
pub fn new(start: Position, end: Position) -> Self {
assert!(start <= end, "a span cannot start after its end");
Self { start, end }
}
/// Gets the starting position of the span.
#[inline]
#[must_use]
pub const fn start(self) -> Position {
self.start
}
/// Gets the final position of the span.
#[inline]
#[must_use]
pub const fn end(self) -> Position {
self.end
}
/// Checks if this span inclusively contains another span or position.
pub fn contains<S>(self, other: S) -> bool
where
S: Into<Self>,
{
let other = other.into();
self.start <= other.start && self.end >= other.end
}
}
impl From<Position> for Span {
fn from(pos: Position) -> Self {
Self {
start: pos,
end: pos,
}
}
}
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
Some(Ordering::Equal)
} else if self.end < other.start {
Some(Ordering::Less)
} else if self.start > other.end {
Some(Ordering::Greater)
} else {
None
}
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}..{}]", self.start, self.end)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::similar_names)]
#![allow(unused_must_use)]
use super::{Position, Span};
/// Checks that we cannot create a position with 0 as the column.
#[test]
#[should_panic]
fn invalid_position_column() {
Position::new(10, 0);
}
/// Checks that we cannot create a position with 0 as the line.
#[test]
#[should_panic]
fn invalid_position_line() {
Position::new(0, 10);
}
/// Checks that the `PartialEq` implementation of `Position` is consistent.
#[test]
fn position_equality() {
assert_eq!(Position::new(10, 50), Position::new(10, 50));
assert_ne!(Position::new(10, 50), Position::new(10, 51));
assert_ne!(Position::new(10, 50), Position::new(11, 50));
assert_ne!(Position::new(10, 50), Position::new(11, 51));
}
/// Checks that the `PartialOrd` implementation of `Position` is consistent.
#[test]
fn position_order() {
assert!(Position::new(10, 50) < Position::new(10, 51));
assert!(Position::new(9, 50) < Position::new(10, 50));
assert!(Position::new(10, 50) < Position::new(11, 51));
assert!(Position::new(10, 50) < Position::new(11, 49));
assert!(Position::new(10, 51) > Position::new(10, 50));
assert!(Position::new(10, 50) > Position::new(9, 50));
assert!(Position::new(11, 51) > Position::new(10, 50));
assert!(Position::new(11, 49) > Position::new(10, 50));
}
/// Checks that the position getters actually retrieve correct values.
#[test]
fn position_getters() {
let pos = Position::new(10, 50);
assert_eq!(pos.line_number(), 10);
assert_eq!(pos.column_number(), 50);
}
/// Checks that the string representation of a position is correct.
#[test]
fn position_to_string() {
let pos = Position::new(10, 50);
assert_eq!("10:50", pos.to_string());
assert_eq!("10:50", pos.to_string());
}
/// Checks that we cannot create an invalid span.
#[test]
#[should_panic]
fn invalid_span() {
let a = Position::new(10, 30);
let b = Position::new(10, 50);
Span::new(b, a);
}
/// Checks that we can create valid spans.
#[test]
fn span_creation() {
let a = Position::new(10, 30);
let b = Position::new(10, 50);
let _ = Span::new(a, b);
let _ = Span::new(a, a);
let _ = Span::from(a);
}
/// Checks that the `PartialEq` implementation of `Span` is consistent.
#[test]
fn span_equality() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let span_ab = Span::new(a, b);
let span_ab_2 = Span::new(a, b);
let span_ac = Span::new(a, c);
let span_bc = Span::new(b, c);
assert_eq!(span_ab, span_ab_2);
assert_ne!(span_ab, span_ac);
assert_ne!(span_ab, span_bc);
assert_ne!(span_bc, span_ac);
let span_a = Span::from(a);
let span_aa = Span::new(a, a);
assert_eq!(span_a, span_aa);
}
/// Checks that the getters retrieve the correct value.
#[test]
fn span_getters() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let span = Span::new(a, b);
assert_eq!(span.start(), a);
assert_eq!(span.end(), b);
}
/// Checks that the `Span::contains()` method works properly.
#[test]
fn span_contains() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let d = Position::new(12, 5);
let span_ac = Span::new(a, c);
assert!(span_ac.contains(b));
let span_ab = Span::new(a, b);
let span_cd = Span::new(c, d);
assert!(!span_ab.contains(span_cd));
assert!(span_ab.contains(b));
let span_ad = Span::new(a, d);
let span_bc = Span::new(b, c);
assert!(span_ad.contains(span_bc));
assert!(!span_bc.contains(span_ad));
let span_ac = Span::new(a, c);
let span_bd = Span::new(b, d);
assert!(!span_ac.contains(span_bd));
assert!(!span_bd.contains(span_ac));
}
/// Checks that the string representation of a span is correct.
#[test]
fn span_to_string() {
let a = Position::new(10, 50);
let b = Position::new(11, 20);
let span = Span::new(a, b);
assert_eq!("[10:50..11:20]", span.to_string());
assert_eq!("[10:50..11:20]", span.to_string());
}
/// Checks that the ordering of spans is correct.
#[test]
fn span_ordering() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let d = Position::new(12, 5);
let span_ab = Span::new(a, b);
let span_cd = Span::new(c, d);
assert!(span_ab < span_cd);
assert!(span_cd > span_ab);
}
}

View File

@@ -0,0 +1,371 @@
//! Property definition related types, used in object literals and class definitions.
use crate::function::PrivateName;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::{
expression::{literal::Literal, Identifier},
function::{AsyncFunction, AsyncGenerator, Function, Generator},
Expression,
};
/// Describes the definition of a property within an object literal.
///
/// A property has a name (a string) and a value (primitive, method, or object reference).
/// Note that when we say that "a property holds an object", that is shorthand for "a property holds an object reference".
/// This distinction matters because the original referenced object remains unchanged when you change the property's value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript
// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyDefinition {
/// Puts a variable into an object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
IdentifierReference(Identifier),
/// Binds a property name to a JavaScript value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
Property(PropertyName, Expression),
/// A property of an object can also refer to a function or a getter or setter method.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions
MethodDefinition(PropertyName, MethodDefinition),
/// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals.
/// It copies own enumerable properties from a provided object onto a new object.
///
/// Shallow-cloning (excluding `prototype`) or merging objects is now possible using a shorter syntax than `Object.assign()`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties
SpreadObject(Expression),
/// Cover grammar for when an object literal is used as an object binding pattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-CoverInitializedName
CoverInitializedName(Identifier, Expression),
}
impl VisitWith for PropertyDefinition {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::IdentifierReference(id) => visitor.visit_identifier(id),
Self::Property(pn, expr) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_expression(expr)
}
Self::MethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_method_definition(md)
}
Self::SpreadObject(expr) => visitor.visit_expression(expr),
Self::CoverInitializedName(id, expr) => {
try_break!(visitor.visit_identifier(id));
visitor.visit_expression(expr)
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::IdentifierReference(id) => visitor.visit_identifier_mut(id),
Self::Property(pn, expr) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_expression_mut(expr)
}
Self::MethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_method_definition_mut(md)
}
Self::SpreadObject(expr) => visitor.visit_expression_mut(expr),
Self::CoverInitializedName(id, expr) => {
try_break!(visitor.visit_identifier_mut(id));
visitor.visit_expression_mut(expr)
}
}
}
}
/// Method definition.
///
/// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced.
/// It is a shorthand for a function assigned to the method's name.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum MethodDefinition {
/// The `get` syntax binds an object property to a function that will be called when that property is looked up.
///
/// Sometimes it is desirable to allow access to a property that returns a dynamically computed value,
/// or you may want to reflect the status of an internal variable without requiring the use of explicit method calls.
/// In JavaScript, this can be accomplished with the use of a getter.
///
/// It is not possible to simultaneously have a getter bound to a property and have that property actually hold a value,
/// although it is possible to use a getter and a setter in conjunction to create a type of pseudo-property.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
Get(Function),
/// The `set` syntax binds an object property to a function to be called when there is an attempt to set that property.
///
/// In JavaScript, a setter can be used to execute a function whenever a specified property is attempted to be changed.
/// Setters are most often used in conjunction with getters to create a type of pseudo-property.
/// It is not possible to simultaneously have a setter on a property that holds an actual value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
Set(Function),
/// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax
Ordinary(Function),
/// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#generator_methods
Generator(Generator),
/// Async generators can be used to define a method
///
/// More information
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_generator_methods
AsyncGenerator(AsyncGenerator),
/// Async function can be used to define a method
///
/// More information
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_methods
Async(AsyncFunction),
}
impl VisitWith for MethodDefinition {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Get(f) | Self::Set(f) | Self::Ordinary(f) => visitor.visit_function(f),
Self::Generator(g) => visitor.visit_generator(g),
Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::Async(af) => visitor.visit_async_function(af),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Get(f) | Self::Set(f) | Self::Ordinary(f) => visitor.visit_function_mut(f),
Self::Generator(g) => visitor.visit_generator_mut(g),
Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::Async(af) => visitor.visit_async_function_mut(af),
}
}
}
/// `PropertyName` can be either a literal or computed.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyName
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyName {
/// A `Literal` property name can be either an identifier, a string or a numeric literal.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-LiteralPropertyName
Literal(Sym),
/// A `Computed` property name is an expression that gets evaluated and converted into a property name.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ComputedPropertyName
Computed(Expression),
}
impl PropertyName {
/// Returns the literal property name if it exists.
#[must_use]
pub const fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self {
Some(*sym)
} else {
None
}
}
/// Returns the expression if the property name is computed.
#[must_use]
pub const fn computed(&self) -> Option<&Expression> {
if let Self::Computed(expr) = self {
Some(expr)
} else {
None
}
}
/// Returns either the literal property name or the computed const string property name.
#[must_use]
pub const fn prop_name(&self) -> Option<Sym> {
match self {
Self::Literal(sym) | Self::Computed(Expression::Literal(Literal::String(sym))) => {
Some(*sym)
}
Self::Computed(_) => None,
}
}
}
impl ToInternedString for PropertyName {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Literal(key) => interner.resolve_expect(*key).to_string(),
Self::Computed(key) => key.to_interned_string(interner),
}
}
}
impl From<Sym> for PropertyName {
fn from(name: Sym) -> Self {
Self::Literal(name)
}
}
impl From<Expression> for PropertyName {
fn from(name: Expression) -> Self {
Self::Computed(name)
}
}
impl VisitWith for PropertyName {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Literal(sym) => visitor.visit_sym(sym),
Self::Computed(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Literal(sym) => visitor.visit_sym_mut(sym),
Self::Computed(expr) => visitor.visit_expression_mut(expr),
}
}
}
/// `ClassElementName` can be either a property name or a private identifier.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElementName
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ClassElementName {
/// A public property.
PropertyName(PropertyName),
/// A private property.
PrivateIdentifier(PrivateName),
}
impl ClassElementName {
/// Returns the property name if it exists.
#[must_use]
pub const fn literal(&self) -> Option<Sym> {
if let Self::PropertyName(name) = self {
name.literal()
} else {
None
}
}
}

View File

@@ -0,0 +1,285 @@
//! The `Punctuator` enum, which contains all punctuators used in ECMAScript.
//!
//! More information:
//! - [ECMAScript Reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#prod-Punctuator
use crate::expression::operator::{
assign::AssignOp,
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
};
use std::{
convert::TryInto,
fmt::{Display, Error, Formatter},
};
/// All of the punctuators used in ECMAScript.
///
/// More information:
/// - [ECMAScript Reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-Punctuator
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Punctuator {
/// `+`
Add,
/// `&`
And,
/// `=>`
Arrow,
/// `=`
Assign,
/// `+=`
AssignAdd,
/// `&=`
AssignAnd,
/// `&&=`
AssignBoolAnd,
/// `||=`
AssignBoolOr,
/// `??=`,
AssignCoalesce,
/// `/=`
AssignDiv,
/// `<<=`
AssignLeftSh,
/// `%=`
AssignMod,
/// `*=`
AssignMul,
/// `|=`
AssignOr,
/// `**=`
AssignPow,
/// `>>=`
AssignRightSh,
/// `-=`
AssignSub,
/// `>>>=`
AssignURightSh,
/// `^=`
AssignXor,
/// `&&`
BoolAnd,
/// `||`
BoolOr,
/// `}`
CloseBlock,
/// `]`
CloseBracket,
/// `)`
CloseParen,
/// `??`
Coalesce,
/// `:`
Colon,
/// `,`
Comma,
/// `--`
Dec,
/// `/`
Div,
/// `.`
Dot,
/// `==`
Eq,
/// `>`
GreaterThan,
/// `>=`
GreaterThanOrEq,
/// `++`
Inc,
/// `<<`
LeftSh,
/// `<`
LessThan,
/// `<=`
LessThanOrEq,
/// `%`
Mod,
/// `*`
Mul,
/// `~`
Neg,
/// `!`
Not,
/// `!=`
NotEq,
/// `{`
OpenBlock,
/// `[`
OpenBracket,
/// `(`
OpenParen,
/// `?.`
Optional,
/// `|`
Or,
/// `**`
Exp,
/// `?`
Question,
/// `>>`
RightSh,
/// `;`
Semicolon,
/// `...`
Spread,
/// `===`
StrictEq,
/// `!==`
StrictNotEq,
/// `-`
Sub,
/// `>>>`
URightSh,
/// `^`
Xor,
}
impl Punctuator {
/// Attempts to convert a punctuator (`+`, `=`...) to an Assign Operator
///
/// If there is no match, `None` will be returned.
#[must_use]
pub const fn as_assign_op(self) -> Option<AssignOp> {
match self {
Self::Assign => Some(AssignOp::Assign),
Self::AssignAdd => Some(AssignOp::Add),
Self::AssignAnd => Some(AssignOp::And),
Self::AssignBoolAnd => Some(AssignOp::BoolAnd),
Self::AssignBoolOr => Some(AssignOp::BoolOr),
Self::AssignCoalesce => Some(AssignOp::Coalesce),
Self::AssignDiv => Some(AssignOp::Div),
Self::AssignLeftSh => Some(AssignOp::Shl),
Self::AssignMod => Some(AssignOp::Mod),
Self::AssignMul => Some(AssignOp::Mul),
Self::AssignOr => Some(AssignOp::Or),
Self::AssignPow => Some(AssignOp::Exp),
Self::AssignRightSh => Some(AssignOp::Shr),
Self::AssignSub => Some(AssignOp::Sub),
Self::AssignURightSh => Some(AssignOp::Ushr),
Self::AssignXor => Some(AssignOp::Xor),
_ => None,
}
}
/// Attempts to convert a punctuator (`+`, `=`...) to a Binary Operator
///
/// If there is no match, `None` will be returned.
#[must_use]
pub const fn as_binary_op(self) -> Option<BinaryOp> {
match self {
Self::Add => Some(BinaryOp::Arithmetic(ArithmeticOp::Add)),
Self::Sub => Some(BinaryOp::Arithmetic(ArithmeticOp::Sub)),
Self::Mul => Some(BinaryOp::Arithmetic(ArithmeticOp::Mul)),
Self::Div => Some(BinaryOp::Arithmetic(ArithmeticOp::Div)),
Self::Mod => Some(BinaryOp::Arithmetic(ArithmeticOp::Mod)),
Self::And => Some(BinaryOp::Bitwise(BitwiseOp::And)),
Self::Or => Some(BinaryOp::Bitwise(BitwiseOp::Or)),
Self::Xor => Some(BinaryOp::Bitwise(BitwiseOp::Xor)),
Self::BoolAnd => Some(BinaryOp::Logical(LogicalOp::And)),
Self::BoolOr => Some(BinaryOp::Logical(LogicalOp::Or)),
Self::Coalesce => Some(BinaryOp::Logical(LogicalOp::Coalesce)),
Self::Eq => Some(BinaryOp::Relational(RelationalOp::Equal)),
Self::NotEq => Some(BinaryOp::Relational(RelationalOp::NotEqual)),
Self::StrictEq => Some(BinaryOp::Relational(RelationalOp::StrictEqual)),
Self::StrictNotEq => Some(BinaryOp::Relational(RelationalOp::StrictNotEqual)),
Self::LessThan => Some(BinaryOp::Relational(RelationalOp::LessThan)),
Self::GreaterThan => Some(BinaryOp::Relational(RelationalOp::GreaterThan)),
Self::GreaterThanOrEq => Some(BinaryOp::Relational(RelationalOp::GreaterThanOrEqual)),
Self::LessThanOrEq => Some(BinaryOp::Relational(RelationalOp::LessThanOrEqual)),
Self::LeftSh => Some(BinaryOp::Bitwise(BitwiseOp::Shl)),
Self::RightSh => Some(BinaryOp::Bitwise(BitwiseOp::Shr)),
Self::URightSh => Some(BinaryOp::Bitwise(BitwiseOp::UShr)),
Self::Comma => Some(BinaryOp::Comma),
_ => None,
}
}
/// Retrieves the punctuator as a static string.
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::And => "&",
Self::Arrow => "=>",
Self::Assign => "=",
Self::AssignAdd => "+=",
Self::AssignAnd => "&=",
Self::AssignBoolAnd => "&&=",
Self::AssignBoolOr => "||=",
Self::AssignCoalesce => "??=",
Self::AssignDiv => "/=",
Self::AssignLeftSh => "<<=",
Self::AssignMod => "%=",
Self::AssignMul => "*=",
Self::AssignOr => "|=",
Self::AssignPow => "**=",
Self::AssignRightSh => ">>=",
Self::AssignSub => "-=",
Self::AssignURightSh => ">>>=",
Self::AssignXor => "^=",
Self::BoolAnd => "&&",
Self::BoolOr => "||",
Self::Coalesce => "??",
Self::CloseBlock => "}",
Self::CloseBracket => "]",
Self::CloseParen => ")",
Self::Colon => ":",
Self::Comma => ",",
Self::Dec => "--",
Self::Div => "/",
Self::Dot => ".",
Self::Eq => "==",
Self::GreaterThan => ">",
Self::GreaterThanOrEq => ">=",
Self::Inc => "++",
Self::LeftSh => "<<",
Self::LessThan => "<",
Self::LessThanOrEq => "<=",
Self::Mod => "%",
Self::Mul => "*",
Self::Neg => "~",
Self::Not => "!",
Self::NotEq => "!=",
Self::OpenBlock => "{",
Self::OpenBracket => "[",
Self::OpenParen => "(",
Self::Optional => "?.",
Self::Or => "|",
Self::Exp => "**",
Self::Question => "?",
Self::RightSh => ">>",
Self::Semicolon => ";",
Self::Spread => "...",
Self::StrictEq => "===",
Self::StrictNotEq => "!==",
Self::Sub => "-",
Self::URightSh => ">>>",
Self::Xor => "^",
}
}
}
impl TryInto<BinaryOp> for Punctuator {
type Error = String;
fn try_into(self) -> Result<BinaryOp, Self::Error> {
self.as_binary_op()
.ok_or_else(|| format!("No binary operation for {self}"))
}
}
impl Display for Punctuator {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{}", self.as_str())
}
}
impl From<Punctuator> for Box<str> {
fn from(p: Punctuator) -> Self {
p.as_str().into()
}
}

View File

@@ -0,0 +1,85 @@
//! Block AST node.
use crate::{
visitor::{VisitWith, Visitor, VisitorMut},
Statement, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
/// A `block` statement (or compound statement in other languages) is used to group zero or
/// more statements.
///
/// The block statement is often called compound statement in other languages.
/// It allows you to use multiple statements where ECMAScript expects only one statement.
/// Combining statements into blocks is a common practice in ECMAScript. The opposite behavior
/// is possible using an empty statement, where you provide no statement, although one is
/// required.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Block {
#[cfg_attr(feature = "serde", serde(flatten))]
statements: StatementList,
}
impl Block {
/// Gets the list of statements and declarations in this block.
#[inline]
#[must_use]
pub const fn statement_list(&self) -> &StatementList {
&self.statements
}
}
impl<T> From<T> for Block
where
T: Into<StatementList>,
{
fn from(list: T) -> Self {
Self {
statements: list.into(),
}
}
}
impl ToIndentedString for Block {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"{{\n{}{}}}",
self.statements
.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
)
}
}
impl From<Block> for Statement {
#[inline]
fn from(block: Block) -> Self {
Self::Block(block)
}
}
impl VisitWith for Block {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_statement_list(&self.statements)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_statement_list_mut(&mut self.statements)
}
}

View File

@@ -0,0 +1,119 @@
//! If statement
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `if` statement executes a statement if a specified condition is [`truthy`][truthy]. If
/// the condition is [`falsy`][falsy], another statement can be executed.
///
/// Multiple `if...else` statements can be nested to create an else if clause.
///
/// Note that there is no elseif (in one word) keyword in JavaScript.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-IfStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/truthy
/// [falsy]: https://developer.mozilla.org/en-US/docs/Glossary/falsy
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct If {
condition: Expression,
body: Box<Statement>,
else_node: Option<Box<Statement>>,
}
impl If {
/// Gets the condition of the if statement.
#[inline]
#[must_use]
pub const fn cond(&self) -> &Expression {
&self.condition
}
/// Gets the body to execute if the condition is true.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
/// Gets the `else` node, if it has one.
#[inline]
pub fn else_node(&self) -> Option<&Statement> {
self.else_node.as_ref().map(Box::as_ref)
}
/// Creates an `If` AST node.
#[inline]
#[must_use]
pub fn new(condition: Expression, body: Statement, else_node: Option<Statement>) -> Self {
Self {
condition,
body: body.into(),
else_node: else_node.map(Box::new),
}
}
}
impl ToIndentedString for If {
fn to_indented_string(&self, interner: &Interner, indent: usize) -> String {
let mut buf = format!("if ({}) ", self.cond().to_interned_string(interner));
match self.else_node() {
Some(else_e) => {
buf.push_str(&format!(
"{} else {}",
self.body().to_indented_string(interner, indent),
else_e.to_indented_string(interner, indent)
));
}
None => {
buf.push_str(&self.body().to_indented_string(interner, indent));
}
}
buf
}
}
impl From<If> for Statement {
fn from(if_stm: If) -> Self {
Self::If(if_stm)
}
}
impl VisitWith for If {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
try_break!(visitor.visit_statement(&self.body));
if let Some(stmt) = &self.else_node {
try_break!(visitor.visit_statement(stmt));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
try_break!(visitor.visit_statement_mut(&mut self.body));
if let Some(stmt) = &mut self.else_node {
try_break!(visitor.visit_statement_mut(stmt));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,79 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::Statement;
/// The `break` statement terminates the current loop, switch, or label statement and transfers
/// program control to the statement following the terminated statement.
///
/// The break statement includes an optional label that allows the program to break out of a
/// labeled statement. The break statement needs to be nested within the referenced label. The
/// labeled statement can be any block statement; it does not have to be preceded by a loop
/// statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BreakStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Break {
label: Option<Sym>,
}
impl Break {
/// Creates a `Break` AST node.
#[must_use]
pub const fn new(label: Option<Sym>) -> Self {
Self { label }
}
/// Gets the label of the break statement, if any.
#[must_use]
pub const fn label(&self) -> Option<Sym> {
self.label
}
}
impl ToInternedString for Break {
fn to_interned_string(&self, interner: &Interner) -> String {
self.label.map_or_else(
|| "break".to_owned(),
|label| format!("break {}", interner.resolve_expect(label)),
)
}
}
impl From<Break> for Statement {
fn from(break_smt: Break) -> Self {
Self::Break(break_smt)
}
}
impl VisitWith for Break {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(sym) = &self.label {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(sym) = &mut self.label {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,77 @@
use crate::statement::Statement;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// The `continue` statement terminates execution of the statements in the current iteration of
/// the current or labeled loop, and continues execution of the loop with the next iteration.
///
/// The continue statement can include an optional label that allows the program to jump to the
/// next iteration of a labeled loop statement instead of the current loop. In this case, the
/// continue statement needs to be nested within this labeled statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Continue {
label: Option<Sym>,
}
impl Continue {
/// Creates a `Continue` AST node.
#[must_use]
pub const fn new(label: Option<Sym>) -> Self {
Self { label }
}
/// Gets the label of this `Continue` statement.
#[must_use]
pub const fn label(&self) -> Option<Sym> {
self.label
}
}
impl ToInternedString for Continue {
fn to_interned_string(&self, interner: &Interner) -> String {
self.label.map_or_else(
|| "continue".to_owned(),
|label| format!("continue {}", interner.resolve_expect(label)),
)
}
}
impl From<Continue> for Statement {
fn from(cont: Continue) -> Self {
Self::Continue(cont)
}
}
impl VisitWith for Continue {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(sym) = &self.label {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(sym) = &mut self.label {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `do...while` statement creates a loop that executes a specified statement until the
/// test condition evaluates to false.
///
/// The condition is evaluated after executing the statement, resulting in the specified
/// statement executing at least once.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct DoWhileLoop {
body: Box<Statement>,
condition: Expression,
}
impl DoWhileLoop {
/// Gets the body of the do-while loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
/// Gets the condition of the do-while loop.
#[inline]
#[must_use]
pub const fn cond(&self) -> &Expression {
&self.condition
}
/// Creates a `DoWhileLoop` AST node.
#[inline]
#[must_use]
pub fn new(body: Statement, condition: Expression) -> Self {
Self {
body: body.into(),
condition,
}
}
}
impl ToIndentedString for DoWhileLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"do {} while ({})",
self.body().to_indented_string(interner, indentation),
self.cond().to_interned_string(interner)
)
}
}
impl From<DoWhileLoop> for Statement {
fn from(do_while: DoWhileLoop) -> Self {
Self::DoWhileLoop(do_while)
}
}
impl VisitWith for DoWhileLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_statement(&self.body));
visitor.visit_expression(&self.condition)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_statement_mut(&mut self.body));
visitor.visit_expression_mut(&mut self.condition)
}
}

View File

@@ -0,0 +1,98 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A `for...in` loop statement, as defined by the [spec].
///
/// [`for...in`][forin] statements loop over all enumerable string properties of an object, including
/// inherited properties.
///
/// [forin]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForInLoop {
initializer: IterableLoopInitializer,
target: Expression,
body: Box<Statement>,
}
impl ForInLoop {
/// Creates a new `ForInLoop`.
#[inline]
#[must_use]
pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self {
Self {
initializer,
target,
body: body.into(),
}
}
/// Gets the initializer of the for...in loop.
#[inline]
#[must_use]
pub const fn initializer(&self) -> &IterableLoopInitializer {
&self.initializer
}
/// Gets the target object of the for...in loop.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Gets the body of the for...in loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
}
impl ToIndentedString for ForInLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!(
"for ({} in {}) ",
self.initializer.to_interned_string(interner),
self.target.to_interned_string(interner)
);
buf.push_str(&self.body().to_indented_string(interner, indentation));
buf
}
}
impl From<ForInLoop> for Statement {
#[inline]
fn from(for_in: ForInLoop) -> Self {
Self::ForInLoop(for_in)
}
}
impl VisitWith for ForInLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer(&self.initializer));
try_break!(visitor.visit_expression(&self.target));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer_mut(&mut self.initializer));
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_statement_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,263 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
declaration::{LexicalDeclaration, VarDeclaration},
statement::Statement,
Expression,
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `for` statement creates a loop that consists of three optional expressions.
///
/// A [`for`][mdn] loop repeats until a specified condition evaluates to `false`.
/// The JavaScript for loop is similar to the Java and C for loop.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForLoop {
#[cfg_attr(feature = "serde", serde(flatten))]
inner: Box<InnerForLoop>,
}
impl ForLoop {
/// Creates a new for loop AST node.
#[inline]
#[must_use]
pub fn new(
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
) -> Self {
Self {
inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)),
}
}
/// Gets the initialization node.
#[inline]
#[must_use]
pub const fn init(&self) -> Option<&ForLoopInitializer> {
self.inner.init()
}
/// Gets the loop condition node.
#[inline]
#[must_use]
pub const fn condition(&self) -> Option<&Expression> {
self.inner.condition()
}
/// Gets the final expression node.
#[inline]
#[must_use]
pub const fn final_expr(&self) -> Option<&Expression> {
self.inner.final_expr()
}
/// Gets the body of the for loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
self.inner.body()
}
}
impl ToIndentedString for ForLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = String::from("for (");
if let Some(init) = self.init() {
buf.push_str(&init.to_interned_string(interner));
}
buf.push_str("; ");
if let Some(condition) = self.condition() {
buf.push_str(&condition.to_interned_string(interner));
}
buf.push_str("; ");
if let Some(final_expr) = self.final_expr() {
buf.push_str(&final_expr.to_interned_string(interner));
}
buf.push_str(&format!(
") {}",
self.inner.body().to_indented_string(interner, indentation)
));
buf
}
}
impl From<ForLoop> for Statement {
#[inline]
fn from(for_loop: ForLoop) -> Self {
Self::ForLoop(for_loop)
}
}
impl VisitWith for ForLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(fli) = &self.inner.init {
try_break!(visitor.visit_for_loop_initializer(fli));
}
if let Some(expr) = &self.inner.condition {
try_break!(visitor.visit_expression(expr));
}
if let Some(expr) = &self.inner.final_expr {
try_break!(visitor.visit_expression(expr));
}
visitor.visit_statement(&self.inner.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(fli) = &mut self.inner.init {
try_break!(visitor.visit_for_loop_initializer_mut(fli));
}
if let Some(expr) = &mut self.inner.condition {
try_break!(visitor.visit_expression_mut(expr));
}
if let Some(expr) = &mut self.inner.final_expr {
try_break!(visitor.visit_expression_mut(expr));
}
visitor.visit_statement_mut(&mut self.inner.body)
}
}
/// Inner structure to avoid multiple indirections in the heap.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
struct InnerForLoop {
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
}
impl InnerForLoop {
/// Creates a new inner for loop.
#[inline]
const fn new(
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
) -> Self {
Self {
init,
condition,
final_expr,
body,
}
}
/// Gets the initialization node.
#[inline]
const fn init(&self) -> Option<&ForLoopInitializer> {
self.init.as_ref()
}
/// Gets the loop condition node.
#[inline]
const fn condition(&self) -> Option<&Expression> {
self.condition.as_ref()
}
/// Gets the final expression node.
#[inline]
const fn final_expr(&self) -> Option<&Expression> {
self.final_expr.as_ref()
}
/// Gets the body of the for loop.
#[inline]
const fn body(&self) -> &Statement {
&self.body
}
}
/// A [`ForLoop`] initializer, as defined by the [spec].
///
/// A `ForLoop` initializer differs a lot from an
/// [`IterableLoopInitializer`][super::IterableLoopInitializer], since it can contain any arbitrary
/// expression instead of only accessors and patterns. Additionally, it can also contain many variable
/// declarations instead of only one.
///
/// [spec]: https://tc39.es/ecma262/#prod-ForStatement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ForLoopInitializer {
/// An expression initializer.
Expression(Expression),
/// A var declaration initializer.
Var(VarDeclaration),
/// A lexical declaration initializer.
Lexical(LexicalDeclaration),
}
impl ToInternedString for ForLoopInitializer {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Var(var) => var.to_interned_string(interner),
Self::Lexical(lex) => lex.to_interned_string(interner),
Self::Expression(expr) => expr.to_interned_string(interner),
}
}
}
impl From<Expression> for ForLoopInitializer {
#[inline]
fn from(expr: Expression) -> Self {
Self::Expression(expr)
}
}
impl From<LexicalDeclaration> for ForLoopInitializer {
#[inline]
fn from(list: LexicalDeclaration) -> Self {
Self::Lexical(list)
}
}
impl From<VarDeclaration> for ForLoopInitializer {
#[inline]
fn from(list: VarDeclaration) -> Self {
Self::Var(list)
}
}
impl VisitWith for ForLoopInitializer {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Expression(expr) => visitor.visit_expression(expr),
Self::Var(vd) => visitor.visit_var_declaration(vd),
Self::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Expression(expr) => visitor.visit_expression_mut(expr),
Self::Var(vd) => visitor.visit_var_declaration_mut(vd),
Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}

View File

@@ -0,0 +1,115 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A `for...of` loop statement, as defined by the [spec].
///
/// [`for..of`][forof] statements loop over a sequence of values obtained from an iterable object (Array,
/// String, Map, generators).
///
/// This type combines `for..of` and [`for await...of`][forawait] statements in a single structure,
/// since `for await...of` is essentially the same statement but with async iterable objects
/// as the source of iteration.
///
/// [forof]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
/// [forawait]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForOfLoop {
init: IterableLoopInitializer,
iterable: Expression,
body: Box<Statement>,
r#await: bool,
}
impl ForOfLoop {
/// Creates a new "for of" loop AST node.
#[inline]
#[must_use]
pub fn new(
init: IterableLoopInitializer,
iterable: Expression,
body: Statement,
r#await: bool,
) -> Self {
Self {
init,
iterable,
body: body.into(),
r#await,
}
}
/// Gets the initializer of the for...of loop.
#[inline]
#[must_use]
pub const fn initializer(&self) -> &IterableLoopInitializer {
&self.init
}
/// Gets the iterable expression of the for...of loop.
#[inline]
#[must_use]
pub const fn iterable(&self) -> &Expression {
&self.iterable
}
/// Gets the body to execute in the for...of loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
/// Returns true if this "for...of" loop is an "for await...of" loop.
#[inline]
#[must_use]
pub const fn r#await(&self) -> bool {
self.r#await
}
}
impl ToIndentedString for ForOfLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"for ({} of {}) {}",
self.init.to_interned_string(interner),
self.iterable.to_interned_string(interner),
self.body().to_indented_string(interner, indentation)
)
}
}
impl From<ForOfLoop> for Statement {
#[inline]
fn from(for_of: ForOfLoop) -> Self {
Self::ForOfLoop(for_of)
}
}
impl VisitWith for ForOfLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer(&self.init));
try_break!(visitor.visit_expression(&self.iterable));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer_mut(&mut self.init));
try_break!(visitor.visit_expression_mut(&mut self.iterable));
visitor.visit_statement_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,94 @@
//! Iteration nodes
mod r#break;
mod r#continue;
mod do_while_loop;
mod for_in_loop;
mod for_loop;
mod for_of_loop;
mod while_loop;
use crate::{
declaration::Binding,
expression::{access::PropertyAccess, Identifier},
pattern::Pattern,
};
use core::ops::ControlFlow;
pub use self::{
do_while_loop::DoWhileLoop,
for_in_loop::ForInLoop,
for_loop::{ForLoop, ForLoopInitializer},
for_of_loop::ForOfLoop,
r#break::Break,
r#continue::Continue,
while_loop::WhileLoop,
};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToInternedString};
/// A `for-in`, `for-of` and `for-await-of` loop initializer.
///
/// The [spec] specifies only single bindings for the listed types of loops, which makes us
/// unable to use plain `LexicalDeclaration`s or `VarStatement`s as initializers, since those
/// can have more than one binding.
///
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum IterableLoopInitializer {
/// An already declared variable.
Identifier(Identifier),
/// A property access.
Access(PropertyAccess),
/// A new var declaration.
Var(Binding),
/// A new let declaration.
Let(Binding),
/// A new const declaration.
Const(Binding),
/// A pattern with already declared variables.
Pattern(Pattern),
}
impl ToInternedString for IterableLoopInitializer {
fn to_interned_string(&self, interner: &Interner) -> String {
let (binding, pre) = match self {
Self::Identifier(ident) => return ident.to_interned_string(interner),
Self::Pattern(pattern) => return pattern.to_interned_string(interner),
Self::Access(access) => return access.to_interned_string(interner),
Self::Var(binding) => (binding, "var"),
Self::Let(binding) => (binding, "let"),
Self::Const(binding) => (binding, "const"),
};
format!("{pre} {}", binding.to_interned_string(interner))
}
}
impl VisitWith for IterableLoopInitializer {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Access(pa) => visitor.visit_property_access(pa),
Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding(b),
Self::Pattern(p) => visitor.visit_pattern(p),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Access(pa) => visitor.visit_property_access_mut(pa),
Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b),
Self::Pattern(p) => visitor.visit_pattern_mut(p),
}
}
}

View File

@@ -0,0 +1,88 @@
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `while` statement creates a loop that executes a specified statement as long as the
/// test condition evaluates to `true`.
///
/// The condition is evaluated before executing the statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct WhileLoop {
condition: Expression,
body: Box<Statement>,
}
impl WhileLoop {
/// Creates a `WhileLoop` AST node.
#[inline]
#[must_use]
pub fn new(condition: Expression, body: Statement) -> Self {
Self {
condition,
body: body.into(),
}
}
/// Gets the condition of the while loop.
#[inline]
#[must_use]
pub const fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the body of the while loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
}
impl ToIndentedString for WhileLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"while ({}) {}",
self.condition().to_interned_string(interner),
self.body().to_indented_string(interner, indentation)
)
}
}
impl From<WhileLoop> for Statement {
#[inline]
fn from(while_loop: WhileLoop) -> Self {
Self::WhileLoop(while_loop)
}
}
impl VisitWith for WhileLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
visitor.visit_statement_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,154 @@
use crate::{
function::Function,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Statement,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The set of Parse Nodes that can be preceded by a label, as defined by the [spec].
///
/// Semantically, a [`Labelled`] statement should only wrap [`Statement`] nodes. However,
/// old ECMAScript implementations supported [labelled function declarations][label-fn] as an extension
/// of the grammar. In the ECMAScript 2015 spec, the production of `LabelledStatement` was
/// modified to include labelled [`Function`]s as a valid node.
///
/// [spec]: https://tc39.es/ecma262/#prod-LabelledItem
/// [label-fn]: https://tc39.es/ecma262/#sec-labelled-function-declarations
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum LabelledItem {
/// A labelled [`Function`].
Function(Function),
/// A labelled [`Statement`].
Statement(Statement),
}
impl LabelledItem {
pub(crate) fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
Self::Function(f) => f.to_indented_string(interner, indentation),
Self::Statement(stmt) => stmt.to_indented_string(interner, indentation),
}
}
}
impl ToInternedString for LabelledItem {
fn to_interned_string(&self, interner: &Interner) -> String {
self.to_indented_string(interner, 0)
}
}
impl From<Function> for LabelledItem {
fn from(f: Function) -> Self {
Self::Function(f)
}
}
impl From<Statement> for LabelledItem {
fn from(stmt: Statement) -> Self {
Self::Statement(stmt)
}
}
impl VisitWith for LabelledItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Function(f) => visitor.visit_function(f),
Self::Statement(s) => visitor.visit_statement(s),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Function(f) => visitor.visit_function_mut(f),
Self::Statement(s) => visitor.visit_statement_mut(s),
}
}
}
/// Labelled statement nodes, as defined by the [spec].
///
/// The method [`Labelled::item`] doesn't return a [`Statement`] for compatibility reasons.
/// See [`LabelledItem`] for more information.
///
/// [spec]: https://tc39.es/ecma262/#sec-labelled-statements
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Labelled {
item: Box<LabelledItem>,
label: Sym,
}
impl Labelled {
/// Creates a new `Labelled` statement.
#[inline]
#[must_use]
pub fn new(item: LabelledItem, label: Sym) -> Self {
Self {
item: Box::new(item),
label,
}
}
/// Gets the labelled item.
#[inline]
#[must_use]
pub const fn item(&self) -> &LabelledItem {
&self.item
}
/// Gets the label name.
#[inline]
#[must_use]
pub const fn label(&self) -> Sym {
self.label
}
pub(crate) fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"{}: {}",
interner.resolve_expect(self.label),
self.item.to_indented_string(interner, indentation)
)
}
}
impl ToInternedString for Labelled {
fn to_interned_string(&self, interner: &Interner) -> String {
self.to_indented_string(interner, 0)
}
}
impl From<Labelled> for Statement {
fn from(labelled: Labelled) -> Self {
Self::Labelled(labelled)
}
}
impl VisitWith for Labelled {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_labelled_item(&self.item));
visitor.visit_sym(&self.label)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_labelled_item_mut(&mut self.item));
visitor.visit_sym_mut(&mut self.label)
}
}

View File

@@ -0,0 +1,241 @@
//! The [`Statement`] Parse Node, as defined by the [spec].
//!
//! ECMAScript [statements] are mainly composed of control flow operations, such as [`If`],
//! [`WhileLoop`], and [`Break`]. However, it also contains statements such as [`VarDeclaration`],
//! [`Block`] or [`Expression`] which are not strictly used for control flow.
//!
//! [spec]: https://tc39.es/ecma262/#prod-Statement
//! [statements]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements
mod block;
mod r#if;
mod labelled;
mod r#return;
mod switch;
mod throw;
mod r#try;
pub mod iteration;
pub use self::{
block::Block,
iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop},
labelled::{Labelled, LabelledItem},
r#if::If,
r#return::Return,
r#try::{Catch, ErrorHandler, Finally, Try},
switch::{Case, Switch},
throw::Throw,
};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use super::{declaration::VarDeclaration, expression::Expression};
/// The `Statement` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Statement {
/// See [`Block`].
Block(Block),
/// See [`VarDeclaration`]
Var(VarDeclaration),
/// An empty statement.
///
/// Empty statements do nothing, just return undefined.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-EmptyStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Empty
Empty,
/// See [`Expression`].
Expression(Expression),
/// See [`If`].
If(If),
/// See [`DoWhileLoop`].
DoWhileLoop(DoWhileLoop),
/// See [`WhileLoop`].
WhileLoop(WhileLoop),
/// See [`ForLoop`].
ForLoop(ForLoop),
/// See [`ForInLoop`].
ForInLoop(ForInLoop),
/// See [`ForOfLoop`].
ForOfLoop(ForOfLoop),
/// See[`Switch`].
Switch(Switch),
/// See [`Continue`].
Continue(Continue),
/// See [`Break`].
Break(Break),
/// See [`Return`].
Return(Return),
// TODO: Possibly add `with` statements.
/// See [`Labelled`].
Labelled(Labelled),
/// See [`Throw`].
Throw(Throw),
/// See [`Try`].
Try(Try),
}
impl Statement {
/// Implements the display formatting with indentation.
///
/// This will not prefix the value with any indentation. If you want to prefix this with proper
/// indents, use [`to_indented_string()`](Self::to_indented_string).
pub(super) fn to_no_indent_string(&self, interner: &Interner, indentation: usize) -> String {
let mut s = match self {
Self::Block(block) => return block.to_indented_string(interner, indentation),
Self::Var(var) => var.to_interned_string(interner),
Self::Empty => return ";".to_owned(),
Self::Expression(expr) => expr.to_indented_string(interner, indentation),
Self::If(if_smt) => return if_smt.to_indented_string(interner, indentation),
Self::DoWhileLoop(do_while) => do_while.to_indented_string(interner, indentation),
Self::WhileLoop(while_loop) => {
return while_loop.to_indented_string(interner, indentation)
}
Self::ForLoop(for_loop) => return for_loop.to_indented_string(interner, indentation),
Self::ForInLoop(for_in) => return for_in.to_indented_string(interner, indentation),
Self::ForOfLoop(for_of) => return for_of.to_indented_string(interner, indentation),
Self::Switch(switch) => return switch.to_indented_string(interner, indentation),
Self::Continue(cont) => cont.to_interned_string(interner),
Self::Break(break_smt) => break_smt.to_interned_string(interner),
Self::Return(ret) => ret.to_interned_string(interner),
Self::Labelled(labelled) => return labelled.to_interned_string(interner),
Self::Throw(throw) => throw.to_interned_string(interner),
Self::Try(try_catch) => return try_catch.to_indented_string(interner, indentation),
};
s.push(';');
s
}
/// Abstract operation [`IsLabelledFunction`][spec].
///
/// This recursively checks if this `Statement` is a labelled function, since adding
/// several labels in a function should not change the return value of the abstract operation:
///
/// ```Javascript
/// l1: l2: l3: l4: function f(){ }
/// ```
///
/// This should return `true` for that snippet.
///
/// [spec]: https://tc39.es/ecma262/#sec-islabelledfunction
#[inline]
#[must_use]
pub fn is_labelled_function(&self) -> bool {
match self {
Self::Labelled(stmt) => match stmt.item() {
LabelledItem::Function(_) => true,
LabelledItem::Statement(stmt) => stmt.is_labelled_function(),
},
_ => false,
}
}
}
impl ToIndentedString for Statement {
/// Creates a string of the value of the node with the given indentation. For example, an
/// indent level of 2 would produce this:
///
/// ```js
/// function hello() {
/// console.log("hello");
/// }
/// hello();
/// a = 2;
/// ```
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = match *self {
Self::Block(_) => String::new(),
_ => " ".repeat(indentation),
};
buf.push_str(&self.to_no_indent_string(interner, indentation));
buf
}
}
impl VisitWith for Statement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Block(b) => visitor.visit_block(b),
Self::Var(v) => visitor.visit_var_declaration(v),
Self::Empty => {
// do nothing; there is nothing to visit here
ControlFlow::Continue(())
}
Self::Expression(e) => visitor.visit_expression(e),
Self::If(i) => visitor.visit_if(i),
Self::DoWhileLoop(dw) => visitor.visit_do_while_loop(dw),
Self::WhileLoop(w) => visitor.visit_while_loop(w),
Self::ForLoop(f) => visitor.visit_for_loop(f),
Self::ForInLoop(fi) => visitor.visit_for_in_loop(fi),
Self::ForOfLoop(fo) => visitor.visit_for_of_loop(fo),
Self::Switch(s) => visitor.visit_switch(s),
Self::Continue(c) => visitor.visit_continue(c),
Self::Break(b) => visitor.visit_break(b),
Self::Return(r) => visitor.visit_return(r),
Self::Labelled(l) => visitor.visit_labelled(l),
Self::Throw(th) => visitor.visit_throw(th),
Self::Try(tr) => visitor.visit_try(tr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Block(b) => visitor.visit_block_mut(b),
Self::Var(v) => visitor.visit_var_declaration_mut(v),
Self::Empty => {
// do nothing; there is nothing to visit here
ControlFlow::Continue(())
}
Self::Expression(e) => visitor.visit_expression_mut(e),
Self::If(i) => visitor.visit_if_mut(i),
Self::DoWhileLoop(dw) => visitor.visit_do_while_loop_mut(dw),
Self::WhileLoop(w) => visitor.visit_while_loop_mut(w),
Self::ForLoop(f) => visitor.visit_for_loop_mut(f),
Self::ForInLoop(fi) => visitor.visit_for_in_loop_mut(fi),
Self::ForOfLoop(fo) => visitor.visit_for_of_loop_mut(fo),
Self::Switch(s) => visitor.visit_switch_mut(s),
Self::Continue(c) => visitor.visit_continue_mut(c),
Self::Break(b) => visitor.visit_break_mut(b),
Self::Return(r) => visitor.visit_return_mut(r),
Self::Labelled(l) => visitor.visit_labelled_mut(l),
Self::Throw(th) => visitor.visit_throw_mut(th),
Self::Try(tr) => visitor.visit_try_mut(tr),
}
}
}

View File

@@ -0,0 +1,84 @@
use crate::{
expression::Expression,
statement::Statement,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `return` statement ends function execution and specifies a value to be returned to the
/// function caller.
///
/// Syntax: `return [expression];`
///
/// `expression`:
/// > The expression whose value is to be returned. If omitted, `undefined` is returned instead.
///
/// When a `return` statement is used in a function body, the execution of the function is
/// stopped. If specified, a given value is returned to the function caller.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Return {
target: Option<Expression>,
}
impl Return {
/// Gets the target expression value of this `Return` statement.
#[must_use]
pub const fn target(&self) -> Option<&Expression> {
self.target.as_ref()
}
/// Creates a `Return` AST node.
#[must_use]
pub const fn new(expression: Option<Expression>) -> Self {
Self { target: expression }
}
}
impl From<Return> for Statement {
fn from(return_smt: Return) -> Self {
Self::Return(return_smt)
}
}
impl ToInternedString for Return {
fn to_interned_string(&self, interner: &Interner) -> String {
self.target().map_or_else(
|| "return".to_owned(),
|ex| format!("return {}", ex.to_interned_string(interner)),
)
}
}
impl VisitWith for Return {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(expr) = &self.target {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(expr) = &mut self.target {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,189 @@
//! Switch node.
//!
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
StatementList,
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A case clause inside a [`Switch`] statement, as defined by the [spec].
///
/// Even though every [`Case`] body is a [`StatementList`], it doesn't create a new lexical
/// environment. This means any variable declared in a `Case` will be considered as part of the
/// lexical environment of the parent [`Switch`] block.
///
/// [spec]: https://tc39.es/ecma262/#prod-CaseClause
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Case {
condition: Expression,
body: StatementList,
}
impl Case {
/// Creates a `Case` AST node.
#[inline]
#[must_use]
pub const fn new(condition: Expression, body: StatementList) -> Self {
Self { condition, body }
}
/// Gets the condition of the case.
#[inline]
#[must_use]
pub const fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the statement listin the body of the case.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
}
impl VisitWith for Case {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
visitor.visit_statement_list_mut(&mut self.body)
}
}
/// The `switch` statement evaluates an expression, matching the expression's value to a case
/// clause, and executes statements associated with that case, as well as statements in cases
/// that follow the matching case.
///
/// A `switch` statement first evaluates its expression. It then looks for the first case
/// clause whose expression evaluates to the same value as the result of the input expression
/// (using the strict comparison, `===`) and transfers control to that clause, executing the
/// associated statements. (If multiple cases match the provided value, the first case that
/// matches is selected, even if the cases are not equal to each other.)
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Switch {
val: Expression,
cases: Box<[Case]>,
default: Option<StatementList>,
}
impl Switch {
/// Creates a `Switch` AST node.
#[inline]
#[must_use]
pub fn new(val: Expression, cases: Box<[Case]>, default: Option<StatementList>) -> Self {
Self {
val,
cases,
default,
}
}
/// Gets the value to switch.
#[inline]
#[must_use]
pub const fn val(&self) -> &Expression {
&self.val
}
/// Gets the list of cases for the switch statement.
#[inline]
#[must_use]
pub const fn cases(&self) -> &[Case] {
&self.cases
}
/// Gets the default statement list, if any.
#[inline]
#[must_use]
pub const fn default(&self) -> Option<&StatementList> {
self.default.as_ref()
}
}
impl ToIndentedString for Switch {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let indent = " ".repeat(indentation);
let mut buf = format!("switch ({}) {{\n", self.val().to_interned_string(interner));
for e in self.cases().iter() {
buf.push_str(&format!(
"{} case {}:\n{}",
indent,
e.condition().to_interned_string(interner),
e.body().to_indented_string(interner, indentation + 2)
));
}
if let Some(ref default) = self.default {
buf.push_str(&format!(
"{indent} default:\n{}",
default.to_indented_string(interner, indentation + 2)
));
}
buf.push_str(&format!("{indent}}}"));
buf
}
}
impl From<Switch> for Statement {
#[inline]
fn from(switch: Switch) -> Self {
Self::Switch(switch)
}
}
impl VisitWith for Switch {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.val));
for case in self.cases.iter() {
try_break!(visitor.visit_case(case));
}
if let Some(sl) = &self.default {
try_break!(visitor.visit_statement_list(sl));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.val));
for case in self.cases.iter_mut() {
try_break!(visitor.visit_case_mut(case));
}
if let Some(sl) = &mut self.default {
try_break!(visitor.visit_statement_list_mut(sl));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,70 @@
use crate::{
statement::Statement,
visitor::{VisitWith, Visitor, VisitorMut},
Expression,
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `throw` statement throws a user-defined exception.
///
/// Syntax: `throw expression;`
///
/// Execution of the current function will stop (the statements after throw won't be executed),
/// and control will be passed to the first catch block in the call stack. If no catch block
/// exists among caller functions, the program will terminate.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ThrowStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Throw {
target: Expression,
}
impl Throw {
/// Gets the target expression of this `Throw` statement.
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Creates a `Throw` AST node.
#[must_use]
pub const fn new(target: Expression) -> Self {
Self { target }
}
}
impl ToInternedString for Throw {
fn to_interned_string(&self, interner: &Interner) -> String {
format!("throw {}", self.target.to_interned_string(interner))
}
}
impl From<Throw> for Statement {
fn from(trw: Throw) -> Self {
Self::Throw(trw)
}
}
impl VisitWith for Throw {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,256 @@
//! Error handling statements
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
declaration::Binding,
statement::{Block, Statement},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `try...catch` statement marks a block of statements to try and specifies a response
/// should an exception be thrown.
///
/// The `try` statement consists of a `try`-block, which contains one or more statements. `{}`
/// must always be used, even for single statements. At least one `catch`-block, or a
/// `finally`-block, must be present.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-TryStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Try {
block: Block,
handler: ErrorHandler,
}
/// The type of error handler in a [`Try`] statement.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ErrorHandler {
/// A [`Catch`] error handler.
Catch(Catch),
/// A [`Finally`] error handler.
Finally(Finally),
/// A [`Catch`] and [`Finally`] error handler.
Full(Catch, Finally),
}
impl Try {
/// Creates a new `Try` AST node.
#[inline]
#[must_use]
pub const fn new(block: Block, handler: ErrorHandler) -> Self {
Self { block, handler }
}
/// Gets the `try` block.
#[inline]
#[must_use]
pub const fn block(&self) -> &Block {
&self.block
}
/// Gets the `catch` block, if any.
#[inline]
#[must_use]
pub const fn catch(&self) -> Option<&Catch> {
match &self.handler {
ErrorHandler::Catch(c) | ErrorHandler::Full(c, _) => Some(c),
ErrorHandler::Finally(_) => None,
}
}
/// Gets the `finally` block, if any.
#[inline]
#[must_use]
pub const fn finally(&self) -> Option<&Finally> {
match &self.handler {
ErrorHandler::Finally(f) | ErrorHandler::Full(_, f) => Some(f),
ErrorHandler::Catch(_) => None,
}
}
}
impl ToIndentedString for Try {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!(
"{}try {}",
" ".repeat(indentation),
self.block.to_indented_string(interner, indentation)
);
if let Some(catch) = self.catch() {
buf.push_str(&catch.to_indented_string(interner, indentation));
}
if let Some(finally) = self.finally() {
buf.push_str(&finally.to_indented_string(interner, indentation));
}
buf
}
}
impl From<Try> for Statement {
#[inline]
fn from(try_catch: Try) -> Self {
Self::Try(try_catch)
}
}
impl VisitWith for Try {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_block(&self.block));
if let Some(catch) = &self.catch() {
try_break!(visitor.visit_catch(catch));
}
if let Some(finally) = &self.finally() {
try_break!(visitor.visit_finally(finally));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_block_mut(&mut self.block));
match &mut self.handler {
ErrorHandler::Catch(c) => try_break!(visitor.visit_catch_mut(c)),
ErrorHandler::Finally(f) => try_break!(visitor.visit_finally_mut(f)),
ErrorHandler::Full(c, f) => {
try_break!(visitor.visit_catch_mut(c));
try_break!(visitor.visit_finally_mut(f));
}
}
ControlFlow::Continue(())
}
}
/// Catch block.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Catch {
parameter: Option<Binding>,
block: Block,
}
impl Catch {
/// Creates a new catch block.
#[inline]
#[must_use]
pub const fn new(parameter: Option<Binding>, block: Block) -> Self {
Self { parameter, block }
}
/// Gets the parameter of the catch block.
#[inline]
#[must_use]
pub const fn parameter(&self) -> Option<&Binding> {
self.parameter.as_ref()
}
/// Retrieves the catch execution block.
#[inline]
#[must_use]
pub const fn block(&self) -> &Block {
&self.block
}
}
impl ToIndentedString for Catch {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = " catch".to_owned();
if let Some(ref param) = self.parameter {
buf.push_str(&format!("({})", param.to_interned_string(interner)));
}
buf.push_str(&format!(
" {}",
self.block.to_indented_string(interner, indentation)
));
buf
}
}
impl VisitWith for Catch {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(binding) = &self.parameter {
try_break!(visitor.visit_binding(binding));
}
visitor.visit_block(&self.block)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(binding) = &mut self.parameter {
try_break!(visitor.visit_binding_mut(binding));
}
visitor.visit_block_mut(&mut self.block)
}
}
/// Finally block.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Finally {
block: Block,
}
impl Finally {
/// Gets the finally block.
#[inline]
#[must_use]
pub const fn block(&self) -> &Block {
&self.block
}
}
impl ToIndentedString for Finally {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
" finally {}",
self.block.to_indented_string(interner, indentation)
)
}
}
impl From<Block> for Finally {
#[inline]
fn from(block: Block) -> Self {
Self { block }
}
}
impl VisitWith for Finally {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_block(&self.block)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_block_mut(&mut self.block)
}
}

View File

@@ -0,0 +1,215 @@
//! Statement list node.
use super::Declaration;
use crate::{
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use std::cmp::Ordering;
/// An item inside a [`StatementList`] Parse Node, as defined by the [spec].
///
/// Items in a `StatementList` can be either [`Declaration`]s (functions, classes, let/const declarations)
/// or [`Statement`]s (if, while, var statement).
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementListItem
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum StatementListItem {
/// See [`Statement`].
Statement(Statement),
/// See [`Declaration`].
Declaration(Declaration),
}
impl StatementListItem {
/// Returns a node ordering based on the hoistability of each statement.
#[must_use]
pub const fn hoistable_order(a: &Self, b: &Self) -> Ordering {
match (a, b) {
(
Self::Declaration(Declaration::Function(_)),
Self::Declaration(Declaration::Function(_)),
) => Ordering::Equal,
(_, Self::Declaration(Declaration::Function(_))) => Ordering::Greater,
(Self::Declaration(Declaration::Function(_)), _) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
}
impl ToIndentedString for StatementListItem {
/// Creates a string of the value of the node with the given indentation. For example, an
/// indent level of 2 would produce this:
///
/// ```js
/// function hello() {
/// console.log("hello");
/// }
/// hello();
/// a = 2;
/// ```
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = " ".repeat(indentation);
match self {
Self::Statement(stmt) => {
buf.push_str(&stmt.to_no_indent_string(interner, indentation));
}
Self::Declaration(decl) => {
buf.push_str(&decl.to_indented_string(interner, indentation));
}
}
buf
}
}
impl From<Statement> for StatementListItem {
#[inline]
fn from(stmt: Statement) -> Self {
Self::Statement(stmt)
}
}
impl From<Declaration> for StatementListItem {
#[inline]
fn from(decl: Declaration) -> Self {
Self::Declaration(decl)
}
}
impl VisitWith for StatementListItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Statement(statement) => visitor.visit_statement(statement),
Self::Declaration(declaration) => visitor.visit_declaration(declaration),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Statement(statement) => visitor.visit_statement_mut(statement),
Self::Declaration(declaration) => visitor.visit_declaration_mut(declaration),
}
}
}
/// List of statements.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StatementList {
statements: Box<[StatementListItem]>,
strict: bool,
}
impl StatementList {
/// Creates a new `StatementList` AST node.
#[must_use]
pub fn new<S>(statements: S, strict: bool) -> Self
where
S: Into<Box<[StatementListItem]>>,
{
Self {
statements: statements.into(),
strict,
}
}
/// Gets the list of statements.
#[inline]
#[must_use]
pub const fn statements(&self) -> &[StatementListItem] {
&self.statements
}
/// Get the strict mode.
#[inline]
#[must_use]
pub const fn strict(&self) -> bool {
self.strict
}
}
impl From<Box<[StatementListItem]>> for StatementList {
#[inline]
fn from(stm: Box<[StatementListItem]>) -> Self {
Self {
statements: stm,
strict: false,
}
}
}
impl From<Vec<StatementListItem>> for StatementList {
#[inline]
fn from(stm: Vec<StatementListItem>) -> Self {
Self {
statements: stm.into(),
strict: false,
}
}
}
impl ToIndentedString for StatementList {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = String::new();
// Print statements
for item in self.statements.iter() {
// We rely on the node to add the correct indent.
buf.push_str(&item.to_indented_string(interner, indentation));
buf.push('\n');
}
buf
}
}
impl VisitWith for StatementList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for statement in self.statements.iter() {
try_break!(visitor.visit_statement_list_item(statement));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for statement in self.statements.iter_mut() {
try_break!(visitor.visit_statement_list_item_mut(statement));
}
ControlFlow::Continue(())
}
}
#[cfg(feature = "fuzz")]
impl<'a> arbitrary::Arbitrary<'a> for StatementList {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
statements: u.arbitrary()?,
strict: false, // disable strictness; this is *not* in source data
})
}
}

View File

@@ -0,0 +1,569 @@
//! ECMAScript Abstract Syntax Tree visitors.
//!
//! This module contains visitors which can be used to inspect or modify AST nodes. This allows for
//! fine-grained manipulation of ASTs for analysis, rewriting, or instrumentation.
use std::ops::ControlFlow;
use crate::{
declaration::{
Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList,
},
expression::{
access::{
PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess,
SuperPropertyAccess,
},
literal::{ArrayLiteral, Literal, ObjectLiteral, TemplateElement, TemplateLiteral},
operator::{
assign::{Assign, AssignTarget},
Binary, Conditional, Unary,
},
Await, Call, Expression, Identifier, New, Optional, OptionalOperation,
OptionalOperationKind, Spread, SuperCall, TaggedTemplate, Yield,
},
function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
FormalParameter, FormalParameterList, Function, Generator, PrivateName,
},
pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern},
property::{MethodDefinition, PropertyDefinition, PropertyName},
statement::{
iteration::{
Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop,
IterableLoopInitializer, WhileLoop,
},
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw,
Try,
},
StatementList, StatementListItem,
};
use boa_interner::Sym;
/// `Try`-like conditional unwrapping of `ControlFlow`.
#[macro_export]
macro_rules! try_break {
($expr:expr) => {
match $expr {
core::ops::ControlFlow::Continue(c) => c,
core::ops::ControlFlow::Break(b) => return core::ops::ControlFlow::Break(b),
}
};
}
/// Creates the default visit function implementation for a particular type
macro_rules! define_visit {
($fn_name:ident, $type_name:ident) => {
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor")]
fn $fn_name(&mut self, node: &'ast $type_name) -> ControlFlow<Self::BreakTy> {
node.visit_with(self)
}
};
}
/// Creates the default mutable visit function implementation for a particular type
macro_rules! define_visit_mut {
($fn_name:ident, $type_name:ident) => {
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor, mutably")]
fn $fn_name(&mut self, node: &'ast mut $type_name) -> ControlFlow<Self::BreakTy> {
node.visit_with_mut(self)
}
};
}
/// Generates the `NodeRef` and `NodeMutRef` enums from a list of variants.
macro_rules! node_ref {
(
$(
$Variant:ident
),*
$(,)?
) => {
/// A reference to a node visitable by a [`Visitor`].
#[derive(Debug, Clone, Copy)]
#[allow(missing_docs)]
pub enum NodeRef<'a> {
$(
$Variant(&'a $Variant)
),*
}
$(
impl<'a> From<&'a $Variant> for NodeRef<'a> {
fn from(node: &'a $Variant) -> NodeRef<'a> {
Self::$Variant(node)
}
}
)*
/// A mutable reference to a node visitable by a [`VisitorMut`].
#[derive(Debug)]
#[allow(missing_docs)]
pub enum NodeRefMut<'a> {
$(
$Variant(&'a mut $Variant)
),*
}
$(
impl<'a> From<&'a mut $Variant> for NodeRefMut<'a> {
fn from(node: &'a mut $Variant) -> NodeRefMut<'a> {
Self::$Variant(node)
}
}
)*
}
}
node_ref! {
StatementList,
StatementListItem,
Statement,
Declaration,
Function,
Generator,
AsyncFunction,
AsyncGenerator,
Class,
LexicalDeclaration,
Block,
VarDeclaration,
Expression,
If,
DoWhileLoop,
WhileLoop,
ForLoop,
ForInLoop,
ForOfLoop,
Switch,
Continue,
Break,
Return,
Labelled,
Throw,
Try,
Identifier,
FormalParameterList,
ClassElement,
PrivateName,
VariableList,
Variable,
Binding,
Pattern,
Literal,
ArrayLiteral,
ObjectLiteral,
Spread,
ArrowFunction,
AsyncArrowFunction,
TemplateLiteral,
PropertyAccess,
New,
Call,
SuperCall,
Optional,
TaggedTemplate,
Assign,
Unary,
Binary,
Conditional,
Await,
Yield,
ForLoopInitializer,
IterableLoopInitializer,
Case,
Sym,
LabelledItem,
Catch,
Finally,
FormalParameter,
PropertyName,
MethodDefinition,
ObjectPattern,
ArrayPattern,
PropertyDefinition,
TemplateElement,
SimplePropertyAccess,
PrivatePropertyAccess,
SuperPropertyAccess,
OptionalOperation,
AssignTarget,
ObjectPatternElement,
ArrayPatternElement,
PropertyAccessField,
OptionalOperationKind,
}
/// Represents an AST visitor.
///
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
/// visitor pattern.
pub trait Visitor<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early.
type BreakTy;
define_visit!(visit_statement_list, StatementList);
define_visit!(visit_statement_list_item, StatementListItem);
define_visit!(visit_statement, Statement);
define_visit!(visit_declaration, Declaration);
define_visit!(visit_function, Function);
define_visit!(visit_generator, Generator);
define_visit!(visit_async_function, AsyncFunction);
define_visit!(visit_async_generator, AsyncGenerator);
define_visit!(visit_class, Class);
define_visit!(visit_lexical_declaration, LexicalDeclaration);
define_visit!(visit_block, Block);
define_visit!(visit_var_declaration, VarDeclaration);
define_visit!(visit_expression, Expression);
define_visit!(visit_if, If);
define_visit!(visit_do_while_loop, DoWhileLoop);
define_visit!(visit_while_loop, WhileLoop);
define_visit!(visit_for_loop, ForLoop);
define_visit!(visit_for_in_loop, ForInLoop);
define_visit!(visit_for_of_loop, ForOfLoop);
define_visit!(visit_switch, Switch);
define_visit!(visit_continue, Continue);
define_visit!(visit_break, Break);
define_visit!(visit_return, Return);
define_visit!(visit_labelled, Labelled);
define_visit!(visit_throw, Throw);
define_visit!(visit_try, Try);
define_visit!(visit_identifier, Identifier);
define_visit!(visit_formal_parameter_list, FormalParameterList);
define_visit!(visit_class_element, ClassElement);
define_visit!(visit_private_name, PrivateName);
define_visit!(visit_variable_list, VariableList);
define_visit!(visit_variable, Variable);
define_visit!(visit_binding, Binding);
define_visit!(visit_pattern, Pattern);
define_visit!(visit_literal, Literal);
define_visit!(visit_array_literal, ArrayLiteral);
define_visit!(visit_object_literal, ObjectLiteral);
define_visit!(visit_spread, Spread);
define_visit!(visit_arrow_function, ArrowFunction);
define_visit!(visit_async_arrow_function, AsyncArrowFunction);
define_visit!(visit_template_literal, TemplateLiteral);
define_visit!(visit_property_access, PropertyAccess);
define_visit!(visit_new, New);
define_visit!(visit_call, Call);
define_visit!(visit_super_call, SuperCall);
define_visit!(visit_optional, Optional);
define_visit!(visit_tagged_template, TaggedTemplate);
define_visit!(visit_assign, Assign);
define_visit!(visit_unary, Unary);
define_visit!(visit_binary, Binary);
define_visit!(visit_conditional, Conditional);
define_visit!(visit_await, Await);
define_visit!(visit_yield, Yield);
define_visit!(visit_for_loop_initializer, ForLoopInitializer);
define_visit!(visit_iterable_loop_initializer, IterableLoopInitializer);
define_visit!(visit_case, Case);
define_visit!(visit_sym, Sym);
define_visit!(visit_labelled_item, LabelledItem);
define_visit!(visit_catch, Catch);
define_visit!(visit_finally, Finally);
define_visit!(visit_formal_parameter, FormalParameter);
define_visit!(visit_property_name, PropertyName);
define_visit!(visit_method_definition, MethodDefinition);
define_visit!(visit_object_pattern, ObjectPattern);
define_visit!(visit_array_pattern, ArrayPattern);
define_visit!(visit_property_definition, PropertyDefinition);
define_visit!(visit_template_element, TemplateElement);
define_visit!(visit_simple_property_access, SimplePropertyAccess);
define_visit!(visit_private_property_access, PrivatePropertyAccess);
define_visit!(visit_super_property_access, SuperPropertyAccess);
define_visit!(visit_optional_operation, OptionalOperation);
define_visit!(visit_assign_target, AssignTarget);
define_visit!(visit_object_pattern_element, ObjectPatternElement);
define_visit!(visit_array_pattern_element, ArrayPatternElement);
define_visit!(visit_property_access_field, PropertyAccessField);
define_visit!(visit_optional_operation_kind, OptionalOperationKind);
/// Generic entry point for a node that is visitable by a `Visitor`.
///
/// This is usually used for generic functions that need to visit an unnamed AST node.
fn visit<N: Into<NodeRef<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
let node = node.into();
match node {
NodeRef::StatementList(n) => self.visit_statement_list(n),
NodeRef::StatementListItem(n) => self.visit_statement_list_item(n),
NodeRef::Statement(n) => self.visit_statement(n),
NodeRef::Declaration(n) => self.visit_declaration(n),
NodeRef::Function(n) => self.visit_function(n),
NodeRef::Generator(n) => self.visit_generator(n),
NodeRef::AsyncFunction(n) => self.visit_async_function(n),
NodeRef::AsyncGenerator(n) => self.visit_async_generator(n),
NodeRef::Class(n) => self.visit_class(n),
NodeRef::LexicalDeclaration(n) => self.visit_lexical_declaration(n),
NodeRef::Block(n) => self.visit_block(n),
NodeRef::VarDeclaration(n) => self.visit_var_declaration(n),
NodeRef::Expression(n) => self.visit_expression(n),
NodeRef::If(n) => self.visit_if(n),
NodeRef::DoWhileLoop(n) => self.visit_do_while_loop(n),
NodeRef::WhileLoop(n) => self.visit_while_loop(n),
NodeRef::ForLoop(n) => self.visit_for_loop(n),
NodeRef::ForInLoop(n) => self.visit_for_in_loop(n),
NodeRef::ForOfLoop(n) => self.visit_for_of_loop(n),
NodeRef::Switch(n) => self.visit_switch(n),
NodeRef::Continue(n) => self.visit_continue(n),
NodeRef::Break(n) => self.visit_break(n),
NodeRef::Return(n) => self.visit_return(n),
NodeRef::Labelled(n) => self.visit_labelled(n),
NodeRef::Throw(n) => self.visit_throw(n),
NodeRef::Try(n) => self.visit_try(n),
NodeRef::Identifier(n) => self.visit_identifier(n),
NodeRef::FormalParameterList(n) => self.visit_formal_parameter_list(n),
NodeRef::ClassElement(n) => self.visit_class_element(n),
NodeRef::PrivateName(n) => self.visit_private_name(n),
NodeRef::VariableList(n) => self.visit_variable_list(n),
NodeRef::Variable(n) => self.visit_variable(n),
NodeRef::Binding(n) => self.visit_binding(n),
NodeRef::Pattern(n) => self.visit_pattern(n),
NodeRef::Literal(n) => self.visit_literal(n),
NodeRef::ArrayLiteral(n) => self.visit_array_literal(n),
NodeRef::ObjectLiteral(n) => self.visit_object_literal(n),
NodeRef::Spread(n) => self.visit_spread(n),
NodeRef::ArrowFunction(n) => self.visit_arrow_function(n),
NodeRef::AsyncArrowFunction(n) => self.visit_async_arrow_function(n),
NodeRef::TemplateLiteral(n) => self.visit_template_literal(n),
NodeRef::PropertyAccess(n) => self.visit_property_access(n),
NodeRef::New(n) => self.visit_new(n),
NodeRef::Call(n) => self.visit_call(n),
NodeRef::SuperCall(n) => self.visit_super_call(n),
NodeRef::Optional(n) => self.visit_optional(n),
NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n),
NodeRef::Assign(n) => self.visit_assign(n),
NodeRef::Unary(n) => self.visit_unary(n),
NodeRef::Binary(n) => self.visit_binary(n),
NodeRef::Conditional(n) => self.visit_conditional(n),
NodeRef::Await(n) => self.visit_await(n),
NodeRef::Yield(n) => self.visit_yield(n),
NodeRef::ForLoopInitializer(n) => self.visit_for_loop_initializer(n),
NodeRef::IterableLoopInitializer(n) => self.visit_iterable_loop_initializer(n),
NodeRef::Case(n) => self.visit_case(n),
NodeRef::Sym(n) => self.visit_sym(n),
NodeRef::LabelledItem(n) => self.visit_labelled_item(n),
NodeRef::Catch(n) => self.visit_catch(n),
NodeRef::Finally(n) => self.visit_finally(n),
NodeRef::FormalParameter(n) => self.visit_formal_parameter(n),
NodeRef::PropertyName(n) => self.visit_property_name(n),
NodeRef::MethodDefinition(n) => self.visit_method_definition(n),
NodeRef::ObjectPattern(n) => self.visit_object_pattern(n),
NodeRef::ArrayPattern(n) => self.visit_array_pattern(n),
NodeRef::PropertyDefinition(n) => self.visit_property_definition(n),
NodeRef::TemplateElement(n) => self.visit_template_element(n),
NodeRef::SimplePropertyAccess(n) => self.visit_simple_property_access(n),
NodeRef::PrivatePropertyAccess(n) => self.visit_private_property_access(n),
NodeRef::SuperPropertyAccess(n) => self.visit_super_property_access(n),
NodeRef::OptionalOperation(n) => self.visit_optional_operation(n),
NodeRef::AssignTarget(n) => self.visit_assign_target(n),
NodeRef::ObjectPatternElement(n) => self.visit_object_pattern_element(n),
NodeRef::ArrayPatternElement(n) => self.visit_array_pattern_element(n),
NodeRef::PropertyAccessField(n) => self.visit_property_access_field(n),
NodeRef::OptionalOperationKind(n) => self.visit_optional_operation_kind(n),
}
}
}
/// Represents an AST visitor which can modify AST content.
///
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
/// visitor pattern.
pub trait VisitorMut<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early.
type BreakTy;
define_visit_mut!(visit_statement_list_mut, StatementList);
define_visit_mut!(visit_statement_list_item_mut, StatementListItem);
define_visit_mut!(visit_statement_mut, Statement);
define_visit_mut!(visit_declaration_mut, Declaration);
define_visit_mut!(visit_function_mut, Function);
define_visit_mut!(visit_generator_mut, Generator);
define_visit_mut!(visit_async_function_mut, AsyncFunction);
define_visit_mut!(visit_async_generator_mut, AsyncGenerator);
define_visit_mut!(visit_class_mut, Class);
define_visit_mut!(visit_lexical_declaration_mut, LexicalDeclaration);
define_visit_mut!(visit_block_mut, Block);
define_visit_mut!(visit_var_declaration_mut, VarDeclaration);
define_visit_mut!(visit_expression_mut, Expression);
define_visit_mut!(visit_if_mut, If);
define_visit_mut!(visit_do_while_loop_mut, DoWhileLoop);
define_visit_mut!(visit_while_loop_mut, WhileLoop);
define_visit_mut!(visit_for_loop_mut, ForLoop);
define_visit_mut!(visit_for_in_loop_mut, ForInLoop);
define_visit_mut!(visit_for_of_loop_mut, ForOfLoop);
define_visit_mut!(visit_switch_mut, Switch);
define_visit_mut!(visit_continue_mut, Continue);
define_visit_mut!(visit_break_mut, Break);
define_visit_mut!(visit_return_mut, Return);
define_visit_mut!(visit_labelled_mut, Labelled);
define_visit_mut!(visit_throw_mut, Throw);
define_visit_mut!(visit_try_mut, Try);
define_visit_mut!(visit_identifier_mut, Identifier);
define_visit_mut!(visit_formal_parameter_list_mut, FormalParameterList);
define_visit_mut!(visit_class_element_mut, ClassElement);
define_visit_mut!(visit_private_name_mut, PrivateName);
define_visit_mut!(visit_variable_list_mut, VariableList);
define_visit_mut!(visit_variable_mut, Variable);
define_visit_mut!(visit_binding_mut, Binding);
define_visit_mut!(visit_pattern_mut, Pattern);
define_visit_mut!(visit_literal_mut, Literal);
define_visit_mut!(visit_array_literal_mut, ArrayLiteral);
define_visit_mut!(visit_object_literal_mut, ObjectLiteral);
define_visit_mut!(visit_spread_mut, Spread);
define_visit_mut!(visit_arrow_function_mut, ArrowFunction);
define_visit_mut!(visit_async_arrow_function_mut, AsyncArrowFunction);
define_visit_mut!(visit_template_literal_mut, TemplateLiteral);
define_visit_mut!(visit_property_access_mut, PropertyAccess);
define_visit_mut!(visit_new_mut, New);
define_visit_mut!(visit_call_mut, Call);
define_visit_mut!(visit_super_call_mut, SuperCall);
define_visit_mut!(visit_optional_mut, Optional);
define_visit_mut!(visit_tagged_template_mut, TaggedTemplate);
define_visit_mut!(visit_assign_mut, Assign);
define_visit_mut!(visit_unary_mut, Unary);
define_visit_mut!(visit_binary_mut, Binary);
define_visit_mut!(visit_conditional_mut, Conditional);
define_visit_mut!(visit_await_mut, Await);
define_visit_mut!(visit_yield_mut, Yield);
define_visit_mut!(visit_for_loop_initializer_mut, ForLoopInitializer);
define_visit_mut!(visit_iterable_loop_initializer_mut, IterableLoopInitializer);
define_visit_mut!(visit_case_mut, Case);
define_visit_mut!(visit_sym_mut, Sym);
define_visit_mut!(visit_labelled_item_mut, LabelledItem);
define_visit_mut!(visit_catch_mut, Catch);
define_visit_mut!(visit_finally_mut, Finally);
define_visit_mut!(visit_formal_parameter_mut, FormalParameter);
define_visit_mut!(visit_property_name_mut, PropertyName);
define_visit_mut!(visit_method_definition_mut, MethodDefinition);
define_visit_mut!(visit_object_pattern_mut, ObjectPattern);
define_visit_mut!(visit_array_pattern_mut, ArrayPattern);
define_visit_mut!(visit_property_definition_mut, PropertyDefinition);
define_visit_mut!(visit_template_element_mut, TemplateElement);
define_visit_mut!(visit_simple_property_access_mut, SimplePropertyAccess);
define_visit_mut!(visit_private_property_access_mut, PrivatePropertyAccess);
define_visit_mut!(visit_super_property_access_mut, SuperPropertyAccess);
define_visit_mut!(visit_optional_operation_mut, OptionalOperation);
define_visit_mut!(visit_assign_target_mut, AssignTarget);
define_visit_mut!(visit_object_pattern_element_mut, ObjectPatternElement);
define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement);
define_visit_mut!(visit_property_access_field_mut, PropertyAccessField);
define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind);
/// Generic entry point for a node that is visitable by a `VisitorMut`.
///
/// This is usually used for generic functions that need to visit an unnamed AST node.
fn visit<N: Into<NodeRefMut<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
let node = node.into();
match node {
NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n),
NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n),
NodeRefMut::Statement(n) => self.visit_statement_mut(n),
NodeRefMut::Declaration(n) => self.visit_declaration_mut(n),
NodeRefMut::Function(n) => self.visit_function_mut(n),
NodeRefMut::Generator(n) => self.visit_generator_mut(n),
NodeRefMut::AsyncFunction(n) => self.visit_async_function_mut(n),
NodeRefMut::AsyncGenerator(n) => self.visit_async_generator_mut(n),
NodeRefMut::Class(n) => self.visit_class_mut(n),
NodeRefMut::LexicalDeclaration(n) => self.visit_lexical_declaration_mut(n),
NodeRefMut::Block(n) => self.visit_block_mut(n),
NodeRefMut::VarDeclaration(n) => self.visit_var_declaration_mut(n),
NodeRefMut::Expression(n) => self.visit_expression_mut(n),
NodeRefMut::If(n) => self.visit_if_mut(n),
NodeRefMut::DoWhileLoop(n) => self.visit_do_while_loop_mut(n),
NodeRefMut::WhileLoop(n) => self.visit_while_loop_mut(n),
NodeRefMut::ForLoop(n) => self.visit_for_loop_mut(n),
NodeRefMut::ForInLoop(n) => self.visit_for_in_loop_mut(n),
NodeRefMut::ForOfLoop(n) => self.visit_for_of_loop_mut(n),
NodeRefMut::Switch(n) => self.visit_switch_mut(n),
NodeRefMut::Continue(n) => self.visit_continue_mut(n),
NodeRefMut::Break(n) => self.visit_break_mut(n),
NodeRefMut::Return(n) => self.visit_return_mut(n),
NodeRefMut::Labelled(n) => self.visit_labelled_mut(n),
NodeRefMut::Throw(n) => self.visit_throw_mut(n),
NodeRefMut::Try(n) => self.visit_try_mut(n),
NodeRefMut::Identifier(n) => self.visit_identifier_mut(n),
NodeRefMut::FormalParameterList(n) => self.visit_formal_parameter_list_mut(n),
NodeRefMut::ClassElement(n) => self.visit_class_element_mut(n),
NodeRefMut::PrivateName(n) => self.visit_private_name_mut(n),
NodeRefMut::VariableList(n) => self.visit_variable_list_mut(n),
NodeRefMut::Variable(n) => self.visit_variable_mut(n),
NodeRefMut::Binding(n) => self.visit_binding_mut(n),
NodeRefMut::Pattern(n) => self.visit_pattern_mut(n),
NodeRefMut::Literal(n) => self.visit_literal_mut(n),
NodeRefMut::ArrayLiteral(n) => self.visit_array_literal_mut(n),
NodeRefMut::ObjectLiteral(n) => self.visit_object_literal_mut(n),
NodeRefMut::Spread(n) => self.visit_spread_mut(n),
NodeRefMut::ArrowFunction(n) => self.visit_arrow_function_mut(n),
NodeRefMut::AsyncArrowFunction(n) => self.visit_async_arrow_function_mut(n),
NodeRefMut::TemplateLiteral(n) => self.visit_template_literal_mut(n),
NodeRefMut::PropertyAccess(n) => self.visit_property_access_mut(n),
NodeRefMut::New(n) => self.visit_new_mut(n),
NodeRefMut::Call(n) => self.visit_call_mut(n),
NodeRefMut::SuperCall(n) => self.visit_super_call_mut(n),
NodeRefMut::Optional(n) => self.visit_optional_mut(n),
NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n),
NodeRefMut::Assign(n) => self.visit_assign_mut(n),
NodeRefMut::Unary(n) => self.visit_unary_mut(n),
NodeRefMut::Binary(n) => self.visit_binary_mut(n),
NodeRefMut::Conditional(n) => self.visit_conditional_mut(n),
NodeRefMut::Await(n) => self.visit_await_mut(n),
NodeRefMut::Yield(n) => self.visit_yield_mut(n),
NodeRefMut::ForLoopInitializer(n) => self.visit_for_loop_initializer_mut(n),
NodeRefMut::IterableLoopInitializer(n) => self.visit_iterable_loop_initializer_mut(n),
NodeRefMut::Case(n) => self.visit_case_mut(n),
NodeRefMut::Sym(n) => self.visit_sym_mut(n),
NodeRefMut::LabelledItem(n) => self.visit_labelled_item_mut(n),
NodeRefMut::Catch(n) => self.visit_catch_mut(n),
NodeRefMut::Finally(n) => self.visit_finally_mut(n),
NodeRefMut::FormalParameter(n) => self.visit_formal_parameter_mut(n),
NodeRefMut::PropertyName(n) => self.visit_property_name_mut(n),
NodeRefMut::MethodDefinition(n) => self.visit_method_definition_mut(n),
NodeRefMut::ObjectPattern(n) => self.visit_object_pattern_mut(n),
NodeRefMut::ArrayPattern(n) => self.visit_array_pattern_mut(n),
NodeRefMut::PropertyDefinition(n) => self.visit_property_definition_mut(n),
NodeRefMut::TemplateElement(n) => self.visit_template_element_mut(n),
NodeRefMut::SimplePropertyAccess(n) => self.visit_simple_property_access_mut(n),
NodeRefMut::PrivatePropertyAccess(n) => self.visit_private_property_access_mut(n),
NodeRefMut::SuperPropertyAccess(n) => self.visit_super_property_access_mut(n),
NodeRefMut::OptionalOperation(n) => self.visit_optional_operation_mut(n),
NodeRefMut::AssignTarget(n) => self.visit_assign_target_mut(n),
NodeRefMut::ObjectPatternElement(n) => self.visit_object_pattern_element_mut(n),
NodeRefMut::ArrayPatternElement(n) => self.visit_array_pattern_element_mut(n),
NodeRefMut::PropertyAccessField(n) => self.visit_property_access_field_mut(n),
NodeRefMut::OptionalOperationKind(n) => self.visit_optional_operation_kind_mut(n),
}
}
}
/// Denotes that a type may be visited, providing a method which allows a visitor to traverse its
/// private fields.
pub trait VisitWith {
/// Visit this node with the provided visitor.
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>;
/// Visit this node with the provided visitor mutably, allowing the visitor to modify private
/// fields.
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>;
}
// implementation for Sym as it is out-of-crate
impl VisitWith for Sym {
fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
core::ops::ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
core::ops::ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,36 @@
[package]
name = "boa_cli"
keywords = ["javascript", "compiler", "js", "cli"]
categories = ["command-line-utilities"]
default-run = "boa"
description.workspace = true
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
boa_engine = { workspace = true, features = ["deser", "console", "flowgraph"] }
boa_ast = { workspace = true, features = ["serde"]}
boa_parser.workspace = true
rustyline = "10.1.1"
rustyline-derive = "0.7.0"
clap = { version = "4.1.1", features = ["derive"] }
serde_json = "1.0.91"
colored = "2.0.0"
regex = "1.7.1"
phf = { version = "0.11.1", features = ["macros"] }
[features]
default = ["intl"]
intl = ["boa_engine/intl"]
[target.x86_64-unknown-linux-gnu.dependencies]
jemallocator = "0.5.0"
[[bin]]
name = "boa"
doc = false
path = "src/main.rs"

View File

@@ -0,0 +1,172 @@
use colored::{Color, Colorize};
use phf::{phf_set, Set};
use regex::{Captures, Regex};
use rustyline::{
error::ReadlineError,
highlight::Highlighter,
validate::{MatchingBracketValidator, ValidationContext, ValidationResult, Validator},
};
use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow;
const STRING_COLOR: Color = Color::Green;
const KEYWORD_COLOR: Color = Color::Yellow;
const PROPERTY_COLOR: Color = Color::Magenta;
const OPERATOR_COLOR: Color = Color::TrueColor {
r: 214,
g: 95,
b: 26,
};
const UNDEFINED_COLOR: Color = Color::TrueColor {
r: 100,
g: 100,
b: 100,
};
const NUMBER_COLOR: Color = Color::TrueColor {
r: 26,
g: 214,
b: 175,
};
const IDENTIFIER_COLOR: Color = Color::TrueColor {
r: 26,
g: 160,
b: 214,
};
#[allow(clippy::upper_case_acronyms)]
#[derive(Completer, Helper, Hinter)]
pub(crate) struct RLHelper {
highlighter: LineHighlighter,
validator: MatchingBracketValidator,
}
impl RLHelper {
pub(crate) fn new() -> Self {
Self {
highlighter: LineHighlighter,
validator: MatchingBracketValidator::new(),
}
}
}
impl Validator for RLHelper {
fn validate(
&self,
context: &mut ValidationContext<'_>,
) -> Result<ValidationResult, ReadlineError> {
self.validator.validate(context)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
impl Highlighter for RLHelper {
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
hint.into()
}
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
self.highlighter.highlight(line, pos)
}
fn highlight_candidate<'c>(
&self,
candidate: &'c str,
_completion: rustyline::CompletionType,
) -> Cow<'c, str> {
self.highlighter.highlight(candidate, 0)
}
fn highlight_char(&self, line: &str, _: usize) -> bool {
!line.is_empty()
}
}
static KEYWORDS: Set<&'static str> = phf_set! {
"break",
"case",
"catch",
"class",
"const",
"continue",
"default",
"delete",
"do",
"else",
"export",
"extends",
"finally",
"for",
"function",
"if",
"import",
"instanceof",
"new",
"return",
"super",
"switch",
"this",
"throw",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
"await",
"enum",
"let",
};
struct LineHighlighter;
impl Highlighter for LineHighlighter {
fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
let mut coloured = line.to_string();
let reg = Regex::new(
r#"(?x)
(?P<identifier>\b[$_\p{ID_Start}][$_\p{ID_Continue}\u{200C}\u{200D}]*\b) |
(?P<string_double_quote>"([^"\\]|\\.)*") |
(?P<string_single_quote>'([^'\\]|\\.)*') |
(?P<template_literal>`([^`\\]|\\.)*`) |
(?P<op>[+\-/*%~^!&|=<>;:]) |
(?P<number>0[bB][01](_?[01])*n?|0[oO][0-7](_?[0-7])*n?|0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?|(([0-9](_?[0-9])*\.([0-9](_?[0-9])*)?)|(([0-9](_?[0-9])*)?\.[0-9](_?[0-9])*)|([0-9](_?[0-9])*))([eE][+-]?[0-9](_?[0-9])*)?n?)"#,
)
.expect("could not compile regular expression");
coloured = reg
.replace_all(&coloured, |caps: &Captures<'_>| {
if let Some(cap) = caps.name("identifier") {
match cap.as_str() {
"true" | "false" | "null" | "Infinity" | "globalThis" => {
cap.as_str().color(PROPERTY_COLOR).to_string()
}
"undefined" => cap.as_str().color(UNDEFINED_COLOR).to_string(),
identifier if KEYWORDS.contains(identifier) => {
cap.as_str().color(KEYWORD_COLOR).bold().to_string()
}
_ => cap.as_str().color(IDENTIFIER_COLOR).to_string(),
}
} else if let Some(cap) = caps.name("string_double_quote") {
cap.as_str().color(STRING_COLOR).to_string()
} else if let Some(cap) = caps.name("string_single_quote") {
cap.as_str().color(STRING_COLOR).to_string()
} else if let Some(cap) = caps.name("template_literal") {
cap.as_str().color(STRING_COLOR).to_string()
} else if let Some(cap) = caps.name("op") {
cap.as_str().color(OPERATOR_COLOR).to_string()
} else if let Some(cap) = caps.name("number") {
cap.as_str().color(NUMBER_COLOR).to_string()
} else {
caps[0].to_string()
}
})
.to_string();
coloured.into()
}
}

View File

@@ -0,0 +1,387 @@
//! A ECMAScript REPL implementation based on boa_engine.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg"
)]
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
#![warn(missing_docs, clippy::dbg_macro)]
#![deny(
// rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html
warnings,
future_incompatible,
let_underscore,
nonstandard_style,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
unused,
// rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
macro_use_extern_crate,
meta_variable_misuse,
missing_abi,
missing_copy_implementations,
missing_debug_implementations,
non_ascii_idents,
noop_method_call,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_crate_dependencies,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_tuple_struct_fields,
variant_size_differences,
// rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::private_doc_tests,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
// clippy categories https://doc.rust-lang.org/clippy/
clippy::all,
clippy::correctness,
clippy::suspicious,
clippy::style,
clippy::complexity,
clippy::perf,
clippy::pedantic,
clippy::nursery,
)]
#![allow(clippy::option_if_let_else, clippy::redundant_pub_crate)]
mod helper;
use boa_ast::StatementList;
use boa_engine::{
context::ContextBuilder,
job::{JobQueue, NativeJob},
vm::flowgraph::{Direction, Graph},
Context, JsResult,
};
use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize};
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
use std::{cell::RefCell, collections::VecDeque, fs::read, fs::OpenOptions, io, path::PathBuf};
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
#[cfg_attr(
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
global_allocator
)]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
/// CLI configuration for Boa.
static CLI_HISTORY: &str = ".boa_history";
const READLINE_COLOR: Color = Color::Cyan;
// Added #[allow(clippy::option_option)] because to StructOpt an Option<Option<T>>
// is an optional argument that optionally takes a value ([--opt=[val]]).
// https://docs.rs/structopt/0.3.11/structopt/#type-magic
#[derive(Debug, Parser)]
#[command(author, version, about, name = "boa")]
struct Opt {
/// The JavaScript file(s) to be evaluated.
#[arg(name = "FILE", value_hint = ValueHint::FilePath)]
files: Vec<PathBuf>,
/// Dump the AST to stdout with the given format.
#[arg(
long,
short = 'a',
value_name = "FORMAT",
ignore_case = true,
value_enum,
conflicts_with = "graph"
)]
#[allow(clippy::option_option)]
dump_ast: Option<Option<DumpFormat>>,
/// Dump the AST to stdout with the given format.
#[arg(long, short, conflicts_with = "graph")]
trace: bool,
/// Use vi mode in the REPL
#[arg(long = "vi")]
vi_mode: bool,
/// Generate instruction flowgraph. Default is Graphviz.
#[arg(
long,
value_name = "FORMAT",
ignore_case = true,
value_enum,
group = "graph"
)]
#[allow(clippy::option_option)]
flowgraph: Option<Option<FlowgraphFormat>>,
/// Specifies the direction of the flowgraph. Default is TopToBottom.
#[arg(
long,
value_name = "FORMAT",
ignore_case = true,
value_enum,
requires = "graph"
)]
flowgraph_direction: Option<FlowgraphDirection>,
}
impl Opt {
/// Returns whether a dump flag has been used.
const fn has_dump_flag(&self) -> bool {
self.dump_ast.is_some()
}
}
#[derive(Debug, Clone, ValueEnum)]
enum DumpFormat {
/// The different types of format available for dumping.
// NOTE: This can easily support other formats just by
// adding a field to this enum and adding the necessary
// implementation. Example: Toml, Html, etc.
//
// NOTE: The fields of this enum are not doc comments because
// arg_enum! macro does not support it.
// This is the default format that you get from std::fmt::Debug.
Debug,
// This is a minified json format.
Json,
// This is a pretty printed json format.
JsonPretty,
}
/// Represents the format of the instruction flowgraph.
#[derive(Debug, Clone, Copy, ValueEnum)]
enum FlowgraphFormat {
/// Generates in [graphviz][graphviz] format.
///
/// [graphviz]: https://graphviz.org/
Graphviz,
/// Generates in [mermaid][mermaid] format.
///
/// [mermaid]: https://mermaid-js.github.io/mermaid/#/
Mermaid,
}
/// Represents the direction of the instruction flowgraph.
#[derive(Debug, Clone, Copy, ValueEnum)]
enum FlowgraphDirection {
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft,
}
/// Parses the the token stream into an AST and returns it.
///
/// Returns a error of type String with a message,
/// if the token stream has a parsing error.
fn parse_tokens<S>(src: S, context: &mut Context<'_>) -> Result<StatementList, String>
where
S: AsRef<[u8]>,
{
let src_bytes = src.as_ref();
boa_parser::Parser::new(src_bytes)
.parse_all(context.interner_mut())
.map_err(|e| format!("ParsingError: {e}"))
}
/// Dumps the AST to stdout with format controlled by the given arguments.
///
/// Returns a error of type String with a error message,
/// if the source has a syntax or parsing error.
fn dump<S>(src: S, args: &Opt, context: &mut Context<'_>) -> Result<(), String>
where
S: AsRef<[u8]>,
{
if let Some(ref arg) = args.dump_ast {
let ast = parse_tokens(src, context)?;
match arg {
Some(DumpFormat::Json) => println!(
"{}",
serde_json::to_string(&ast).expect("could not convert AST to a JSON string")
),
Some(DumpFormat::JsonPretty) => println!(
"{}",
serde_json::to_string_pretty(&ast)
.expect("could not convert AST to a pretty JSON string")
),
Some(DumpFormat::Debug) | None => println!("{ast:#?}"),
}
}
Ok(())
}
fn generate_flowgraph(
context: &mut Context<'_>,
src: &[u8],
format: FlowgraphFormat,
direction: Option<FlowgraphDirection>,
) -> JsResult<String> {
let ast = context.parse(src)?;
let code = context.compile(&ast)?;
let direction = match direction {
Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom,
Some(FlowgraphDirection::BottomToTop) => Direction::BottomToTop,
Some(FlowgraphDirection::LeftToRight) => Direction::LeftToRight,
Some(FlowgraphDirection::RightToLeft) => Direction::RightToLeft,
};
let mut graph = Graph::new(direction);
code.to_graph(context.interner(), graph.subgraph(String::default()));
let result = match format {
FlowgraphFormat::Graphviz => graph.to_graphviz_format(),
FlowgraphFormat::Mermaid => graph.to_mermaid_format(),
};
Ok(result)
}
fn main() -> Result<(), io::Error> {
let args = Opt::parse();
let queue = Jobs::default();
let mut context = ContextBuilder::new().job_queue(&queue).build();
// Trace Output
context.set_trace(args.trace);
for file in &args.files {
let buffer = read(file)?;
if args.has_dump_flag() {
if let Err(e) = dump(&buffer, &args, &mut context) {
eprintln!("{e}");
}
} else if let Some(flowgraph) = args.flowgraph {
match generate_flowgraph(
&mut context,
&buffer,
flowgraph.unwrap_or(FlowgraphFormat::Graphviz),
args.flowgraph_direction,
) {
Ok(v) => println!("{v}"),
Err(v) => eprintln!("Uncaught {v}"),
}
} else {
match context.eval(&buffer) {
Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"),
}
context.run_jobs();
}
}
if args.files.is_empty() {
let config = Config::builder()
.keyseq_timeout(1)
.edit_mode(if args.vi_mode {
EditMode::Vi
} else {
EditMode::Emacs
})
.build();
let mut editor =
Editor::with_config(config).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
// Check if the history file exists. If it does, create it.
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(CLI_HISTORY)?;
editor.load_history(CLI_HISTORY).map_err(|err| match err {
ReadlineError::Io(e) => e,
e => io::Error::new(io::ErrorKind::Other, e),
})?;
editor.set_helper(Some(helper::RLHelper::new()));
let readline = ">> ".color(READLINE_COLOR).bold().to_string();
loop {
match editor.readline(&readline) {
Ok(line) if line == ".exit" => break,
Err(ReadlineError::Interrupted | ReadlineError::Eof) => break,
Ok(line) => {
editor.add_history_entry(&line);
if args.has_dump_flag() {
if let Err(e) = dump(&line, &args, &mut context) {
eprintln!("{e}");
}
} else if let Some(flowgraph) = args.flowgraph {
match generate_flowgraph(
&mut context,
line.trim_end().as_bytes(),
flowgraph.unwrap_or(FlowgraphFormat::Graphviz),
args.flowgraph_direction,
) {
Ok(v) => println!("{v}"),
Err(v) => eprintln!("Uncaught {v}"),
}
} else {
match context.eval(line.trim_end()) {
Ok(v) => {
println!("{}", v.display());
}
Err(v) => {
eprintln!("{}: {}", "Uncaught".red(), v.to_string().red());
}
}
context.run_jobs();
}
}
Err(err) => {
eprintln!("Unknown error: {err:?}");
break;
}
}
}
editor
.save_history(CLI_HISTORY)
.expect("could not save CLI history");
}
Ok(())
}
#[derive(Default)]
struct Jobs(RefCell<VecDeque<NativeJob>>);
impl JobQueue for Jobs {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) {
self.0.borrow_mut().push_front(job);
}
fn run_jobs(&self, context: &mut Context<'_>) {
loop {
let jobs = std::mem::take(&mut *self.0.borrow_mut());
if jobs.is_empty() {
return;
}
for job in jobs {
if let Err(e) = job.call(context) {
eprintln!("Uncaught {e}");
}
}
}
}
}

View File

@@ -0,0 +1,95 @@
[package]
name = "boa_engine"
keywords = ["javascript", "js", "compiler", "lexer", "parser"]
categories = ["parser-implementations", "compilers"]
readme = "../README.md"
description.workspace = true
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[features]
profiler = ["boa_profiler/profiler"]
deser = ["boa_interner/serde", "boa_ast/serde"]
intl = [
"dep:boa_icu_provider",
"dep:icu_locid_transform",
"dep:icu_locid",
"dep:icu_datetime",
"dep:icu_plurals",
"dep:icu_provider",
"dep:icu_calendar",
"dep:icu_collator",
"dep:icu_list",
"dep:writeable",
"dep:sys-locale",
]
fuzz = ["boa_ast/fuzz", "boa_interner/fuzz"]
# Enable Boa's VM instruction flowgraph generator.
flowgraph = []
# Enable Boa's WHATWG console object implementation.
console = []
[dependencies]
boa_interner.workspace = true
boa_gc.workspace = true
boa_profiler.workspace = true
boa_macros.workspace = true
boa_ast.workspace = true
boa_parser.workspace = true
serde = { version = "1.0.152", features = ["derive", "rc"] }
serde_json = "1.0.91"
rand = "0.8.5"
num-traits = "0.2.15"
regress = "0.4.1"
rustc-hash = "1.1.0"
num-bigint = { version = "0.4.3", features = ["serde"] }
num-integer = "0.1.45"
bitflags = "1.3.2"
indexmap = "1.9.2"
ryu-js = "0.2.2"
chrono = "0.4.23"
fast-float = "0.2.0"
unicode-normalization = "0.1.22"
once_cell = "1.17.0"
tap = "1.0.1"
sptr = "0.3.2"
static_assertions = "1.1.0"
thiserror = "1.0.38"
dashmap = "5.4.0"
num_enum = "0.5.7"
# intl deps
boa_icu_provider = { workspace = true, optional = true }
icu_locid_transform = { version = "1.0.0", features = ["serde"], optional = true }
icu_locid = { version = "1.0.0", features = ["serde"], optional = true }
icu_datetime = { version = "1.0.0", features = ["serde", "experimental"], optional = true }
icu_calendar = { version = "1.0.0", optional = true }
icu_collator = { version = "1.0.1", features = ["serde"], optional = true }
icu_plurals = { version = "1.0.0", features = ["serde"], optional = true }
icu_provider = { version = "1.0.1", optional = true }
icu_list = { version = "1.0.0", features = ["serde"], optional = true }
writeable = { version = "0.5.0", optional = true }
sys-locale = { version = "0.2.3", optional = true }
[dev-dependencies]
criterion = "0.4.0"
float-cmp = "0.9.0"
[target.x86_64-unknown-linux-gnu.dev-dependencies]
jemallocator = "0.5.0"
[lib]
crate-type = ["cdylib", "lib"]
name = "boa_engine"
bench = false
[[bench]]
name = "full"
harness = false

View File

@@ -0,0 +1,10 @@
# Boa Benchmarks
For each js script in the `bench_scripts` folder, we create three benchmarks:
- Parser => lexing and parsing of the source code
- Compiler => compilation of the parsed statement list into bytecode
- Execution => execution of the bytecode in the vm
The idea is to check the performance of Boa in different scenarios.
Different parts of Boa are benchmarked separately to make the impact of local changes visible.

View File

@@ -0,0 +1 @@
((2 + 2) ** 3 / 100 - 5 ** 3 * -1000) ** 2 + 100 - 8;

View File

@@ -0,0 +1,7 @@
(function () {
let testArr = [1, 2, 3, 4, 5];
let res = testArr[2];
return res;
})();

View File

@@ -0,0 +1,8 @@
(function () {
let testArr = [];
for (let a = 0; a <= 500; a++) {
testArr[a] = "p" + a;
}
return testArr;
})();

Some files were not shown because too many files have changed in this diff Show More